From 4b13366750f9c5e489a9879af1f7ee37f3f2e9ac Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Tue, 7 Jul 2020 16:27:45 -0500 Subject: [PATCH 1/6] Switch release.js to use arguments instead of env vars --- package.json | 1 + scripts/release.js | 63 +++++++++++++++++++++++++++++++++------------- yarn.lock | 7 ++++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 95cd227f9f8..91765779f93 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^3.2.0", "@typescript-eslint/parser": "^3.2.0", + "argparse": "^1.0.10", "autoprefixer": "^7.1.5", "axe-core": "^3.3.2", "axe-puppeteer": "^1.0.0", diff --git a/scripts/release.js b/scripts/release.js index 65b169c3209..07975975084 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -1,9 +1,10 @@ +const argparse = require('argparse'); const chalk = require('chalk'); const fs = require('fs'); const git = require('nodegit'); const path = require('path'); const prompt = require('prompt'); -const { execSync } = require('child_process'); +let { execSync } = require('child_process'); const cwd = path.resolve(__dirname, '..'); const stdio = 'inherit'; @@ -18,6 +19,15 @@ const humanReadableTypes = { [TYPE_PATCH]: 'patch' }; +const args = parseArguments(); + +if (args.dry_run) { + console.warn(chalk.yellow('Dry run mode: no changes will be pushed to npm or Github')); + execSync = function() { + console.log.apply(arguments); + }; +} + (async function () { // make sure the release script is being run by npm (required for `npm publish` step) // https://github.com/yarnpkg/yarn/issues/5063 @@ -60,6 +70,29 @@ const humanReadableTypes = { execSync('npm run sync-docs', execOptions); }()).catch(e => console.error(e)); +function parseArguments() { + const parser = new argparse.ArgumentParser({ + addHelp: true, + description: 'Tag and publish a new version of EUI', + }); + + parser.addArgument('--type', { + help: 'Version type; can be "major", "minor" or "patch"', + choices: Object.values(humanReadableTypes), + }); + + parser.addArgument('--mfa', { + help: 'Multi-factor authentication code used by npm', + }); + + parser.addArgument('--dry-run', { + action: 'storeTrue', + defaultValue: false, + }); + + return parser.parseArgs(); +} + async function ensureMasterBranch() { // ignore master check in CI since it's checking out the HEAD commit instead if (process.env.CI === 'true') { @@ -131,23 +164,17 @@ async function getVersionTypeFromChangelog() { console.log(''); console.log(`${chalk.magenta('The recommended version update for these changes is')} ${chalk.blue(humanReadableRecommendation)}`); - // checking for VERSION_TYPE environment variable, which overrides prompts to - // the user to choose a version type; this is used by CI to automate releases - const envVersion = process.env.VERSION_TYPE; - if (envVersion) { + // checking for --type argument value; used by CI to automate releases + const versionType = args.type; + if (versionType) { // detected version type preference set - console.log(`${chalk.magenta('VERSION_TYPE environment variable identifed, set to')} ${chalk.blue(envVersion)}`); - - if (['major', 'minor', 'patch'].indexOf(envVersion) === -1) { - console.error(`${chalk.magenta('VERSION_TYPE environment variable is not "major", "minor" or "patch"')}`); - process.exit(1); - } + console.log(`${chalk.magenta('--type argument identifed, set to')} ${chalk.blue(versionType)}`); - if (envVersion !== recommendedType) { - console.warn(`${chalk.yellow('WARNING: VERSION_TYPE does not match recommended version update')}`); + if (versionType !== recommendedType) { + console.warn(`${chalk.yellow('WARNING: --type argument does not match recommended version update')}`); } - return envVersion; + return versionType; } else { console.log(`${chalk.magenta('What part of the package version do you want to bump?')} ${chalk.gray('(major, minor, patch)')}`); @@ -188,10 +215,10 @@ async function getOneTimePassword() { console.log(''); console.log(chalk.magenta('The @elastic organization requires membership and 2FA to publish')); - if (process.env.NPM_OTP) { - // skip prompting user for manual input if NPM_OTP env var is present - console.log(chalk.magenta('2FA code provided by NPM_OTP environment variable')); - return process.env.NPM_OTP; + if (args.mfa) { + // skip prompting user for manual input if --mfa argument is present + console.log(chalk.magenta('2FA code provided by ---mfa argument')); + return args.mfa; } console.log(chalk.magenta('What is your one-time password?')); diff --git a/yarn.lock b/yarn.lock index 9ad07a23dfe..2542ccb852b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1924,6 +1924,13 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +argparse@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" From c718c12a43db2528da126d15b9b80de0748fb20d Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Tue, 7 Jul 2020 16:47:32 -0500 Subject: [PATCH 2/6] Switch MFA code back to env var so it doesn't leak in CI logs --- scripts/release.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scripts/release.js b/scripts/release.js index 07975975084..058f370cd98 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -81,10 +81,6 @@ function parseArguments() { choices: Object.values(humanReadableTypes), }); - parser.addArgument('--mfa', { - help: 'Multi-factor authentication code used by npm', - }); - parser.addArgument('--dry-run', { action: 'storeTrue', defaultValue: false, @@ -215,10 +211,9 @@ async function getOneTimePassword() { console.log(''); console.log(chalk.magenta('The @elastic organization requires membership and 2FA to publish')); - if (args.mfa) { - // skip prompting user for manual input if --mfa argument is present - console.log(chalk.magenta('2FA code provided by ---mfa argument')); - return args.mfa; + if (process.env.NPM_OTP) { + console.log(chalk.magenta('2FA code provided by NPM_OTP environment variable')); + return process.env.NPM_OTP; } console.log(chalk.magenta('What is your one-time password?')); From cb544de56fc4f534f78537dd0aa3d8fcb7691436 Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Tue, 7 Jul 2020 16:47:52 -0500 Subject: [PATCH 3/6] Update job definition to use --type arg --- .ci/jobs/elastic+eui+npm-publish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/jobs/elastic+eui+npm-publish.yml b/.ci/jobs/elastic+eui+npm-publish.yml index a69236b91d0..363034d442f 100644 --- a/.ci/jobs/elastic+eui+npm-publish.yml +++ b/.ci/jobs/elastic+eui+npm-publish.yml @@ -49,5 +49,4 @@ set -x - export VERSION_TYPE=${version_type} - npm run release + npm run release -- --type=${version_type} From 130323875e63077a65691adca3f02d2e1e3463d1 Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Wed, 8 Jul 2020 15:51:43 -0500 Subject: [PATCH 4/6] Support breaking up release steps with args --- scripts/release.js | 75 +++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/scripts/release.js b/scripts/release.js index 058f370cd98..2a89c6a7f22 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -24,7 +24,7 @@ const args = parseArguments(); if (args.dry_run) { console.warn(chalk.yellow('Dry run mode: no changes will be pushed to npm or Github')); execSync = function() { - console.log.apply(arguments); + console.log.apply(null, arguments); }; } @@ -41,33 +41,46 @@ if (args.dry_run) { await ensureMasterBranch(); // run linting and unit tests - execSync('npm test', execOptions); + if (args.steps.indexOf('test') > -1) { + execSync('npm test', execOptions); + } // (trans|com)pile `src` into `lib` and `dist` - execSync('npm run build', execOptions); + if (args.steps.indexOf('build') > -1) { + execSync('npm run build', execOptions); + } - // prompt user for what type of version bump to make (major|minor|patch) - const versionTarget = await getVersionTypeFromChangelog(); + + if (args.steps.indexOf('version') > -1) { + // prompt user for what type of version bump to make (major|minor|patch) + const versionTarget = await getVersionTypeFromChangelog(); - // build may have generated a new src-docs/src/i18ntokens.json file, dirtying the git workspace - // it's important to track those changes with this release, so determine the changes and write them - // to src-docs/src/i18ntokens_changelog.json, comitting both to the workspace before running `npm version` - execSync(`npm run update-token-changelog -- ${versionTarget}`, execOptions); + // build may have generated a new src-docs/src/i18ntokens.json file, dirtying the git workspace + // it's important to track those changes with this release, so determine the changes and write them + // to src-docs/src/i18ntokens_changelog.json, comitting both to the workspace before running `npm version` + execSync(`npm run update-token-changelog -- ${versionTarget}`, execOptions); - // update package.json & package-lock.json version, git commit, git tag - execSync(`npm version ${versionTarget}`, execOptions); + // update package.json & package-lock.json version, git commit, git tag + execSync(`npm version ${versionTarget}`, execOptions); + } - // push the version commit & tag to upstream - execSync('git push upstream --tags', execOptions); + if (args.steps.indexOf('tag') > -1) { + // push the version commit & tag to upstream + execSync('git push upstream --tags', execOptions); + } - // prompt user for npm 2FA - const otp = await getOneTimePassword(); + if (args.steps.indexOf('publish') > -1) { + // prompt user for npm 2FA + const otp = await getOneTimePassword(); - // publish new version to npm - execSync(`npm publish --otp=${otp}`, execOptions); + // publish new version to npm + execSync(`npm publish --otp=${otp}`, execOptions); + } - // update docs, git commit, git push - execSync('npm run sync-docs', execOptions); + if (args.steps.indexOf('docs') > -1) { + // update docs, git commit, git push + execSync('npm run sync-docs', execOptions); + } }()).catch(e => console.error(e)); function parseArguments() { @@ -84,9 +97,29 @@ function parseArguments() { parser.addArgument('--dry-run', { action: 'storeTrue', defaultValue: false, + help: 'Dry run mode; no changes are made', }); - return parser.parseArgs(); + const allSteps = ['test', 'build', 'version', 'tag', 'publish', 'docs']; + parser.addArgument('--steps', { + help: 'Which release steps to run; a comma-separated list of values that can include "test", "build", "version", "tag", "publish" and "docs". If no value is given, all steps are run. Example: --steps=test,build,version,tag', + defaultValue: allSteps.join(','), + }); + + const args = parser.parseArgs(); + + // validate --steps argument + const steps = args.steps.trim().split(','); + const diff = steps.filter(x => allSteps.indexOf(x) === -1); + if (diff.length > 0) { + console.error(`Invalid --step value(s): ${diff.join(', ')}`); + process.exit(1); + } + + return { + ...args, + steps, + }; } async function ensureMasterBranch() { @@ -166,7 +199,7 @@ async function getVersionTypeFromChangelog() { // detected version type preference set console.log(`${chalk.magenta('--type argument identifed, set to')} ${chalk.blue(versionType)}`); - if (versionType !== recommendedType) { + if (versionType !== humanReadableRecommendation) { console.warn(`${chalk.yellow('WARNING: --type argument does not match recommended version update')}`); } From 795b4cacde4d70b247ab9eca37f358e337aeecd1 Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Thu, 9 Jul 2020 13:10:00 -0500 Subject: [PATCH 5/6] Break release up to fetch time-sensitive MFA token right before publish --- .ci/jobs/elastic+eui+npm-publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.ci/jobs/elastic+eui+npm-publish.yml b/.ci/jobs/elastic+eui+npm-publish.yml index 363034d442f..3bfd69aa694 100644 --- a/.ci/jobs/elastic+eui+npm-publish.yml +++ b/.ci/jobs/elastic+eui+npm-publish.yml @@ -34,6 +34,8 @@ npm install -g yarn yarn + npm run release -- --type=${version_type} --steps=test,build,version,tag + set +x export VAULT_TOKEN=$(vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") @@ -49,4 +51,4 @@ set -x - npm run release -- --type=${version_type} + npm run release -- --type=${version_type} --steps=publish,docs From d9788ac6adef1674227c671e83da04dd08f04322 Mon Sep 17 00:00:00 2001 From: Josh Mock Date: Mon, 13 Jul 2020 09:38:43 -0500 Subject: [PATCH 6/6] Strip whitespace from each step Co-authored-by: Chandler Prall --- scripts/release.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.js b/scripts/release.js index 2a89c6a7f22..f55cc9b4053 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -109,7 +109,7 @@ function parseArguments() { const args = parser.parseArgs(); // validate --steps argument - const steps = args.steps.trim().split(','); + const steps = args.steps.split(',').map(step => step.trim()); const diff = steps.filter(x => allSteps.indexOf(x) === -1); if (diff.length > 0) { console.error(`Invalid --step value(s): ${diff.join(', ')}`);