Skip to content

Commit

Permalink
feat: Add GitHub Actions integration (#335)
Browse files Browse the repository at this point in the history
  • Loading branch information
lgandecki committed May 5, 2020
1 parent a5910c9 commit ab555b9
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"request-debug": "^0.2.0",
"request-promise": "^4.1.1",
"travis-ci": "^2.1.1",
"tweetsodium": "^0.0.5",
"update-notifier": "^3.0.0",
"user-home": "^2.0.0",
"validator": "^12.0.0"
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const inquirer = require('inquirer');
const validator = require('validator');
const travis = require('./travis');
const circle = require('./circle');
const githubActions = require('./github-actions');

const cis = {
'Travis CI': travis.bind(null, 'https://api.travis-ci.org'),
'Travis CI Pro': travis.bind(null, 'https://api.travis-ci.com'),
'Travis CI Enterprise': travis,
'Circle CI': circle,
'Github Actions': githubActions,
'Other (prints tokens)': (pkg, info) => {
const message = `
${_.repeat('-', 46)}
Expand Down
85 changes: 85 additions & 0 deletions src/lib/github-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint require-atomic-updates: off */

const inquirer = require('inquirer');
const request = require('request-promise').defaults({resolveWithFullResponse: true});
const validator = require('validator');
const log = require('npmlog');
const sodium = require('tweetsodium');

async function ask2FA() {
return (
await inquirer.prompt([
{
type: 'input',
name: 'code',
message: 'What is your GitHub two-factor authentication code?',
validate: validator.isNumeric,
},
])
).code;
}

function createEncryptedSecret(value, key) {
const messageBytes = Buffer.from(value);
const keyBytes = Buffer.from(key, 'base64');

const encryptedBytes = sodium.seal(messageBytes, keyBytes);

return Buffer.from(encryptedBytes).toString('base64');
}

async function createSecret(info) {
const owner = info.ghrepo.slug[0];
const repo = info.ghrepo.slug[1];
try {
const response = await request({
method: 'GET',
url: `${info.github.endpoint}/repos/${owner}/${repo}/actions/secrets/public-key`,
auth: info.github,
headers: {'User-Agent': 'semantic-release', 'X-GitHub-OTP': info.github.code},
});
if (response.statusCode === 200) {
const {key, key_id: keyId} = JSON.parse(response.body);

const encryptedValue = createEncryptedSecret(info.npm.token, key);

const responsePut = await request({
method: 'PUT',
url: `${info.github.endpoint}/repos/${owner}/${repo}/actions/secrets/NPM_TOKEN`,
auth: info.github,
headers: {'User-Agent': 'semantic-release', 'X-GitHub-OTP': info.github.code},
json: true,
body: {
encrypted_value: encryptedValue, // eslint-disable-line camelcase
key_id: keyId, // eslint-disable-line camelcase
},
});

if (responsePut.statusCode !== 201 && responsePut.statusCode !== 204) {
throw new Error(
`Can’t add the NPM_TOKEN secret to Github Actions. Please add it manually: NPM_TOKEN=${info.npm.token}`
);
}
}
} catch (error) {
if (error.statusCode === 401 && error.response.headers['x-github-otp']) {
const [, type] = error.response.headers['x-github-otp'].split('; ');

if (info.github.retry) log.warn('Invalid two-factor authentication code.');
else log.info(`Two-factor authentication code needed via ${type}.`);

const code = await ask2FA();
info.github.code = code;
info.github.retry = true;
return createSecret(info);
}

throw error;
}
}

module.exports = async function (pkg, info) {
await createSecret(info);

log.info('Successfully created GitHub Actions NPM_TOKEN secret.');
};

0 comments on commit ab555b9

Please sign in to comment.