Skip to content

Commit 15f2489

Browse files
committed
[TASK] add possibility to add github repo secrets
1 parent 15879c4 commit 15f2489

6 files changed

Lines changed: 221 additions & 4 deletions

File tree

lib/cmd/init.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { checkIsOnline } from '../utils/check.js'
33
import { checkGithubTokenEnv, checkGithubToken, getGithubOrgs } from '../github/auth.js'
44
import { githubRepoFromTemplatePrompts, initType } from './init/prompts.js'
55
import { createGithubRepoWithTemplate, updateGithubRepoSettings, updateGithubBranchProtection } from '../github/repo.js'
6+
import { addRepositorySecrets } from '../github/secrets.js'
67
import { updateAndCommit } from '../github/commit.js'
78
import { confirmNextSteps } from '../utils/prompts.js'
89
import chalk from 'chalk'
@@ -30,9 +31,10 @@ async function init (data) {
3031
`You are about to create a new repository with the following settings:
3132
- New repo owner: ${chalk.blue(data.prompts?.repoFromTmpl?.newRepoOwner)}
3233
- New repo name: ${chalk.green(data.prompts?.repoFromTmpl?.newRepoName)}
33-
- Private repo: ${chalk.bold.yellow(data.prompts?.repoFromTmpl?.isPrivate)}
3434
- Template owner: ${data.prompts?.repoFromTmpl?.templateRepoOwner}
35-
- Template name: ${data.prompts?.repoFromTmpl?.templateRepoName}`
35+
- Template name: ${data.prompts?.repoFromTmpl?.templateRepoName}
36+
- Private repo: ${chalk.bold.yellow(data.prompts?.repoFromTmpl?.isPrivate)}
37+
- Add secrets: ${chalk.bold.yellow(data.prompts?.repoFromTmpl?.isSecrets)}`
3638
)
3739
await createGithubRepoWithTemplate(data)
3840
await updateGithubRepoSettings(data, {
@@ -70,6 +72,9 @@ async function init (data) {
7072
required_linear_history: true,
7173
required_conversation_resolution: true
7274
})
75+
if (data.prompts?.repoFromTmpl?.isSecrets) {
76+
await addRepositorySecrets(data)
77+
}
7378
await updateAndCommit(
7479
data.prompts?.repoFromTmpl?.newRepoOwner || '',
7580
data.prompts?.repoFromTmpl?.newRepoName || '',

lib/cmd/init/prompts.js

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ async function selectRepoTemplate () {
117117
* #### confirm if repo should be private
118118
* @async
119119
* @private
120-
* @returns {Promise<boolean>} - child theme
120+
* @returns {Promise<boolean>} - is private
121121
*/
122122
async function confirmRepoPrivacy () {
123123
const answer = await confirm({
@@ -126,6 +126,57 @@ async function confirmRepoPrivacy () {
126126
return answer
127127
}
128128

129+
/**
130+
* #### confirm if to add secrets to the repo
131+
* @async
132+
* @private
133+
* @returns {Promise<boolean>} - is secrets
134+
*/
135+
async function confirmRepoSecrets () {
136+
const answer = await confirm({
137+
message: 'Add secrets to the repository?'
138+
})
139+
return answer
140+
}
141+
142+
/**
143+
* #### collect secrets data
144+
* @async
145+
* @private
146+
* @returns {Promise<{hubspotPortalId: string, hubspotPersonalAccessKey: string}>} - repo secrets
147+
*/
148+
async function collectRepoSecrets () {
149+
const hubspotPortalId = await input({
150+
message: 'HUBSPOT_PORTAL_ID:',
151+
validate: (input) => {
152+
if (input.length < 1) {
153+
return 'Please enter a HubSpot portal ID'
154+
}
155+
if (!/^[0-9]*$/.test(input)) {
156+
return 'Please enter a valid HubSpot portal ID (numbers only)'
157+
}
158+
return true
159+
}
160+
})
161+
const hubspotPersonalAccessKey = await input({
162+
message: 'HUBSPOT_PERSONAL_ACCESS_KEY:',
163+
validate: (input) => {
164+
if (input.length < 1) {
165+
return 'Please enter a HubSpot personal access key'
166+
}
167+
if (!/^[a-zA-Z0-9-_]*$/.test(input)) {
168+
return 'Please enter a valid HubSpot personal access key (letters and numbers only)'
169+
}
170+
return true
171+
}
172+
})
173+
const repoSecrets = {
174+
hubspotPortalId,
175+
hubspotPersonalAccessKey
176+
}
177+
return repoSecrets
178+
}
179+
129180
/**
130181
* #### collect data for the new child theme
131182
* @param {LOCALDATA} data - env variables
@@ -136,14 +187,21 @@ async function githubRepoFromTemplatePrompts (data) {
136187
const projectName = await getProjectName()
137188
const repoOwner = await selectRepoOwner(data)
138189
const isPrivate = await confirmRepoPrivacy()
190+
const isSecrets = await confirmRepoSecrets()
191+
let repoSecrets
192+
if (isSecrets) {
193+
repoSecrets = await collectRepoSecrets()
194+
}
139195
data.prompts = {
140196
repoFromTmpl: {
141197
newRepoOwner: repoOwner,
142198
newRepoName: `${projectName}-${childTheme}-${new Date().getFullYear()}`,
143199
newRepoLabel: `${projectName} theme`,
144200
templateRepoOwner: 'Resultify',
145201
templateRepoName: childTheme,
146-
isPrivate
202+
isPrivate,
203+
isSecrets,
204+
repoSecrets
147205
}
148206
}
149207
}

lib/github/secrets.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as TYPES from '../types/types.js' // eslint-disable-line
2+
import { request } from '@octokit/request'
3+
import chalk from 'chalk'
4+
import ora from 'ora'
5+
import _sodium from 'libsodium-wrappers'
6+
7+
/**
8+
* @ignore
9+
* @typedef {TYPES.LOCALDATA} LOCALDATA {@link LOCALDATA}
10+
*/
11+
12+
/**
13+
* #### add repository secrets
14+
* @async
15+
* @memberof GITHUB
16+
* @param {LOCALDATA} data - data object
17+
* @returns undefined
18+
*/
19+
async function addRepositorySecrets (data) {
20+
const spinner = ora('Add repository secrets').start()
21+
try {
22+
const { hubspotPortalIdSecret, hubspotPersonalAccessKeySecret, publicKeyId } = await encryptSecrets(data)
23+
await request('PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}', {
24+
headers: {
25+
authorization: `token ${process.env.GITHUB_TOKEN}`
26+
},
27+
owner: data.prompts?.repoFromTmpl?.newRepoOwner || '',
28+
repo: data.prompts?.repoFromTmpl?.newRepoName || '',
29+
secret_name: 'HUBSPOT_PORTAL_ID',
30+
encrypted_value: hubspotPortalIdSecret,
31+
key_id: publicKeyId
32+
})
33+
await request('PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}', {
34+
headers: {
35+
authorization: `token ${process.env.GITHUB_TOKEN}`
36+
},
37+
owner: data.prompts?.repoFromTmpl?.newRepoOwner || '',
38+
repo: data.prompts?.repoFromTmpl?.newRepoName || '',
39+
secret_name: 'HUBSPOT_PERSONAL_ACCESS_KEY',
40+
encrypted_value: hubspotPersonalAccessKeySecret,
41+
key_id: publicKeyId
42+
})
43+
spinner.succeed()
44+
} catch (error) {
45+
spinner.fail()
46+
console.error(`${chalk.red('Error:')} ${error.message}`)
47+
if (process.env.RH_MODE === 'debug') {
48+
console.error(error)
49+
}
50+
process.exit(1)
51+
}
52+
}
53+
54+
/**
55+
* #### get a repository public key
56+
* @async
57+
* @private
58+
* @memberof GITHUB
59+
* @param {LOCALDATA} data - data object
60+
* @returns {Promise<{publicKey: string, publicKeyId: string}>} - public key
61+
*/
62+
async function getRepositoryPublicKey (data) {
63+
const spinner = ora('Get repository public key').start()
64+
try {
65+
const publicKeyResponce = await request('GET /repos/{owner}/{repo}/actions/secrets/public-key', {
66+
headers: {
67+
authorization: `token ${process.env.GITHUB_TOKEN}`
68+
},
69+
owner: data.prompts?.repoFromTmpl?.newRepoOwner || '',
70+
repo: data.prompts?.repoFromTmpl?.newRepoName || ''
71+
})
72+
const publicKey = publicKeyResponce.data.key
73+
const publicKeyId = publicKeyResponce.data.key_id
74+
spinner.succeed()
75+
return { publicKey, publicKeyId }
76+
} catch (error) {
77+
spinner.fail()
78+
console.error(`${chalk.red('Error:')} ${error.message}`)
79+
if (process.env.RH_MODE === 'debug') {
80+
console.error(error)
81+
}
82+
process.exit(1)
83+
}
84+
}
85+
86+
/**
87+
* #### Encrypt secrets for the REST API
88+
* @async
89+
* @private
90+
* @memberof GITHUB
91+
* @param {LOCALDATA} data - data object
92+
* @returns {Promise<{hubspotPortalIdSecret: string, hubspotPersonalAccessKeySecret: string, publicKeyId: string}>} - encrypted secrets and public key ID
93+
*/
94+
async function encryptSecrets (data) {
95+
const spinner = ora('Encrypting secret').start()
96+
try {
97+
const hubspotPortalIdSecretVal = data.prompts?.repoFromTmpl?.repoSecrets?.hubspotPortalId || ''
98+
const hubspotPersonalAccessKeySecretVal = data.prompts?.repoFromTmpl?.repoSecrets?.hubspotPersonalAccessKey || ''
99+
const publicKey = await getRepositoryPublicKey(data)
100+
await _sodium.ready
101+
const sodium = _sodium
102+
103+
// Convert the secret and key to a Uint8Array.
104+
const binkey = sodium.from_base64(publicKey.publicKey, sodium.base64_variants.ORIGINAL)
105+
const binHubspotPortalIdSecret = sodium.from_string(hubspotPortalIdSecretVal)
106+
const binHubspotPersonalAccessKeySecret = sodium.from_string(hubspotPersonalAccessKeySecretVal)
107+
108+
// Encrypt the secret using libsodium
109+
const encryptHubspotPortalIdSecret = sodium.crypto_box_seal(binHubspotPortalIdSecret, binkey)
110+
const encryptHubspotPersonalAccessKeySecret = sodium.crypto_box_seal(binHubspotPersonalAccessKeySecret, binkey)
111+
112+
// Convert the encrypted Uint8Array to Base64
113+
const hubspotPortalIdSecret = sodium.to_base64(encryptHubspotPortalIdSecret, sodium.base64_variants.ORIGINAL)
114+
const hubspotPersonalAccessKeySecret = sodium.to_base64(encryptHubspotPersonalAccessKeySecret, sodium.base64_variants.ORIGINAL)
115+
spinner.succeed()
116+
return { hubspotPortalIdSecret, hubspotPersonalAccessKeySecret, publicKeyId: publicKey.publicKeyId }
117+
} catch (error) {
118+
spinner.fail()
119+
console.error(`${chalk.red('Error:')} ${error.message}`)
120+
if (process.env.RH_MODE === 'debug') {
121+
console.error(error)
122+
}
123+
process.exit(1)
124+
}
125+
}
126+
127+
export { addRepositorySecrets }

lib/types/types.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@
8787
* @property {string} templateRepoOwner - template repo owner
8888
* @property {string} templateRepoName - template repo name
8989
* @property {boolean} isPrivate - is private repo
90+
* @property {boolean} isSecrets - confirm adding secrets
91+
* @property {Object} [repoSecrets] - is private repo
92+
* @property {string} repoSecrets.hubspotPortalId - is private repo
93+
* @property {string} repoSecrets.hubspotPersonalAccessKey - is private repo
9094
*/
9195

9296
/**

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@
5151
"git-url-parse": "14.0.0",
5252
"globby": "14.0.2",
5353
"is-online": "10.0.0",
54+
"libsodium-wrappers": "0.7.13",
5455
"minimist": "1.2.8",
5556
"ora": "8.0.1",
5657
"semver": "7.6.2",
5758
"signal-exit": "4.1.0"
5859
},
5960
"devDependencies": {
6061
"@types/git-url-parse": "~9.0.3",
62+
"@types/libsodium-wrappers": "^0.7.14",
6163
"@types/minimist": "~1.2.5",
6264
"@types/node": "~20.14.9",
6365
"@types/semver": "~7.5.8",

0 commit comments

Comments
 (0)