From a9dfd9e4710df7cc516ddee608970427e4cd1bfe Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 10:39:57 -0700 Subject: [PATCH 01/16] First chunk of new release script --- scripts/release/README.md | 1 + .../commands/check-circle-ci-status.js | 42 ++ .../commands/check-environment-variables.js | 20 + .../release/commands/check-npm-permissions.js | 39 ++ .../commands/check-package-dependencies.js | 53 ++ .../commands/check-uncommitted-changes.js | 18 + .../commands/install-yarn-dependencies.js | 12 + scripts/release/commands/parse-parameters.js | 73 +++ .../commands/print-changelog-instructions.js | 17 + .../release/commands/run-automated-tests.js | 31 + .../release/commands/update-error-codes.js | 25 + scripts/release/commands/update-git.js | 17 + .../commands/update-package-versions.js | 58 ++ .../commands/update-yarn-dependencies.js | 38 ++ scripts/release/config.js | 16 + scripts/release/logo.js | 19 + scripts/release/package.json | 21 + scripts/release/release.js | 56 ++ scripts/release/utils.js | 45 ++ scripts/release/yarn.lock | 576 ++++++++++++++++++ 20 files changed, 1177 insertions(+) create mode 100644 scripts/release/README.md create mode 100644 scripts/release/commands/check-circle-ci-status.js create mode 100644 scripts/release/commands/check-environment-variables.js create mode 100644 scripts/release/commands/check-npm-permissions.js create mode 100644 scripts/release/commands/check-package-dependencies.js create mode 100644 scripts/release/commands/check-uncommitted-changes.js create mode 100644 scripts/release/commands/install-yarn-dependencies.js create mode 100644 scripts/release/commands/parse-parameters.js create mode 100644 scripts/release/commands/print-changelog-instructions.js create mode 100644 scripts/release/commands/run-automated-tests.js create mode 100644 scripts/release/commands/update-error-codes.js create mode 100644 scripts/release/commands/update-git.js create mode 100644 scripts/release/commands/update-package-versions.js create mode 100644 scripts/release/commands/update-yarn-dependencies.js create mode 100644 scripts/release/config.js create mode 100644 scripts/release/logo.js create mode 100644 scripts/release/package.json create mode 100755 scripts/release/release.js create mode 100644 scripts/release/utils.js create mode 100644 scripts/release/yarn.lock diff --git a/scripts/release/README.md b/scripts/release/README.md new file mode 100644 index 0000000000000..a330cd98ef0f9 --- /dev/null +++ b/scripts/release/README.md @@ -0,0 +1 @@ +TODO Document \ No newline at end of file diff --git a/scripts/release/commands/check-circle-ci-status.js b/scripts/release/commands/check-circle-ci-status.js new file mode 100644 index 0000000000000..3b029705c5bb8 --- /dev/null +++ b/scripts/release/commands/check-circle-ci-status.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const http = require('request-promise-json'); +const {execRead, logPromise} = require('../utils'); + +// https://circleci.com/docs/api/v1-reference/#projects +const CIRCLE_CI_BASE_URL = + 'https://circleci.com/api/v1.1/project/github/facebook/react/tree/master'; + +const check = async ({cwd}) => { + const token = process.env.CIRCLE_CI_API_TOKEN; + const uri = `${CIRCLE_CI_BASE_URL}?circle-token=${token}&limit=1`; + + const response = await http.get(uri, true); + const {outcome, status, vcs_revision: ciRevision} = response[0]; + + const gitRevision = await execRead('git rev-parse HEAD', {cwd}); + + if (gitRevision !== ciRevision) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('CircleCI is stale')}\n\n` + + `The latest Git revision is ${chalk.yellow(gitRevision)}.\n` + + `The most recent CircleCI revision is ${chalk.yellow(ciRevision)}.\n` + + 'Please wait for CircleCI to catch up.' + ); + process.exit(1); + } else if (outcome !== 'success') { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('CircleCI failed')}\n\n` + + `The most recent CircleCI build has a status of ${chalk.red(outcome || status)}.\n` + + 'Please retry this build in CircleCI if you believe this is an error.' + ); + process.exit(1); + } +}; + +module.exports = async params => { + return logPromise(check(params), 'Checking CircleCI status'); +}; diff --git a/scripts/release/commands/check-environment-variables.js b/scripts/release/commands/check-environment-variables.js new file mode 100644 index 0000000000000..78212f36021b0 --- /dev/null +++ b/scripts/release/commands/check-environment-variables.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); + +module.exports = () => { + if (!process.env.CIRCLE_CI_API_TOKEN) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Missing CircleCI API token')}\n\n` + + 'The CircleCI API is used to check the status of the latest commit.\n' + + `This API requires a token which must be exposed via ${chalk.yellow('CIRCLE_CI_API_TOKEN')}.\n` + + 'For instructions on creating this token check out the link below:\n\n' + + chalk.gray( + 'https://circleci.com/docs/api/v1-reference/#getting-started' + ) + ); + process.exit(1); + } +}; diff --git a/scripts/release/commands/check-npm-permissions.js b/scripts/release/commands/check-npm-permissions.js new file mode 100644 index 0000000000000..6a4ae943ca1ab --- /dev/null +++ b/scripts/release/commands/check-npm-permissions.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {execRead, logPromise} = require('../utils'); +const {projects} = require('../config'); + +module.exports = async () => { + const currentUser = await execRead('npm whoami'); + const failedProjects = []; + + const checkProject = async project => { + const owners = (await execRead(`npm owner ls ${project}`)) + .split('\n') + .filter(owner => owner) + .map(owner => owner.split(' ')[0]); + + if (!owners.includes(currentUser)) { + failedProjects.push(project); + } + }; + + await logPromise( + Promise.all(projects.map(checkProject)), + `Checking ${chalk.yellow(currentUser)}'s NPM permissions` + ); + + if (failedProjects.length) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Insufficient NPM permissions')}\n\n` + + `NPM user ${chalk.yellow(currentUser)} is not an owner for: ` + + chalk.red(failedProjects.join(', ')) + + '\n' + + 'Please contact a React team member to be added to the above project(s).' + ); + process.exit(1); + } +}; diff --git a/scripts/release/commands/check-package-dependencies.js b/scripts/release/commands/check-package-dependencies.js new file mode 100644 index 0000000000000..4e361b048ac5d --- /dev/null +++ b/scripts/release/commands/check-package-dependencies.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {readJson} = require('fs-extra'); +const {join} = require('path'); +const {dependencies, projects} = require('../config'); +const {logPromise} = require('../utils'); + +const check = async ({cwd}) => { + const rootPackage = await readJson(join(cwd, 'package.json')); + + const projectPackages = []; + for (let i = 0; i < projects.length; i++) { + const project = projects[i]; + projectPackages.push( + await readJson(join(cwd, join('packages', project), 'package.json')) + ); + } + + const invalidDependencies = []; + + const checkModule = async module => { + const rootVersion = rootPackage.devDependencies[module]; + + projectPackages.forEach(projectPackage => { + const projectVersion = projectPackage.dependencies[module]; + + if (rootVersion !== projectVersion && projectVersion !== undefined) { + invalidDependencies.push( + `${chalk.yellow(module)} is ${chalk.red(rootVersion)} in root but ` + + `${chalk.red(projectVersion)} in ${chalk.yellow(projectPackage.name)}` + ); + } + }); + }; + + await Promise.all(dependencies.map(checkModule)); + + if (invalidDependencies.length) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Dependency mismatch')}\n\n` + + `The following dependencies do not match between the root package and NPM dependencies:\n` + + invalidDependencies.join('\n') + ); + process.exit(1); + } +}; + +module.exports = async ({cwd}) => { + logPromise(check({cwd}), 'Checking runtime dependencies'); +}; diff --git a/scripts/release/commands/check-uncommitted-changes.js b/scripts/release/commands/check-uncommitted-changes.js new file mode 100644 index 0000000000000..d580447e4dc87 --- /dev/null +++ b/scripts/release/commands/check-uncommitted-changes.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {execRead} = require('../utils'); + +module.exports = async ({cwd}) => { + const status = await execRead('git diff HEAD', {cwd}); + + if (status) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Uncommitted local changes')}\n\n` + + 'Please revert or commit all local changes before making a release.' + ); + process.exit(1); + } +}; diff --git a/scripts/release/commands/install-yarn-dependencies.js b/scripts/release/commands/install-yarn-dependencies.js new file mode 100644 index 0000000000000..baf8c3ac221d5 --- /dev/null +++ b/scripts/release/commands/install-yarn-dependencies.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child-process-promise'); +const {logPromise} = require('../utils'); + +const install = async ({cwd}) => await exec('yarn', {cwd}); + +module.exports = async ({cwd}) => { + return logPromise(install({cwd}), 'Installing NPM dependencies'); +}; diff --git a/scripts/release/commands/parse-parameters.js b/scripts/release/commands/parse-parameters.js new file mode 100644 index 0000000000000..46c282c10dd0b --- /dev/null +++ b/scripts/release/commands/parse-parameters.js @@ -0,0 +1,73 @@ +#!/usr/bin/env node + +'use strict'; + +const commandLineArgs = require('command-line-args'); +const commandLineUsage = require('command-line-usage'); +const logo = require('../logo'); + +module.exports = () => { + const paramDefinitions = [ + { + name: 'dry', + type: Boolean, + defaultValue: false, + description: 'Build artifacts but do not commit or publish', + }, + { + name: 'path', + type: String, + alias: 'p', + description: 'Location of React repository to release; defaults to [bold]{cwd}', + }, + { + name: 'version', + type: String, + alias: 'v', + description: 'Semantic version number', + }, + ]; + + const params = commandLineArgs(paramDefinitions); + + if (!params.version) { + const usage = commandLineUsage([ + { + content: logo, + raw: true, + }, + { + header: 'React Release Manager', + content: 'Automated release script.', + }, + { + header: 'Options', + optionList: paramDefinitions, + }, + { + header: 'Examples', + content: [ + { + desc: '1. A concise example.', + example: '$ ./release [bold]{-v} [underline]{16.0.0}', + }, + { + desc: '2. Dry run publishing a release candidate.', + example: '$ ./release [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}', + }, + { + desc: '3. Release from another checkout.', + example: '$ ./release [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo', + }, + ], + }, + ]); + console.log(usage); + process.exit(1); + } + + return { + ...params, + cwd: params.path, // For script convenience + }; +}; diff --git a/scripts/release/commands/print-changelog-instructions.js b/scripts/release/commands/print-changelog-instructions.js new file mode 100644 index 0000000000000..baacb5b3afc46 --- /dev/null +++ b/scripts/release/commands/print-changelog-instructions.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); + +module.exports = () => { + console.log( + `Now it's time to addd release notes to the ${chalk.yellow.bold('CHANGELOG.md')}!\n\n` + + 'Here are a few things to keep in mind:\n' + + '• The changes should be easy to understand. ' + + '(Friendly one-liners are better than PR titles.)\n' + + '• Make sure all contributors are credited.\n' + + '• Verify that the markup is valid by previewing it in the editor: ' + + chalk.cyan('https://github.com/facebook/react/edit/master/CHANGELOG.md') + ); +}; diff --git a/scripts/release/commands/run-automated-tests.js b/scripts/release/commands/run-automated-tests.js new file mode 100644 index 0000000000000..ba3af16897a93 --- /dev/null +++ b/scripts/release/commands/run-automated-tests.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {logPromise} = require('../utils'); + +const runYarnTask = async (cwd, task, errorMessage) => { + try { + await exec(`yarn ${task}`, {cwd}); + } catch (error) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red(errorMessage)}\n\n` + + error.stdout + ); + process.exit(1); + } +}; + +module.exports = async ({cwd}) => { + await logPromise(runYarnTask(cwd, 'lint', 'Lint failed'), 'Running ESLint'); + await logPromise( + runYarnTask(cwd, 'flow', 'Flow failed'), + 'Running Flow checks' + ); + await logPromise( + runYarnTask(cwd, 'jest', 'Jest failed'), + 'Running Jest tests' + ); +}; diff --git a/scripts/release/commands/update-error-codes.js b/scripts/release/commands/update-error-codes.js new file mode 100644 index 0000000000000..82712307c6eb8 --- /dev/null +++ b/scripts/release/commands/update-error-codes.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child-process-promise'); +const {execRead, logPromise} = require('../utils'); + +const run = async ({cwd, dry, version}) => { + await exec('yarn build -- --extract-errors', {cwd}); + + const modifiedFiles = await execRead('git ls-files -m', {cwd}); + + if (!dry) { + if (modifiedFiles.includes('scripts/error-codes/codes.json')) { + await exec('git add scripts/error-codes/codes.json', {cwd}); + await exec(`git commit -m "Update error codes for ${version} release"`, { + cwd, + }); + } + } +}; + +module.exports = async params => { + return logPromise(run(params), 'Updating error codes'); +}; diff --git a/scripts/release/commands/update-git.js b/scripts/release/commands/update-git.js new file mode 100644 index 0000000000000..1bd873beb8306 --- /dev/null +++ b/scripts/release/commands/update-git.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {logPromise} = require('../utils'); + +const update = async ({cwd}) => { + await exec('git fetch', {cwd}); + await exec('git checkout master', {cwd}); + await exec('git pull', {cwd}); +}; + +module.exports = async ({cwd}) => { + return logPromise(update({cwd}), `Updating checkout ${chalk.yellow(cwd)}`); +}; diff --git a/scripts/release/commands/update-package-versions.js b/scripts/release/commands/update-package-versions.js new file mode 100644 index 0000000000000..44700c9f61db6 --- /dev/null +++ b/scripts/release/commands/update-package-versions.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {readFileSync, writeFileSync} = require('fs'); +const {readJson, writeJson} = require('fs-extra'); +const {join} = require('path'); +const {projects} = require('../config'); +const {logPromise} = require('../utils'); + +const update = async ({cwd, dry, version}) => { + try { + // Update root package.json + const packagePath = join(cwd, 'package.json'); + const rootPackage = await readJson(packagePath); + rootPackage.version = version; + await writeJson(packagePath, rootPackage, {spaces: 2}); + + // Update ReactVersion source file + const reactVersionPath = join(cwd, 'src/ReactVersion.js'); + const reactVersion = readFileSync(reactVersionPath, 'utf8') + .replace( + /module\.exports = '[^']+';/, + `module.exports = '${version}';` + ); + writeFileSync(reactVersionPath, reactVersion); + + // Update renderer versions and peer dependencies + const updateProjectPackage = async project => { + const path = join(cwd, 'packages', project, 'package.json'); + const json = await readJson(path); + json.version = version; + if (project !== 'react') { + json.peerDependencies.react = `^${version}`; + } + await writeJson(path, json, {spaces: 2}); + }; + await Promise.all(projects.map(updateProjectPackage)); + + // Version sanity check + await exec('yarn version-check', {cwd}); + + if (!dry) { + await exec(`git commit -am "Updating package versions for release ${version}"`, {cwd}); + } + } catch (error) { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Failed while updating package versions')}` + ); + process.exit(1); + } +}; + +module.exports = async params => { + return logPromise(update(params), 'Updating package versions'); +}; diff --git a/scripts/release/commands/update-yarn-dependencies.js b/scripts/release/commands/update-yarn-dependencies.js new file mode 100644 index 0000000000000..63243d9bd2327 --- /dev/null +++ b/scripts/release/commands/update-yarn-dependencies.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {dependencies} = require('../config'); +const {execRead, logPromise} = require('../utils'); + +const update = async ({cwd, dry, version}) => { + await exec(`yarn upgrade ${dependencies.join(' ')}`, {cwd}); + + const modifiedFiles = await execRead('git ls-files -m', {cwd}); + + // If yarn.lock has changed we should commit it. + // If anything else has changed, it's an error. + if (modifiedFiles) { + if (modifiedFiles !== 'yarn.lock') { + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Unexpected modifications')}\n\n` + + `The following files have been modified unexpectedly:\n` + + chalk.gray(modifiedFiles) + ); + process.exit(1); + } + + if (!dry) { + await exec( + `git commit -am "Updating yarn.lock file for ${version} release"`, + {cwd} + ); + } + } +}; + +module.exports = async params => { + return logPromise(update(params), 'Upgrading NPM dependencies'); +}; diff --git a/scripts/release/config.js b/scripts/release/config.js new file mode 100644 index 0000000000000..57acfcd2e4f95 --- /dev/null +++ b/scripts/release/config.js @@ -0,0 +1,16 @@ +'use strict'; + +const dependencies = ['fbjs', 'object-assign', 'prop-types']; + +const projects = [ + 'react', + 'react-art', + 'react-dom', + 'react-reconciler', + 'react-test-renderer', +]; + +module.exports = { + dependencies, + projects, +}; diff --git a/scripts/release/logo.js b/scripts/release/logo.js new file mode 100644 index 0000000000000..63c576e3506a4 --- /dev/null +++ b/scripts/release/logo.js @@ -0,0 +1,19 @@ +'use strict'; + +const chalk = require('chalk'); + +const LOGO = chalk + .hex('#61dafb') + .bold( + ` + __ +_______ ____ _____ _____/ |_ +\\_ __ \\_/ __ \\\\__ \\ _/ ___\\ __\\ + | | \\/\\ ___/ / __ \\\\ \\___| | + |__| \\___ >____ /\\___ >__| + \\/ \\/ \\/ +` + ) + .replace(/(^\n+|\n+$)/g, ''); + +module.exports = LOGO; diff --git a/scripts/release/package.json b/scripts/release/package.json new file mode 100644 index 0000000000000..4856585051b37 --- /dev/null +++ b/scripts/release/package.json @@ -0,0 +1,21 @@ +{ + "name": "release", + "version": "1.0.0", + "description": "TODO Document", + "main": "release.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^2.1.0", + "child-process-promise": "^2.2.1", + "cli-spinners": "^1.1.0", + "command-line-args": "^4.0.7", + "command-line-usage": "^4.0.1", + "fs-extra": "^4.0.2", + "log-update": "^2.1.0", + "request-promise-json": "^1.0.4" + } +} diff --git a/scripts/release/release.js b/scripts/release/release.js new file mode 100755 index 0000000000000..aa26a44af4433 --- /dev/null +++ b/scripts/release/release.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const logUpdate = require('log-update'); + +const checkCircleCiStatus = require('./commands/check-circle-ci-status'); +const checkEnvironmentVariables = require('./commands/check-environment-variables'); +const checkNpmPermissions = require('./commands/check-npm-permissions'); +const checkPackageDependencies = require('./commands/check-package-dependencies'); +const checkUncommittedChanges = require('./commands/check-uncommitted-changes'); +const installYarnDependencies = require('./commands/install-yarn-dependencies'); +const parseParameters = require('./commands/parse-parameters'); +const printChangelogInstructions = require('./commands/print-changelog-instructions'); +const runAutomatedTests = require('./commands/run-automated-tests'); +const updateGit = require('./commands/update-git'); +const updateErrorCodes = require('./commands/update-error-codes'); +const updatePackageVersions = require('./commands/update-package-versions'); +const updateYarnDependencies = require('./commands/update-yarn-dependencies'); + +// Follows the steps outlined in github.com/facebook/react/issues/10620 +const run = async () => { + const params = parseParameters(); + + try { +/* TODO + await checkEnvironmentVariables(params); + await checkUncommittedChanges(params); + await checkNpmPermissions(params); + await updateGit(params); + await checkCircleCiStatus(params); + await installYarnDependencies(params); + await checkPackageDependencies(params); + await updateYarnDependencies(params); + await runAutomatedTests(params); + await updateErrorCodes(params); + await updatePackageVersions(params); +*/ + // TODO 'yarn build' actual release artifacts (is this necessary after the error codes build step? or could we re-arrange and speed up?) + await printChangelogInstructions(); + // TODO Print testing instructions + // TODO Print publish instructions (and create separate publish.js script) + // TODO Update website (instructions) + // TODO Update bower + // TODO Test via create-react-app + } catch (error) { + logUpdate.clear(); + + console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); + + process.exit(1); + } +}; + +run(); diff --git a/scripts/release/utils.js b/scripts/release/utils.js new file mode 100644 index 0000000000000..49a0ed679b0de --- /dev/null +++ b/scripts/release/utils.js @@ -0,0 +1,45 @@ +'use strict'; + +const chalk = require('chalk'); +const {dots} = require('cli-spinners'); +const {exec} = require('child-process-promise'); +const logUpdate = require('log-update'); + +const execRead = async (command, options) => { + const {stdout} = await exec(command, options); + + return stdout.trim(); +}; + +const logPromise = async (promise, text, completedLabel = '') => { + const {frames, interval} = dots; + + let index = 0; + + const id = setInterval(() => { + index = ++index % frames.length; + logUpdate( + `${chalk.yellow(frames[index])} ${text} ${chalk.gray('- this may take a few seconds')}` + ); + }, interval); + + try { + const returnValue = await promise; + + clearInterval(id); + + logUpdate(`${chalk.green('✓')} ${text} ${chalk.gray(completedLabel)}`); + logUpdate.done(); + + return returnValue; + } catch (error) { + logUpdate.clear(); + + throw error; + } +}; + +module.exports = { + execRead, + logPromise, +}; diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock new file mode 100644 index 0000000000000..96c928d17aeaa --- /dev/null +++ b/scripts/release/yarn.lock @@ -0,0 +1,576 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ajv@^5.1.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +ansi-escape-sequences@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ansi-escape-sequences/-/ansi-escape-sequences-4.0.0.tgz#e0ecb042958b71e42942d35c1fcf1d9b00a0f67e" + dependencies: + array-back "^2.0.0" + +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +array-back@^1.0.3, array-back@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" + dependencies: + typical "^2.6.0" + +array-back@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022" + dependencies: + typical "^2.6.1" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +child-process-promise@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/child-process-promise/-/child-process-promise-2.2.1.tgz#4730a11ef610fad450b8f223c79d31d7bdad8074" + dependencies: + cross-spawn "^4.0.2" + node-version "^1.0.0" + promise-polyfill "^6.0.1" + +cli-cursor@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.1.0.tgz#f1847b168844d917a671eb9d147e3df497c90d06" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +color-convert@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +command-line-args@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46" + dependencies: + array-back "^2.0.0" + find-replace "^1.0.3" + typical "^2.6.1" + +command-line-usage@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-4.0.1.tgz#d89cf16c8ae71e8e8a6e6aabae1652af76ff644e" + dependencies: + ansi-escape-sequences "^4.0.0" + array-back "^2.0.0" + table-layout "^0.4.1" + typical "^2.6.1" + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +deep-extend@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +find-replace@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" + dependencies: + array-back "^1.0.4" + test-value "^2.1.0" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +fs-extra@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + +hoek@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +lodash.padend@^4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" + +log-update@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.1.0.tgz#ea37258b5354edb02e73b29190016c87d1c87141" + dependencies: + ansi-escapes "^2.0.0" + cli-cursor "^2.0.0" + wrap-ansi "^3.0.1" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@^2.1.12, mime-types@~2.1.17: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +node-version@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/node-version/-/node-version-1.1.0.tgz#f437d7ba407e65e2c4eaef8887b1718ba523d4f0" + +oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +promise-polyfill@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.0.2.tgz#d9c86d3dc4dc2df9016e88946defd69b49b41162" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +q@^1.1.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" + +qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +reduce-flatten@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz#258c78efd153ddf93cb561237f61184f3696e327" + +request-promise-json@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/request-promise-json/-/request-promise-json-1.0.4.tgz#71119e82a263c900a72be255c65e49a3d3ca5e6f" + dependencies: + q "^1.1.2" + request "^2.51.0" + +request@^2.51.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +sntp@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b" + dependencies: + hoek "4.x.x" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +stringstream@~0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +supports-color@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +table-layout@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-0.4.2.tgz#10e9043c142a1e2d155da7257e478f0ef4981786" + dependencies: + array-back "^2.0.0" + deep-extend "~0.5.0" + lodash.padend "^4.6.1" + typical "^2.6.1" + wordwrapjs "^3.0.0" + +test-value@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291" + dependencies: + array-back "^1.0.3" + typical "^2.6.0" + +tough-cookie@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + dependencies: + punycode "^1.4.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +typical@^2.6.0, typical@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" + +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + +uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wordwrapjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz#c94c372894cadc6feb1a66bff64e1d9af92c5d1e" + dependencies: + reduce-flatten "^1.0.1" + typical "^2.6.1" + +wrap-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" From 59041a4a1019f1eff2fe05c356d2b27dff357162 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 10:54:29 -0700 Subject: [PATCH 02/16] Re-ordered build steps to combine error codes and releases --- .../{update-error-codes.js => build-artifacts.js} | 9 ++++++++- .../release/commands/update-package-versions.js | 14 ++++++++------ scripts/release/release.js | 7 ++----- 3 files changed, 18 insertions(+), 12 deletions(-) rename scripts/release/commands/{update-error-codes.js => build-artifacts.js} (67%) diff --git a/scripts/release/commands/update-error-codes.js b/scripts/release/commands/build-artifacts.js similarity index 67% rename from scripts/release/commands/update-error-codes.js rename to scripts/release/commands/build-artifacts.js index 82712307c6eb8..fc2038f600f86 100644 --- a/scripts/release/commands/update-error-codes.js +++ b/scripts/release/commands/build-artifacts.js @@ -17,9 +17,16 @@ const run = async ({cwd, dry, version}) => { cwd, }); } + + if (modifiedFiles.includes('scripts/rollup/results.json')) { + await exec('git add scripts/rollup/results.json', {cwd}); + await exec(`git commit -m "Update bundle sizes for ${version} release"`, { + cwd, + }); + } } }; module.exports = async params => { - return logPromise(run(params), 'Updating error codes'); + return logPromise(run(params), 'Building artifacts'); }; diff --git a/scripts/release/commands/update-package-versions.js b/scripts/release/commands/update-package-versions.js index 44700c9f61db6..ca6e283c0f4ef 100644 --- a/scripts/release/commands/update-package-versions.js +++ b/scripts/release/commands/update-package-versions.js @@ -20,11 +20,10 @@ const update = async ({cwd, dry, version}) => { // Update ReactVersion source file const reactVersionPath = join(cwd, 'src/ReactVersion.js'); - const reactVersion = readFileSync(reactVersionPath, 'utf8') - .replace( - /module\.exports = '[^']+';/, - `module.exports = '${version}';` - ); + const reactVersion = readFileSync(reactVersionPath, 'utf8').replace( + /module\.exports = '[^']+';/, + `module.exports = '${version}';` + ); writeFileSync(reactVersionPath, reactVersion); // Update renderer versions and peer dependencies @@ -43,7 +42,10 @@ const update = async ({cwd, dry, version}) => { await exec('yarn version-check', {cwd}); if (!dry) { - await exec(`git commit -am "Updating package versions for release ${version}"`, {cwd}); + await exec( + `git commit -am "Updating package versions for release ${version}"`, + {cwd} + ); } } catch (error) { console.log( diff --git a/scripts/release/release.js b/scripts/release/release.js index aa26a44af4433..498f442242207 100755 --- a/scripts/release/release.js +++ b/scripts/release/release.js @@ -5,6 +5,7 @@ const chalk = require('chalk'); const logUpdate = require('log-update'); +const buildArtifacts = require('./commands/build-artifacts'); const checkCircleCiStatus = require('./commands/check-circle-ci-status'); const checkEnvironmentVariables = require('./commands/check-environment-variables'); const checkNpmPermissions = require('./commands/check-npm-permissions'); @@ -15,7 +16,6 @@ const parseParameters = require('./commands/parse-parameters'); const printChangelogInstructions = require('./commands/print-changelog-instructions'); const runAutomatedTests = require('./commands/run-automated-tests'); const updateGit = require('./commands/update-git'); -const updateErrorCodes = require('./commands/update-error-codes'); const updatePackageVersions = require('./commands/update-package-versions'); const updateYarnDependencies = require('./commands/update-yarn-dependencies'); @@ -24,7 +24,6 @@ const run = async () => { const params = parseParameters(); try { -/* TODO await checkEnvironmentVariables(params); await checkUncommittedChanges(params); await checkNpmPermissions(params); @@ -34,10 +33,8 @@ const run = async () => { await checkPackageDependencies(params); await updateYarnDependencies(params); await runAutomatedTests(params); - await updateErrorCodes(params); await updatePackageVersions(params); -*/ - // TODO 'yarn build' actual release artifacts (is this necessary after the error codes build step? or could we re-arrange and speed up?) + await buildArtifacts(params); await printChangelogInstructions(); // TODO Print testing instructions // TODO Print publish instructions (and create separate publish.js script) From 14ebc68d15c3e3b04f9aa4b43598a1cd5ad5ddb2 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 12:45:04 -0700 Subject: [PATCH 03/16] Reorganized build files; added stub publish script --- scripts/release/build.js | 48 +++++++++++++++++ .../{commands => build}/build-artifacts.js | 0 .../check-circle-ci-status.js | 0 .../check-environment-variables.js | 0 .../check-npm-permissions.js | 0 .../check-package-dependencies.js | 0 .../check-uncommitted-changes.js | 0 .../install-yarn-dependencies.js | 0 .../parse-build-parameters.js} | 18 ++++--- .../build/print-prerelease-instructions.js | 39 ++++++++++++++ .../run-automated-tests.js | 0 .../release/{commands => build}/update-git.js | 0 .../update-package-versions.js | 0 .../update-yarn-dependencies.js | 0 .../commands/print-changelog-instructions.js | 17 ------ scripts/release/logo.js | 19 ------- scripts/release/package.json | 1 + scripts/release/publish.js | 29 ++++++++++ scripts/release/release.js | 53 ------------------- scripts/release/yarn.lock | 4 ++ 20 files changed, 131 insertions(+), 97 deletions(-) create mode 100755 scripts/release/build.js rename scripts/release/{commands => build}/build-artifacts.js (100%) rename scripts/release/{commands => build}/check-circle-ci-status.js (100%) rename scripts/release/{commands => build}/check-environment-variables.js (100%) rename scripts/release/{commands => build}/check-npm-permissions.js (100%) rename scripts/release/{commands => build}/check-package-dependencies.js (100%) rename scripts/release/{commands => build}/check-uncommitted-changes.js (100%) rename scripts/release/{commands => build}/install-yarn-dependencies.js (100%) rename scripts/release/{commands/parse-parameters.js => build/parse-build-parameters.js} (68%) create mode 100644 scripts/release/build/print-prerelease-instructions.js rename scripts/release/{commands => build}/run-automated-tests.js (100%) rename scripts/release/{commands => build}/update-git.js (100%) rename scripts/release/{commands => build}/update-package-versions.js (100%) rename scripts/release/{commands => build}/update-yarn-dependencies.js (100%) delete mode 100644 scripts/release/commands/print-changelog-instructions.js delete mode 100644 scripts/release/logo.js create mode 100755 scripts/release/publish.js delete mode 100755 scripts/release/release.js diff --git a/scripts/release/build.js b/scripts/release/build.js new file mode 100755 index 0000000000000..d8e91264bb274 --- /dev/null +++ b/scripts/release/build.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const logUpdate = require('log-update'); + +const buildArtifacts = require('./build/build-artifacts'); +const checkCircleCiStatus = require('./build/check-circle-ci-status'); +const checkEnvironmentVariables = require('./build/check-environment-variables'); +const checkNpmPermissions = require('./build/check-npm-permissions'); +const checkPackageDependencies = require('./build/check-package-dependencies'); +const checkUncommittedChanges = require('./build/check-uncommitted-changes'); +const installYarnDependencies = require('./build/install-yarn-dependencies'); +const parseBuildParameters = require('./build/parse-build-parameters'); +const printPrereleaseInstructions = require('./build/print-prerelease-instructions'); +const runAutomatedTests = require('./build/run-automated-tests'); +const updateGit = require('./build/update-git'); +const updatePackageVersions = require('./build/update-package-versions'); +const updateYarnDependencies = require('./build/update-yarn-dependencies'); + +// Follows the steps outlined in github.com/facebook/react/issues/10620 +const run = async () => { + const params = parseBuildParameters(); + + try { + await checkEnvironmentVariables(params); + await checkUncommittedChanges(params); + await checkNpmPermissions(params); + await updateGit(params); + await checkCircleCiStatus(params); + await installYarnDependencies(params); + await checkPackageDependencies(params); + await updateYarnDependencies(params); + await runAutomatedTests(params); + await updatePackageVersions(params); + await buildArtifacts(params); + await printPrereleaseInstructions(); + } catch (error) { + logUpdate.clear(); + + console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); + + process.exit(1); + } +}; + +run(); diff --git a/scripts/release/commands/build-artifacts.js b/scripts/release/build/build-artifacts.js similarity index 100% rename from scripts/release/commands/build-artifacts.js rename to scripts/release/build/build-artifacts.js diff --git a/scripts/release/commands/check-circle-ci-status.js b/scripts/release/build/check-circle-ci-status.js similarity index 100% rename from scripts/release/commands/check-circle-ci-status.js rename to scripts/release/build/check-circle-ci-status.js diff --git a/scripts/release/commands/check-environment-variables.js b/scripts/release/build/check-environment-variables.js similarity index 100% rename from scripts/release/commands/check-environment-variables.js rename to scripts/release/build/check-environment-variables.js diff --git a/scripts/release/commands/check-npm-permissions.js b/scripts/release/build/check-npm-permissions.js similarity index 100% rename from scripts/release/commands/check-npm-permissions.js rename to scripts/release/build/check-npm-permissions.js diff --git a/scripts/release/commands/check-package-dependencies.js b/scripts/release/build/check-package-dependencies.js similarity index 100% rename from scripts/release/commands/check-package-dependencies.js rename to scripts/release/build/check-package-dependencies.js diff --git a/scripts/release/commands/check-uncommitted-changes.js b/scripts/release/build/check-uncommitted-changes.js similarity index 100% rename from scripts/release/commands/check-uncommitted-changes.js rename to scripts/release/build/check-uncommitted-changes.js diff --git a/scripts/release/commands/install-yarn-dependencies.js b/scripts/release/build/install-yarn-dependencies.js similarity index 100% rename from scripts/release/commands/install-yarn-dependencies.js rename to scripts/release/build/install-yarn-dependencies.js diff --git a/scripts/release/commands/parse-parameters.js b/scripts/release/build/parse-build-parameters.js similarity index 68% rename from scripts/release/commands/parse-parameters.js rename to scripts/release/build/parse-build-parameters.js index 46c282c10dd0b..baa42bbdb94c7 100644 --- a/scripts/release/commands/parse-parameters.js +++ b/scripts/release/build/parse-build-parameters.js @@ -2,9 +2,10 @@ 'use strict'; +const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); -const logo = require('../logo'); +const figlet = require('figlet'); module.exports = () => { const paramDefinitions = [ @@ -33,12 +34,13 @@ module.exports = () => { if (!params.version) { const usage = commandLineUsage([ { - content: logo, + content: chalk + .hex('#61dafb') + .bold(figlet.textSync('react', {font: 'Graffiti'})), raw: true, }, { - header: 'React Release Manager', - content: 'Automated release script.', + content: 'Automated pre-release build script.', }, { header: 'Options', @@ -49,15 +51,15 @@ module.exports = () => { content: [ { desc: '1. A concise example.', - example: '$ ./release [bold]{-v} [underline]{16.0.0}', + example: '$ ./build.js [bold]{-v} [underline]{16.0.0}', }, { - desc: '2. Dry run publishing a release candidate.', - example: '$ ./release [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}', + desc: '2. Dry run build a release candidate (no git commits).', + example: '$ ./build.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}', }, { desc: '3. Release from another checkout.', - example: '$ ./release [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo', + example: '$ ./build.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo', }, ], }, diff --git a/scripts/release/build/print-prerelease-instructions.js b/scripts/release/build/print-prerelease-instructions.js new file mode 100644 index 0000000000000..7c4befc1a5b31 --- /dev/null +++ b/scripts/release/build/print-prerelease-instructions.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); + +const CHANGELOG_PATH = + 'https://github.com/facebook/react/edit/master/CHANGELOG.md'; + +module.exports = () => { + console.log( + chalk` + {green.bold Release build successful!} + + Next there are a couple of manual steps: + + {bold.underline Step 1: Update the CHANGELOG} + + Here are a few things to keep in mind: + • The changes should be easy to understand. (Friendly one-liners are better than PR titles.) + • Make sure all contributors are credited. + • Verify that the markup is valid by previewing it in the editor: {blue.bold ${CHANGELOG_PATH}} + + {bold.underline Step 2: Smoke test the packages} + + 1. Open {yellow.bold fixtures/packaging/babel-standalone/dev.html} in the browser. + 2. It should say {italic "Hello world!"} + 3. Next go to {yellow.bold fixtures/packaging} and run {bold node build-all.js} + 4. Install the "serve" module ({bold npm install -g serve}) + 5. Go to the repo root and {bold run serve -s .} + 6. Open {blue.bold http://localhost:5000/fixtures/packaging} + 7. Verify every iframe shows {italic "Hello world!"} + + After completing the above steps, resume the release process by running {yellow.bold ./publish.js} + ` + .trim() + .replace(/\n +/g, '\n') + ); +}; diff --git a/scripts/release/commands/run-automated-tests.js b/scripts/release/build/run-automated-tests.js similarity index 100% rename from scripts/release/commands/run-automated-tests.js rename to scripts/release/build/run-automated-tests.js diff --git a/scripts/release/commands/update-git.js b/scripts/release/build/update-git.js similarity index 100% rename from scripts/release/commands/update-git.js rename to scripts/release/build/update-git.js diff --git a/scripts/release/commands/update-package-versions.js b/scripts/release/build/update-package-versions.js similarity index 100% rename from scripts/release/commands/update-package-versions.js rename to scripts/release/build/update-package-versions.js diff --git a/scripts/release/commands/update-yarn-dependencies.js b/scripts/release/build/update-yarn-dependencies.js similarity index 100% rename from scripts/release/commands/update-yarn-dependencies.js rename to scripts/release/build/update-yarn-dependencies.js diff --git a/scripts/release/commands/print-changelog-instructions.js b/scripts/release/commands/print-changelog-instructions.js deleted file mode 100644 index baacb5b3afc46..0000000000000 --- a/scripts/release/commands/print-changelog-instructions.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); - -module.exports = () => { - console.log( - `Now it's time to addd release notes to the ${chalk.yellow.bold('CHANGELOG.md')}!\n\n` + - 'Here are a few things to keep in mind:\n' + - '• The changes should be easy to understand. ' + - '(Friendly one-liners are better than PR titles.)\n' + - '• Make sure all contributors are credited.\n' + - '• Verify that the markup is valid by previewing it in the editor: ' + - chalk.cyan('https://github.com/facebook/react/edit/master/CHANGELOG.md') - ); -}; diff --git a/scripts/release/logo.js b/scripts/release/logo.js deleted file mode 100644 index 63c576e3506a4..0000000000000 --- a/scripts/release/logo.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const chalk = require('chalk'); - -const LOGO = chalk - .hex('#61dafb') - .bold( - ` - __ -_______ ____ _____ _____/ |_ -\\_ __ \\_/ __ \\\\__ \\ _/ ___\\ __\\ - | | \\/\\ ___/ / __ \\\\ \\___| | - |__| \\___ >____ /\\___ >__| - \\/ \\/ \\/ -` - ) - .replace(/(^\n+|\n+$)/g, ''); - -module.exports = LOGO; diff --git a/scripts/release/package.json b/scripts/release/package.json index 4856585051b37..4bfd521b61a1d 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -14,6 +14,7 @@ "cli-spinners": "^1.1.0", "command-line-args": "^4.0.7", "command-line-usage": "^4.0.1", + "figlet": "^1.2.0", "fs-extra": "^4.0.2", "log-update": "^2.1.0", "request-promise-json": "^1.0.4" diff --git a/scripts/release/publish.js b/scripts/release/publish.js new file mode 100755 index 0000000000000..ab91ce4ffa7d9 --- /dev/null +++ b/scripts/release/publish.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const logUpdate = require('log-update'); + +// Follows the steps outlined in github.com/facebook/react/issues/10620 +const run = async () => { + // TODO Parse params (if we need them?) + + try { + // TODO Verify update to CHANGELOG.md and commit + // TODO git push / git push --tags + // TODO build/packages ~ npm publish + // TODO npm info dist-tags + // TODO Update bower + // TODO Print post-publish website update instructions + // TODO Print post-publish testing instructions + } catch (error) { + logUpdate.clear(); + + console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); + + process.exit(1); + } +}; + +run(); diff --git a/scripts/release/release.js b/scripts/release/release.js deleted file mode 100755 index 498f442242207..0000000000000 --- a/scripts/release/release.js +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const logUpdate = require('log-update'); - -const buildArtifacts = require('./commands/build-artifacts'); -const checkCircleCiStatus = require('./commands/check-circle-ci-status'); -const checkEnvironmentVariables = require('./commands/check-environment-variables'); -const checkNpmPermissions = require('./commands/check-npm-permissions'); -const checkPackageDependencies = require('./commands/check-package-dependencies'); -const checkUncommittedChanges = require('./commands/check-uncommitted-changes'); -const installYarnDependencies = require('./commands/install-yarn-dependencies'); -const parseParameters = require('./commands/parse-parameters'); -const printChangelogInstructions = require('./commands/print-changelog-instructions'); -const runAutomatedTests = require('./commands/run-automated-tests'); -const updateGit = require('./commands/update-git'); -const updatePackageVersions = require('./commands/update-package-versions'); -const updateYarnDependencies = require('./commands/update-yarn-dependencies'); - -// Follows the steps outlined in github.com/facebook/react/issues/10620 -const run = async () => { - const params = parseParameters(); - - try { - await checkEnvironmentVariables(params); - await checkUncommittedChanges(params); - await checkNpmPermissions(params); - await updateGit(params); - await checkCircleCiStatus(params); - await installYarnDependencies(params); - await checkPackageDependencies(params); - await updateYarnDependencies(params); - await runAutomatedTests(params); - await updatePackageVersions(params); - await buildArtifacts(params); - await printChangelogInstructions(); - // TODO Print testing instructions - // TODO Print publish instructions (and create separate publish.js script) - // TODO Update website (instructions) - // TODO Update bower - // TODO Test via create-react-app - } catch (error) { - logUpdate.clear(); - - console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); - - process.exit(1); - } -}; - -run(); diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index 96c928d17aeaa..37330d2cd9b71 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -201,6 +201,10 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +figlet@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410" + find-replace@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" From cdae992af6737b637e5623a7dca8f86fb5ca019a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 15:19:54 -0700 Subject: [PATCH 04/16] First pass at publis script. Also collect and print dry-run commits/publish commands. --- scripts/release/build.js | 17 ++++-- scripts/release/build/build-artifacts.js | 28 +++++----- .../release/build/check-circle-ci-status.js | 26 +++++---- .../build/check-environment-variables.js | 19 ++++--- .../release/build/check-npm-permissions.js | 18 +++--- .../build/check-package-dependencies.js | 20 ++++--- .../build/check-uncommitted-changes.js | 10 ++-- .../release/build/parse-build-parameters.js | 22 +------- ...uctions.js => print-post-build-summary.js} | 19 ++++--- scripts/release/build/run-automated-tests.js | 10 ++-- scripts/release/build/update-git.js | 5 +- .../release/build/update-package-versions.js | 21 +++---- .../release/build/update-yarn-dependencies.js | 24 ++++---- scripts/release/config.js | 23 ++++++++ scripts/release/package.json | 3 +- scripts/release/publish.js | 28 +++++++--- scripts/release/publish/check-build-status.js | 24 ++++++++ scripts/release/publish/commit-changelog.js | 24 ++++++++ .../release/publish/parse-publish-params.js | 55 +++++++++++++++++++ .../publish/print-post-publish-summary.js | 41 ++++++++++++++ scripts/release/publish/publish-to-npm.js | 52 ++++++++++++++++++ scripts/release/publish/push-git-remote.js | 14 +++++ scripts/release/utils.js | 23 ++++++++ scripts/release/yarn.lock | 4 ++ 24 files changed, 406 insertions(+), 124 deletions(-) rename scripts/release/build/{print-prerelease-instructions.js => print-post-build-summary.js} (76%) create mode 100644 scripts/release/publish/check-build-status.js create mode 100644 scripts/release/publish/commit-changelog.js create mode 100644 scripts/release/publish/parse-publish-params.js create mode 100644 scripts/release/publish/print-post-publish-summary.js create mode 100644 scripts/release/publish/publish-to-npm.js create mode 100644 scripts/release/publish/push-git-remote.js diff --git a/scripts/release/build.js b/scripts/release/build.js index d8e91264bb274..ed6c14af055b0 100755 --- a/scripts/release/build.js +++ b/scripts/release/build.js @@ -13,18 +13,20 @@ const checkPackageDependencies = require('./build/check-package-dependencies'); const checkUncommittedChanges = require('./build/check-uncommitted-changes'); const installYarnDependencies = require('./build/install-yarn-dependencies'); const parseBuildParameters = require('./build/parse-build-parameters'); -const printPrereleaseInstructions = require('./build/print-prerelease-instructions'); +const printPostBuildSummary = require('./build/print-post-build-summary'); const runAutomatedTests = require('./build/run-automated-tests'); const updateGit = require('./build/update-git'); const updatePackageVersions = require('./build/update-package-versions'); const updateYarnDependencies = require('./build/update-yarn-dependencies'); +const validateVersion = require('./build/validate-version'); // Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { - const params = parseBuildParameters(); - try { + const params = parseBuildParameters(); + await checkEnvironmentVariables(params); + await validateVersion(params); await checkUncommittedChanges(params); await checkNpmPermissions(params); await updateGit(params); @@ -35,11 +37,16 @@ const run = async () => { await runAutomatedTests(params); await updatePackageVersions(params); await buildArtifacts(params); - await printPrereleaseInstructions(); + await printPostBuildSummary(params); } catch (error) { logUpdate.clear(); - console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); + const message = error.message.trim().replace(/\n +/g, '\n'); + const stack = error.stack.replace(error.message, ''); + + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(stack)}` + ); process.exit(1); } diff --git a/scripts/release/build/build-artifacts.js b/scripts/release/build/build-artifacts.js index fc2038f600f86..9af06631084e7 100644 --- a/scripts/release/build/build-artifacts.js +++ b/scripts/release/build/build-artifacts.js @@ -3,27 +3,27 @@ 'use strict'; const {exec} = require('child-process-promise'); -const {execRead, logPromise} = require('../utils'); +const {execRead, execUnlessDry, logPromise} = require('../utils'); const run = async ({cwd, dry, version}) => { await exec('yarn build -- --extract-errors', {cwd}); const modifiedFiles = await execRead('git ls-files -m', {cwd}); - if (!dry) { - if (modifiedFiles.includes('scripts/error-codes/codes.json')) { - await exec('git add scripts/error-codes/codes.json', {cwd}); - await exec(`git commit -m "Update error codes for ${version} release"`, { - cwd, - }); - } + if (modifiedFiles.includes('scripts/error-codes/codes.json')) { + await execUnlessDry('git add scripts/error-codes/codes.json', {cwd, dry}); + await execUnlessDry( + `git commit -m "Update error codes for ${version} release"`, + {cwd, dry} + ); + } - if (modifiedFiles.includes('scripts/rollup/results.json')) { - await exec('git add scripts/rollup/results.json', {cwd}); - await exec(`git commit -m "Update bundle sizes for ${version} release"`, { - cwd, - }); - } + if (modifiedFiles.includes('scripts/rollup/results.json')) { + await execUnlessDry('git add scripts/rollup/results.json', {cwd, dry}); + await execUnlessDry( + `git commit -m "Update bundle sizes for ${version} release"`, + {cwd, dry} + ); } }; diff --git a/scripts/release/build/check-circle-ci-status.js b/scripts/release/build/check-circle-ci-status.js index 3b029705c5bb8..bb973c70dc07d 100644 --- a/scripts/release/build/check-circle-ci-status.js +++ b/scripts/release/build/check-circle-ci-status.js @@ -20,20 +20,24 @@ const check = async ({cwd}) => { const gitRevision = await execRead('git rev-parse HEAD', {cwd}); if (gitRevision !== ciRevision) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('CircleCI is stale')}\n\n` + - `The latest Git revision is ${chalk.yellow(gitRevision)}.\n` + - `The most recent CircleCI revision is ${chalk.yellow(ciRevision)}.\n` + - 'Please wait for CircleCI to catch up.' + throw Error( + chalk` + CircleCI is stale + + {white The latest Git revision is {yellow.bold ${gitRevision}}} + {white The most recent CircleCI revision is {yellow.bold ${ciRevision}}} + {white Please wait for CircleCI to catch up.} + ` ); - process.exit(1); } else if (outcome !== 'success') { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('CircleCI failed')}\n\n` + - `The most recent CircleCI build has a status of ${chalk.red(outcome || status)}.\n` + - 'Please retry this build in CircleCI if you believe this is an error.' + throw Error( + chalk` + CircleCI failed + + {white The most recent CircleCI build has a status of {red.bold ${outcome || status}}} + {white Please retry this build in CircleCI if you believe this is an error.} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/check-environment-variables.js b/scripts/release/build/check-environment-variables.js index 78212f36021b0..4a1a4ca26ae74 100644 --- a/scripts/release/build/check-environment-variables.js +++ b/scripts/release/build/check-environment-variables.js @@ -6,15 +6,16 @@ const chalk = require('chalk'); module.exports = () => { if (!process.env.CIRCLE_CI_API_TOKEN) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Missing CircleCI API token')}\n\n` + - 'The CircleCI API is used to check the status of the latest commit.\n' + - `This API requires a token which must be exposed via ${chalk.yellow('CIRCLE_CI_API_TOKEN')}.\n` + - 'For instructions on creating this token check out the link below:\n\n' + - chalk.gray( - 'https://circleci.com/docs/api/v1-reference/#getting-started' - ) + throw Error( + chalk` + {red Missing CircleCI API token} + + {white The CircleCI API is used to check the status of the latest commit.} + {white This API requires a token which must be exposed via {yellow.bold CIRCLE_CI_API_TOKEN}} + {white For instructions on creating this token check out the link below:} + + {blue.bold https://circleci.com/docs/api/v1-reference/#getting-started} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/check-npm-permissions.js b/scripts/release/build/check-npm-permissions.js index 6a4ae943ca1ab..c285a3e86f35e 100644 --- a/scripts/release/build/check-npm-permissions.js +++ b/scripts/release/build/check-npm-permissions.js @@ -23,17 +23,19 @@ module.exports = async () => { await logPromise( Promise.all(projects.map(checkProject)), - `Checking ${chalk.yellow(currentUser)}'s NPM permissions` + `Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions` ); if (failedProjects.length) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Insufficient NPM permissions')}\n\n` + - `NPM user ${chalk.yellow(currentUser)} is not an owner for: ` + - chalk.red(failedProjects.join(', ')) + - '\n' + - 'Please contact a React team member to be added to the above project(s).' + throw Error( + chalk` + Insufficient NPM permissions + + {white NPM user {yellow.bold ${currentUser}} is not an owner for:} + {red ${failedProjects.join(', ')}} + + {white Please contact a React team member to be added to the above project(s).} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/check-package-dependencies.js b/scripts/release/build/check-package-dependencies.js index 4e361b048ac5d..370343944d36e 100644 --- a/scripts/release/build/check-package-dependencies.js +++ b/scripts/release/build/check-package-dependencies.js @@ -29,8 +29,8 @@ const check = async ({cwd}) => { if (rootVersion !== projectVersion && projectVersion !== undefined) { invalidDependencies.push( - `${chalk.yellow(module)} is ${chalk.red(rootVersion)} in root but ` + - `${chalk.red(projectVersion)} in ${chalk.yellow(projectPackage.name)}` + `${module} is ${chalk.red.bold(rootVersion)} in root but ` + + `${chalk.red.bold(projectVersion)} in ${projectPackage.name}` ); } }); @@ -39,15 +39,19 @@ const check = async ({cwd}) => { await Promise.all(dependencies.map(checkModule)); if (invalidDependencies.length) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Dependency mismatch')}\n\n` + - `The following dependencies do not match between the root package and NPM dependencies:\n` + - invalidDependencies.join('\n') + throw Error( + chalk` + Dependency mismatch + + {white The following dependencies do not match between the root package and NPM dependencies:} + ${invalidDependencies + .map(dependency => chalk.white(dependency)) + .join('\n')} + ` ); - process.exit(1); } }; module.exports = async ({cwd}) => { - logPromise(check({cwd}), 'Checking runtime dependencies'); + return logPromise(check({cwd}), 'Checking runtime dependencies'); }; diff --git a/scripts/release/build/check-uncommitted-changes.js b/scripts/release/build/check-uncommitted-changes.js index d580447e4dc87..4d18e889b5f9e 100644 --- a/scripts/release/build/check-uncommitted-changes.js +++ b/scripts/release/build/check-uncommitted-changes.js @@ -9,10 +9,12 @@ module.exports = async ({cwd}) => { const status = await execRead('git diff HEAD', {cwd}); if (status) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Uncommitted local changes')}\n\n` + - 'Please revert or commit all local changes before making a release.' + throw Error( + chalk` + Uncommitted local changes + + {white Please revert or commit all local changes before making a release.} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/parse-build-parameters.js b/scripts/release/build/parse-build-parameters.js index baa42bbdb94c7..c07c048e7172e 100644 --- a/scripts/release/build/parse-build-parameters.js +++ b/scripts/release/build/parse-build-parameters.js @@ -6,29 +6,9 @@ const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); const figlet = require('figlet'); +const {paramDefinitions} = require('../config'); module.exports = () => { - const paramDefinitions = [ - { - name: 'dry', - type: Boolean, - defaultValue: false, - description: 'Build artifacts but do not commit or publish', - }, - { - name: 'path', - type: String, - alias: 'p', - description: 'Location of React repository to release; defaults to [bold]{cwd}', - }, - { - name: 'version', - type: String, - alias: 'v', - description: 'Semantic version number', - }, - ]; - const params = commandLineArgs(paramDefinitions); if (!params.version) { diff --git a/scripts/release/build/print-prerelease-instructions.js b/scripts/release/build/print-post-build-summary.js similarity index 76% rename from scripts/release/build/print-prerelease-instructions.js rename to scripts/release/build/print-post-build-summary.js index 7c4befc1a5b31..ca2b088f9dac4 100644 --- a/scripts/release/build/print-prerelease-instructions.js +++ b/scripts/release/build/print-post-build-summary.js @@ -3,15 +3,21 @@ 'use strict'; const chalk = require('chalk'); +const {getUnexecutedCommands} = require('../utils'); const CHANGELOG_PATH = 'https://github.com/facebook/react/edit/master/CHANGELOG.md'; -module.exports = () => { +module.exports = params => { + const command = + `./publish.js -v ${params.version}` + + (params.path ? ` -p ${params.path}` : '') + + (params.dry ? ' --dry' : ''); + console.log( chalk` - {green.bold Release build successful!} - + {green.bold Build successful!} + ${getUnexecutedCommands()} Next there are a couple of manual steps: {bold.underline Step 1: Update the CHANGELOG} @@ -31,9 +37,8 @@ module.exports = () => { 6. Open {blue.bold http://localhost:5000/fixtures/packaging} 7. Verify every iframe shows {italic "Hello world!"} - After completing the above steps, resume the release process by running {yellow.bold ./publish.js} - ` - .trim() - .replace(/\n +/g, '\n') + After completing the above steps, resume the release process by running: + {yellow.bold ${command}} + `.replace(/\n +/g, '\n') ); }; diff --git a/scripts/release/build/run-automated-tests.js b/scripts/release/build/run-automated-tests.js index ba3af16897a93..ca4c19c78c16a 100644 --- a/scripts/release/build/run-automated-tests.js +++ b/scripts/release/build/run-automated-tests.js @@ -10,11 +10,13 @@ const runYarnTask = async (cwd, task, errorMessage) => { try { await exec(`yarn ${task}`, {cwd}); } catch (error) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red(errorMessage)}\n\n` + - error.stdout + throw Error( + chalk` + ${errorMessage} + + {white ${error.stdout}} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/update-git.js b/scripts/release/build/update-git.js index 1bd873beb8306..58255a48ca084 100644 --- a/scripts/release/build/update-git.js +++ b/scripts/release/build/update-git.js @@ -13,5 +13,8 @@ const update = async ({cwd}) => { }; module.exports = async ({cwd}) => { - return logPromise(update({cwd}), `Updating checkout ${chalk.yellow(cwd)}`); + return logPromise( + update({cwd}), + `Updating checkout ${chalk.yellow.bold(cwd)}` + ); }; diff --git a/scripts/release/build/update-package-versions.js b/scripts/release/build/update-package-versions.js index ca6e283c0f4ef..b061b4d8f8dd4 100644 --- a/scripts/release/build/update-package-versions.js +++ b/scripts/release/build/update-package-versions.js @@ -8,7 +8,7 @@ const {readFileSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); const {join} = require('path'); const {projects} = require('../config'); -const {logPromise} = require('../utils'); +const {execUnlessDry, logPromise} = require('../utils'); const update = async ({cwd, dry, version}) => { try { @@ -41,17 +41,18 @@ const update = async ({cwd, dry, version}) => { // Version sanity check await exec('yarn version-check', {cwd}); - if (!dry) { - await exec( - `git commit -am "Updating package versions for release ${version}"`, - {cwd} - ); - } + await execUnlessDry( + `git commit -am "Updating package versions for release ${version}"`, + {cwd, dry} + ); } catch (error) { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Failed while updating package versions')}` + throw Error( + chalk` + Failed while updating package versions + + {white ${error.message}} + ` ); - process.exit(1); } }; diff --git a/scripts/release/build/update-yarn-dependencies.js b/scripts/release/build/update-yarn-dependencies.js index 63243d9bd2327..b8defaf5eaa15 100644 --- a/scripts/release/build/update-yarn-dependencies.js +++ b/scripts/release/build/update-yarn-dependencies.js @@ -5,7 +5,7 @@ const chalk = require('chalk'); const {exec} = require('child-process-promise'); const {dependencies} = require('../config'); -const {execRead, logPromise} = require('../utils'); +const {execRead, execUnlessDry, logPromise} = require('../utils'); const update = async ({cwd, dry, version}) => { await exec(`yarn upgrade ${dependencies.join(' ')}`, {cwd}); @@ -16,20 +16,20 @@ const update = async ({cwd, dry, version}) => { // If anything else has changed, it's an error. if (modifiedFiles) { if (modifiedFiles !== 'yarn.lock') { - console.log( - `${chalk.bgRed.white(' ERROR ')} ${chalk.red('Unexpected modifications')}\n\n` + - `The following files have been modified unexpectedly:\n` + - chalk.gray(modifiedFiles) - ); - process.exit(1); - } + throw Error( + chalk` + Unexpected modifications - if (!dry) { - await exec( - `git commit -am "Updating yarn.lock file for ${version} release"`, - {cwd} + {white The following files have been modified unexpectedly:} + {gray ${modifiedFiles}} + ` ); } + + await execUnlessDry( + `git commit -am "Updating yarn.lock file for ${version} release"`, + {cwd, dry} + ); } }; diff --git a/scripts/release/config.js b/scripts/release/config.js index 57acfcd2e4f95..6a427a7be140a 100644 --- a/scripts/release/config.js +++ b/scripts/release/config.js @@ -10,7 +10,30 @@ const projects = [ 'react-test-renderer', ]; +const paramDefinitions = [ + { + name: 'dry', + type: Boolean, + description: 'Build artifacts but do not commit or publish', + defaultValue: false, + }, + { + name: 'path', + type: String, + alias: 'p', + description: 'Location of React repository to release; defaults to [bold]{cwd}', + defaultValue: '.', + }, + { + name: 'version', + type: String, + alias: 'v', + description: 'Semantic version number', + }, +]; + module.exports = { dependencies, + paramDefinitions, projects, }; diff --git a/scripts/release/package.json b/scripts/release/package.json index 4bfd521b61a1d..7200b6df6b68e 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -17,6 +17,7 @@ "figlet": "^1.2.0", "fs-extra": "^4.0.2", "log-update": "^2.1.0", - "request-promise-json": "^1.0.4" + "request-promise-json": "^1.0.4", + "semver": "^5.4.1" } } diff --git a/scripts/release/publish.js b/scripts/release/publish.js index ab91ce4ffa7d9..f9de677977f76 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -5,22 +5,32 @@ const chalk = require('chalk'); const logUpdate = require('log-update'); +const checkBuildStatus = require('./publish/check-build-status'); +const commitChangelog = require('./publish/commit-changelog'); +const parsePublishParams = require('./publish/parse-publish-params'); +const printPostPublishSummary = require('./publish/print-post-publish-summary'); +const pushGitRemote = require('./publish/push-git-remote'); +const publishToNpm = require('./publish/publish-to-npm'); + // Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { - // TODO Parse params (if we need them?) + const params = parsePublishParams(); try { - // TODO Verify update to CHANGELOG.md and commit - // TODO git push / git push --tags - // TODO build/packages ~ npm publish - // TODO npm info dist-tags - // TODO Update bower - // TODO Print post-publish website update instructions - // TODO Print post-publish testing instructions + await checkBuildStatus(params); + await commitChangelog(params); + await pushGitRemote(params); + await publishToNpm(params); + await printPostPublishSummary(params); } catch (error) { logUpdate.clear(); - console.log(`${chalk.bgRed.white(' ERROR ')} ${chalk.red(error.message)}`); + const message = error.message.trim().replace(/\n +/g, '\n'); + const stack = error.stack.replace(error.message, ''); + + console.log( + `${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(stack)}` + ); process.exit(1); } diff --git a/scripts/release/publish/check-build-status.js b/scripts/release/publish/check-build-status.js new file mode 100644 index 0000000000000..a416cfc8933ff --- /dev/null +++ b/scripts/release/publish/check-build-status.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {existsSync} = require('fs'); +const {readJson} = require('fs-extra'); +const {join} = require('path'); + +module.exports = async ({cwd, version}) => { + const packagePath = join(cwd, 'build', 'packages', 'react', 'package.json'); + + if (!existsSync(packagePath)) { + throw Error('No build found'); + } + + const packageJson = await readJson(packagePath); + + if (packageJson.version !== version) { + throw Error( + chalk`Expected version {bold.white ${version}} but found {bold.white ${packageJson.version}}` + ); + } +}; diff --git a/scripts/release/publish/commit-changelog.js b/scripts/release/publish/commit-changelog.js new file mode 100644 index 0000000000000..448dda5a30903 --- /dev/null +++ b/scripts/release/publish/commit-changelog.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child-process-promise'); +const {execRead, logPromise} = require('../utils'); + +const update = async ({cwd, dry, version}) => { + const modifiedFiles = await execRead('git ls-files -m', {cwd}); + + if (!dry && modifiedFiles.includes('CHANGELOG.md')) { + await exec('git add CHANGELOG.md', {cwd}); + await exec( + `git commit -am "Updating CHANGELOG.md for ${version} release"`, + { + cwd, + } + ); + } +}; + +module.exports = async params => { + return logPromise(update(params), 'Committing CHANGELOG updates'); +}; diff --git a/scripts/release/publish/parse-publish-params.js b/scripts/release/publish/parse-publish-params.js new file mode 100644 index 0000000000000..dd4a649b99ed0 --- /dev/null +++ b/scripts/release/publish/parse-publish-params.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const commandLineArgs = require('command-line-args'); +const commandLineUsage = require('command-line-usage'); +const figlet = require('figlet'); +const {paramDefinitions} = require('../config'); + +module.exports = () => { + const params = commandLineArgs(paramDefinitions); + + if (!params.version) { + const usage = commandLineUsage([ + { + content: chalk + .hex('#61dafb') + .bold(figlet.textSync('react', {font: 'Graffiti'})), + raw: true, + }, + { + content: 'Automated release publishing script.', + }, + { + header: 'Options', + optionList: paramDefinitions, + }, + { + header: 'Examples', + content: [ + { + desc: '1. A concise example.', + example: '$ ./publish.js [bold]{-v} [underline]{16.0.0}', + }, + { + desc: '2. Dry run publish a release candidate.', + example: '$ ./publish.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}', + }, + { + desc: '3. Release from another checkout.', + example: '$ ./publish.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo', + }, + ], + }, + ]); + console.log(usage); + process.exit(1); + } + + return { + ...params, + cwd: params.path, // For script convenience + }; +}; diff --git a/scripts/release/publish/print-post-publish-summary.js b/scripts/release/publish/print-post-publish-summary.js new file mode 100644 index 0000000000000..16bc08af2af75 --- /dev/null +++ b/scripts/release/publish/print-post-publish-summary.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {getUnexecutedCommands} = require('../utils'); + +module.exports = ({version}) => { + console.log( + chalk` + {green.bold Publish successful!} + ${getUnexecutedCommands()} + Next there are a couple of manual steps: + + {bold.underline Step 1: Create GitHub release} + + 1. Open new release page: {blue.bold https://github.com/facebook/react/releases/new} + 2. Choose {bold ${version}} from the dropdown menu + 3. Paste the new release notes from {yellow.bold CHANGELOG.md} + 4. Attach all files in {yellow.bold build/dist/*.js} except {yellow.bold react-art.*} to the release. + 5. Press {bold "Publish release"}! + + {bold.underline Step 2: Update the version on reactjs.org} + + 1. Git clone (or update) {blue.bold https://github.com/reactjs/reactjs.org} + 2. Open the {bold.yellow src/site-constants.js} file + 3. Update the {bold version} value to {bold ${version}} + 4. Open a Pull Request to {bold master} + + {bold.underline Step 3: Test the new release} + + 1. Install CRA: {bold npm i -g create-react-app} + 2. Create a test application: {bold create-react-app myapp} + 3. Run it: {bold cd myapp && npm start} + + {bold.underline Step 4: Notify the DOM team} + + 1. Notify DOM team members: {bold @nhunzaker @jquense @aweary} + `.replace(/\n +/g, '\n') + ); +}; diff --git a/scripts/release/publish/publish-to-npm.js b/scripts/release/publish/publish-to-npm.js new file mode 100644 index 0000000000000..a4d11f54a7ef3 --- /dev/null +++ b/scripts/release/publish/publish-to-npm.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {join} = require('path'); +const semver = require('semver'); +const {execRead, execUnlessDry, logPromise} = require('../utils'); +const {projects} = require('../config'); + +const push = async ({cwd, dry, version}) => { + const errors = []; + const tag = semver.prerelease(version) ? 'next' : 'latest'; + + const publishProject = async project => { + try { + const path = join(cwd, 'build', 'packages', project); + await execUnlessDry(`npm publish --tag ${tag}`, {cwd: path, dry}); + + if (!dry) { + const status = JSON.parse( + await execRead(`npm info ${project} dist-tags --json`) + ); + const remoteVersion = status[tag]; + + if (remoteVersion !== version) { + throw Error( + chalk`Publised version {yellow.bold ${version}} for ` + + `{bold ${project}} but NPM shows {yellow.bold ${remoteVersion}}` + ); + } + } + } catch (error) { + errors.push(error.message); + } + }; + + await Promise.all(projects.map(publishProject)); + + if (errors.length > 0) { + throw Error( + chalk` + Failure publishing to NPM + + {white ${errors.join('\n')}}` + ); + } +}; + +module.exports = async params => { + return logPromise(push(params), 'Pushing to git remote'); +}; diff --git a/scripts/release/publish/push-git-remote.js b/scripts/release/publish/push-git-remote.js new file mode 100644 index 0000000000000..c30d035f289d5 --- /dev/null +++ b/scripts/release/publish/push-git-remote.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +'use strict'; + +const {execUnlessDry, logPromise} = require('../utils'); + +const push = async ({cwd, dry}) => { + await execUnlessDry('git push', {cwd, dry}); + await execUnlessDry('git push --tags', {cwd, dry}); +}; + +module.exports = async params => { + return logPromise(push(params), 'Pushing to git remote'); +}; diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 49a0ed679b0de..f273da21a32f0 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -11,6 +11,27 @@ const execRead = async (command, options) => { return stdout.trim(); }; +const unexecutedCommands = []; + +const execUnlessDry = async (command, {cwd, dry}) => { + if (dry) { + unexecutedCommands.push(`${command} # {cwd: ${cwd}}`); + } else { + await exec(command, {cwd}); + } +}; + +const getUnexecutedCommands = () => { + if (unexecutedCommands.length > 0) { + return chalk` + The following commands were not executed because of the {bold --dry} flag: + {gray ${unexecutedCommands.join('\n')}} + `; + } else { + return ''; + } +}; + const logPromise = async (promise, text, completedLabel = '') => { const {frames, interval} = dots; @@ -41,5 +62,7 @@ const logPromise = async (promise, text, completedLabel = '') => { module.exports = { execRead, + execUnlessDry, + getUnexecutedCommands, logPromise, }; diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index 37330d2cd9b71..f2f3835bc100a 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -455,6 +455,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +semver@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" From b531e127a3f0e6ce54a6a40f9442270d9dba7bf5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 15:20:30 -0700 Subject: [PATCH 05/16] Deleted old react-release-manager scripts --- scripts/release-manager/.eslintrc.js | 7 - scripts/release-manager/.gitignore | 3 - scripts/release-manager/Readme.md | 480 ------------------ scripts/release-manager/cli.js | 138 ----- scripts/release-manager/commands/docs-prs.js | 150 ------ scripts/release-manager/commands/init.js | 74 --- .../commands/npm-check-access.js | 43 -- .../commands/npm-grant-access.js | 46 -- .../release-manager/commands/npm-publish.js | 74 --- scripts/release-manager/commands/q.js | 11 - .../release-manager/commands/stable-prs.js | 286 ----------- .../release-manager/commands/start-release.js | 46 -- scripts/release-manager/commands/utils/git.js | 87 ---- scripts/release-manager/commands/utils/npm.js | 42 -- scripts/release-manager/commands/version.js | 176 ------- scripts/release-manager/package.json | 24 - 16 files changed, 1687 deletions(-) delete mode 100644 scripts/release-manager/.eslintrc.js delete mode 100644 scripts/release-manager/.gitignore delete mode 100644 scripts/release-manager/Readme.md delete mode 100755 scripts/release-manager/cli.js delete mode 100644 scripts/release-manager/commands/docs-prs.js delete mode 100644 scripts/release-manager/commands/init.js delete mode 100644 scripts/release-manager/commands/npm-check-access.js delete mode 100644 scripts/release-manager/commands/npm-grant-access.js delete mode 100644 scripts/release-manager/commands/npm-publish.js delete mode 100644 scripts/release-manager/commands/q.js delete mode 100644 scripts/release-manager/commands/stable-prs.js delete mode 100644 scripts/release-manager/commands/start-release.js delete mode 100644 scripts/release-manager/commands/utils/git.js delete mode 100644 scripts/release-manager/commands/utils/npm.js delete mode 100644 scripts/release-manager/commands/version.js delete mode 100644 scripts/release-manager/package.json diff --git a/scripts/release-manager/.eslintrc.js b/scripts/release-manager/.eslintrc.js deleted file mode 100644 index 98f92d0827fe5..0000000000000 --- a/scripts/release-manager/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = { - rules: { - 'no-shadow': 0, - }, -}; diff --git a/scripts/release-manager/.gitignore b/scripts/release-manager/.gitignore deleted file mode 100644 index db864976cc981..0000000000000 --- a/scripts/release-manager/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.config.json -node_modules -data diff --git a/scripts/release-manager/Readme.md b/scripts/release-manager/Readme.md deleted file mode 100644 index 9799e54ef5e51..0000000000000 --- a/scripts/release-manager/Readme.md +++ /dev/null @@ -1,480 +0,0 @@ -# react-release-manager - -This is a tool that is being used to manage React releases. - -## Prerequisites - -You should have an existing clone of the React repo. We will call this a **“working copy”**. Ideally this is where you are most comfortable working on React. - -Your working copy of React **should be up to date**. Check out the `master` branch in it and run `git pull` just to be sure. - -## Cloning the Release Manager - -**If this is your first time using the Release Manager**, you need to set it up. -Skip this section if you’ve done this before. - -The Release Manager is also located inside the React repository so you need to **clone it to a separate folder**. Call it something other than `react` so that you don’t confuse it with the working copy. - -Check it out, install the dependencies, and run the CLI: - - ``` - cd ~/projects # or wherever - git clone https://github.com/facebook/react.git react-release-manager - cd react-release-manager/scripts/release-manager - yarn - ./cli.js - ``` - - You will see a command-line interface that lets you enter commands. - It will need to learn a few things to work on your machine. - Type `init` and press Enter. It will ask you a few prompts: - - 1. `GitHub token? (needs "repo" privs)` - Follow [these instructions](https://help.github.com/articles/creating-an-access-token-for-command-line-use/) to generate a GitHub token. Make sure to put a checkmark for `repo` privileges. Don’t share it with anyone! - - 2. `Location of local React checkout?` - Enter the local path to your React working copy. For example, it is `~/projects/react` on my machine. - -Now you should be all set for releasing React on this machine! - -## Before You Do Anything Else - -You should have two separate React checkouts by now: - -* **The Release Manager copy.** The previous section described how to set it up. You will only use this checkout for *running* the Release Manager. Run `git checkout master` and `git pull` to ensure it is up-to-date. - -* **Your working copy of React.** The Release Manager will operate on it, and you will fix any merge conflicts inside of it. This should be the folder path you specified when you ran `init` in the previous section. Run `git checkout master` and `git pull` to ensure it is up-to-date. - -Both clones clean and up-to-date? -If you aren’t already running it, run the Release Manager CLI: - -``` -cd react-release-manager/scripts/release-manager -./cli.js -``` - -Keep your working copy and the running Release Manager in separate terminal tabs. - -## Updating the Documentation - -When we merge a pull request to the documentation and it is relevant to the current version, we tag it with a `Documentation: needs merge to stable` label. The Release Manager can cherry-pick those commits so that they appear on the website. - -The documentation is built from the current stable branch. For example, for React 15.x the branch is called `15-stable`. Switch your working copy to it: - -``` -cd react -git checkout 15-stable -git pull -``` - -Then, in the Release Manager, run the command: - -``` -docs-prs -``` - -The Release Manager should find the PRs that haven’t been merged yet. Reply with `y` to get them merged and then with `y` to push your changes. - -**Tip:** If you see an error like `The previous cherry-pick is now empty, possibly due to conflict resolution` it might mean that there’s a stray PR with a label that has already been merged to the stable branch. In this case you need to remove the label manually and retry the command. - -## Cutting a Release - -### Verifying Permissions - -In the Release Manager, verify you have npm publish permissions: - -``` -npm-check-access -``` - -You will need to get all permissions before you can proceed. - -### Cherry Picking PRs - -If the permissions are cool, run: - -``` -start-release -``` - -**Tip:** if you get an error saying `'upstream' does not appear to be a git repository`, run `git remote add upstream https://github.com/facebook/react.git` in your working copy of React and try again. - -If everything went well, you should see a green `OK!` in the output. - -Create a new milestone in the [GitHub web interface](https://github.com/facebook/react/milestones) for the new release. Name it exactly after the version you intend to cut (e.g. `15.4.1`). Then run: - -``` -stable-prs -``` - -First, choose the current major “stable” milestone (such as `15-next`). Note that the Release Manager only sees merged PRs that have this milestone. - -**Tip:** our 15.x branch has diverged significantly so we are using `15-hipri` for things we really need to get out, and `15-lopri` for everything else. This is a temporary situation that should get better after Fiber is out. - -Next, choose the milestone you just created. This one should be specific and correspond to the version you intend to publish (such as `15.4.1`). The Release Manager will re-tag all PRs matching the previous “stable” milestone with this specific milestone after cherry-picking them. - -Finally, pick all appropriate labels with a spacebar. For example, a patch release usually contains `exempt` and `patch` PRs, and a minor release contains `minor` PRs in addition to them. - -Now the Release Manager will find all relevant PRs and attempt to cherry-pick them. Before agreeing to this, copy the list of PRs it prints out so that you can look them up later when you write the changelog. - -It is likely that some PRs won’t get merged cleanly. You’ll need to manually resolve the conflicts in the working copy. If the resolutions are not obvious it might be a sign the branches diverged too much which might be bad. (Talk to the team.) - -### Verifying the Changes - -Your working copy should now be in a clean state on a development branch. For example, during 15.x the development branch is `15-dev`. - -Verify it by running: - -``` -git status - ->On branch 15-dev ->Your branch is ahead of 'origin/15-dev' by 10 commits. -> (use "git push" to publish your local commits) ->nothing to commit, working directory clean -``` - -Next, run `npm test`. - -If there are any issues you might have introduced mistakes resolving conflicts. -You can fix them in a separate commit. - -**Tip:** tests might also be failing if dependency versions are incorrect. You might want to run `yarn` first since sometimes `package.json` on master is different from the stable branches. - -### Update the Error Codes - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Run this so that `scripts/error-codes/codes.json` is up to date: - -``` -npm run build -- --extract-errors -``` - -Check `git diff`. Do changes, if any, look sensible? - -If there are any changes, commit them: - -``` -git commit -am 'Update error codes' -``` - -You will see the commit hash. Copy it in your editor. You will need it later to cherry-pick the error codes update to master. - -If there were no changes, it’s also fine. - -### Push and Choose the Branch - -If you followed the guide correctly (and ran `start-release` in the beginning), you should be on a “stable development” branch such as `15-dev`. Now is a good time to push the development branch: - -``` -git push -``` - -Then comes the important part. -**If you plan to cut a stable release, switch the branch to the stable branch now.** - -For example, if you plan to cut `15.4.1` (rather than a `15.4.1-0` alpha release), run: - -``` -git checkout 15-stable -git merge --no-ff 15-dev -``` - -This will merge the commits you cherry-picked into the stable branch. - -However, if you plan to cut an alpha or a beta, you should stay on the “stable development” branch. - -### Update the Lockfile - -Run this so that the build is reproducible: - -``` -rm yarn.lock -rm -rf node_modules -yarn cache clean -yarn -``` - -Check `git diff`. Do changes look sensible? - -Commit your changes: - -``` -git commit -am 'Update Yarn lockfile' -``` - -If you’re feeling extra careful, you can run `npm test` again. - -### Write the Changelog - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Open `CHANGELOG.md` in the working copy and add release notes in the same format as earlier. It’s usually a good idea to summarize changes in a friendly way instead of using PR titles. While you may skip non-essential changes, it’s still good to give credit to contributors, so maybe group them together. You can verify that you haven’t messed up the markup by previewing them in an online Markdown editor. - -Commit your changes, for example: - -``` -git commit -am 'Add changelog' -``` - -You will see the commit hash. Copy it in your editor. You will need it later to cherry-pick the changelog update to master. - -### Bump the Version - -In the Release Manager, run: - -``` -version -``` - -It will ask you about the version you want to ship and it will commit the result with a tag. - -We’re not pushing anything yet, it will just create a local commit. - -### Ensure You Have the Bower Repo - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -There’s another repository you need to clone! -This time, it should be a sibling of your React working copy. - -In the working copy directory, you can run: - -``` -git clone https://github.com/reactjs/react-bower.git ../react-bower -``` - -### Build It! - - -Run in the working copy: - -``` -npm run build -``` - -This will create the build products in the working copy. You won’t see changes in git because the `build` folder is ignored. - -### Verify the Build Works - -At the very least, open `fixtures/packaging/babel-standalone/dev.html` in the browser. You should see a “Hello, World!” there, and the console should have no errors. - -If you changed anything related to how packages are created, I recommend following the instructions in `fixtures/packaging/README.md` to verify all fixtures still work. You can skip the “build React” step in it but still need to build the fixtures. In short, run `node build-all.js` in `fixtures` folder and follow the instructions it prints. - -They are manual tests, so the CI wouldn’t have caught errors in them. - -### Update Bower and Docs - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -**TODO: We used to have a `grunt release` command that does a few extra things but it was deleted when we moved build process to Rollup. We need to decide if we care about Bower or not, and if we do, either add a similar script back, or write up commands to do it by hand.** - -[Here's what the script used to do:](https://github.com/facebook/react/blob/50b3cab3ec7565085da21106791dbaa6fe22d862/grunt/tasks/release.js) - -* Copy UMD bundles to `../react-bower`. -* Commit and tag them in `react-bower` (but not push yet). -* Copy them to `docs/js/` (note: we changed their filenames so might need to change docs too). - -In addition to those changes, bump the version inside `docs/_config.yml`: - -```diff -- react_version: 15.4.0 -+ react_version: -``` - -Now commit the changes: - -``` -git commit -am 'Update React version in docs' -``` - -### Push the Working Copy - -Now we are ready to push the branch in the working copy: - -``` -git push -git push --tags -``` - -### Release on Bower - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Go to the Bower folder from your working copy and push the new commit and tag: - -``` -cd ../react-bower -git push -git push --tags -cd ../react -``` - -### Release on npm - -In the Release Manager, run: - -``` -npm-publish -``` - -### Cherry-Pick the Changelog - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Remember how you saved the hash of the commit changelog a few hours before? - -Now it’s time to switch our working copy to `master` and cherry-pick it: - -``` -git checkout master -git pull -git cherry-pick -``` - -Verify you picked the right commit: - -``` -git diff HEAD~ -``` - -Looks good? Push it. - -``` -git push -``` - -### Cherry-Pick the Error Codes - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -If error codes were updated, you were supposed to commit that earlier and record the commit hash. - -Did this happen? - -If so, cherry-pick it to `master` as well: - -``` -git cherry-pick -``` - -Verify you picked the right commit: - -``` -git diff HEAD~ -``` - -Looks good? Push it. - -``` -git push -``` - -### Creating a GitHub Release - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Copy your new release notes from `CHANGELOG.md` and [create a new Release](https://github.com/facebook/react/releases/new) on GitHub. Choose the tag version you just pushed in the dropdown so that it says “Existing tag”. Paste the release notes. - -Finally, attach these files to the release: - -* `build/dist/react.development.js` -* `build/dist/react.production.min.js` -* `build/dist/react-dom.development.js` -* `build/dist/react-dom.production.min.js` - -### Force-Updating the Website - -**This step is only necessary for a stable release.** -If you’re just cutting an alpha, you should skip it. - -Normally the docs should update themselves after CI runs. -However sometimes our CI might be slow or something might break. - -You can rebuild the docs manually if you want to. -Make sure you have a React copy in a sibling folder called `react-gh-pages`: - -``` -git clone https://github.com/facebook/react.git ../react-gh-pages -``` - -Then make sure it’s on `gh-pages` branch and that it’s up-to-date: - -``` -cd ../react-gh-pages -git checkout gh-pages -git pull -``` - -Switch back to the working copy and go to the `docs` folder: - -``` -cd ../react/docs -``` - -Switch to the stable branch (the one you just spent a lot of time with). -For example: - -``` -git checkout 15-stable -``` - -Build the docs now: - -``` -bundle install # Might need sudo. -bundle exec rake release -``` - -If this fails, maybe you’re missing some Ruby dependencies: - -``` -gem install bundler -``` - -Install them and try again. - -This should not produce any changes in the working copy, but `react-gh-pages` should get some file changes: - -``` -cd ../../react-gh-pages -git diff -``` - -If they look alright, commit and push them: - -``` -git commit -am 'Rebuild the website' -git push -``` - -Now open https://reactjs.org/, give it a few minutes, refresh, and behold. - -Don’t forget to switch to `master` for the future development. - -``` -git checkout master -``` - -### Bonus: Trying It Out - -Run: - -``` -npm i -g create-react-app -create-react-app ../my-new-app -cd ../my-new-app -npm start -``` - -This should use the latest version of React. - diff --git a/scripts/release-manager/cli.js b/scripts/release-manager/cli.js deleted file mode 100755 index de06b49a4926c..0000000000000 --- a/scripts/release-manager/cli.js +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const Vorpal = require('vorpal'); -const GitHubAPI = require('github-api'); -const untildify = require('untildify'); - -const fs = require('fs'); -const path = require('path'); -const os = require('os'); - -const child_process = require('child_process'); -const execSync = child_process.execSync; - -const vorpal = new Vorpal(); - -// Expects to be in a checkout of react that is a sibling of the react checkout you want to operate on -// eg ~/code/react@release-manager/scripts/release-manager & ~/code/react -// TODO: Make this an argument to the script -let PATH_TO_REPO = null; - -const PATH_TO_CONFIG = path.resolve( - os.homedir(), - '.react-release-manager.json' -); - -const DEFAULT_CONFIG = { - githubToken: null, - reactPath: path.resolve('../../../react'), -}; - -// Quick dry run opt-in. This allows quick debugging of execInRepo without -// actually running the command, ensuring no accidental publishing. -const DRY_RUN = false; - -// Enabled commands -const COMMANDS = [ - 'init', - 'docs-prs', - 'q', - 'stable-prs', - 'version', - 'npm-publish', - 'npm-check-access', - 'npm-grant-access', - 'start-release', -]; - -// HELPERS - -// Simple helper to write out some JSON for debugging -function writeTo(file, data) { - var folder = path.join(__dirname, 'data'); - if (!fs.existsSync(folder)) { - fs.mkdirSync(folder); - } - - fs.writeFile(path.join(folder, file), JSON.stringify(data, null, 2)); -} - -// Wrapper around exec so we don't have to worry about paths -function execInRepo(command) { - vorpal.log(chalk.gray(`Executing ${chalk.underline(command)}`)); - - if (DRY_RUN) { - return ''; - } - - return execSync(command, { - cwd: PATH_TO_REPO, - encoding: 'utf8', - }).trim(); -} - -function getReactVersion() { - return JSON.parse( - fs.readFileSync(path.join(PATH_TO_REPO, 'package.json'), 'utf8') - ).version; -} - -const app = { - vorpal, - - updateConfig() { - // TODO: write this. This should make it possible to start without a config - // and go through the init process to create one and then re-init the github - // setup. - this.config = this.loadConfig(); - }, - - loadConfig() { - try { - // TODO: validate config - let config = JSON.parse(fs.readFileSync(PATH_TO_CONFIG, 'utf8')); - config.reactPath = path.normalize(untildify(config.reactPath)); - PATH_TO_REPO = config.reactPath; - return config; - } catch (e) { - console.error( - 'Attempt to load config file failed. Please run `init` command for initial setup or make sure ' + - '~/.react-release-manager.json is valid JSON. Using a default config which may not work ' + - 'properly.' - ); - return DEFAULT_CONFIG; - } - }, - - init() { - this.config = this.loadConfig(); - - this.PATH_TO_CONFIG = PATH_TO_CONFIG; - - // GITHUB - this.github = new GitHubAPI({ - token: this.config.githubToken, - }); - this.ghrepo = this.github.getRepo('facebook', 'react'); - this.ghissues = this.github.getIssues('facebook', 'react'); - - // HELPERS - this.writeTo = writeTo; - this.execInRepo = execInRepo; - this.getReactVersion = getReactVersion; - - // Register commands - COMMANDS.forEach(command => { - vorpal.use(require(`./commands/${command}`)(vorpal, app)); - }); - - var v = vorpal.history('react-release-manager').delimiter('rrm \u2234'); - v.exec('help'); - v.show(); - }, -}; - -app.init(); diff --git a/scripts/release-manager/commands/docs-prs.js b/scripts/release-manager/commands/docs-prs.js deleted file mode 100644 index b1d970f49fb36..0000000000000 --- a/scripts/release-manager/commands/docs-prs.js +++ /dev/null @@ -1,150 +0,0 @@ -'use strict'; - -const chalk = require('chalk'); -const git = require('./utils/git'); - -const DOCS_LABEL = 'Documentation: needs merge to stable'; - -// FOR DOCS -// get all issues with label -// ensure all have pull_request -// FAIL: log issues that aren't prs -// fetch each pr, (issues.pull_request.url) -// sort each by merged_at -// git cherry-pick -x sha || git cherry-pick -x -m1 sha -// (or use API to look up number of parents, 2 = use -m1) -// track progress. on fail, pause and force user to handle manually, continue? prompt -// git push -// update labels on each PR -// ALT: dump link to -// https://github.com/facebook/react/issues?q=label%3A%22Documentation%3A+needs+merge+to+stable%22+is%3Aclosed -// and say manual step to remove label - -module.exports = function(vorpal, app) { - vorpal - .command('docs-prs') - .description( - 'Get list of documentation pull requests that need to be merged to the stable branch' - ) - .action(function(args, actionCB) { - const branch = git.getBranch(app); - if (!branch.match(/-stable$/)) { - this.log(chalk.red('Aborting...')); - this.log( - `You need to be on the latest stable branch in the React repo ` + - `to execute this command.\nYou are currently in ${branch}.` - ); - actionCB(); - return; - } - - const query = { - labels: [DOCS_LABEL].join(), // github-api doesn't join automatically - state: 'closed', - }; - - app.ghissues.listIssues(query, (err, body) => { - app.writeTo('issues.json', body); - // console.log(body); - // fs.writeFileSync('body.json', JSON.stringify(body, null, 2)); - // fs.writeFileSync('headers.json', JSON.stringify(headers, null, 2)); - // const prs = require('./body'); - - // This API *could* return issues that aren't pull requests, so filter out - // issues that don't have pull_request set. - const pulls = body.filter(issue => issue.pull_request); - - // We don't enough data about the pull request (merge sha or merge time) so we - // need to fetch more. We'll use promises so we don't have to count completions. - const pullPromises = pulls.map(pr => { - return new Promise((resolve, reject) => { - app.ghrepo.getPullRequest(pr.number, (err, body) => { - if (err) { - reject(err); - } - - app.writeTo(`pr-${pr.number}.json`, body); - // We want to track the original issue as well since it has the - // label information. - const richPull = body; - richPull.__originalIssue = pr; - resolve(richPull); - }); - }); - }); - Promise.all(pullPromises).then(richPulls => { - richPulls.forEach(pr => { - // Convert merged_at to real Date for sorting - pr.merged_at_date = new Date(pr.merged_at); - }); - - richPulls = richPulls.sort( - (a, b) => a.merged_at_date - b.merged_at_date - ); - - this.log(`Found ${chalk.bold(richPulls.length)} pull requests:`); - richPulls.forEach(pr => { - this.log(`${pr.html_url}: ${chalk.bold(pr.title)}`); - }); - - this.prompt( - { - name: 'merge', - type: 'confirm', - message: `Merge these ${richPulls.length} pull requests?`, - }, - res => { - if (res.merge) { - richPulls.forEach(pr => { - git.cherryPickMerge(app, pr.merge_commit_sha); - }); - - this.prompt( - { - name: 'push', - type: 'confirm', - message: 'Push these commits upstream?', - }, - res => { - if (res.push) { - git.push(app); - this.log( - `Pushed upstream! Removing "${DOCS_LABEL}" label from pull requests.` - ); - } - - // TODO: actually test this - var removeLabelsPromises = richPulls.map(pr => { - return new Promise((resolve, reject) => { - const updatedLabels = pr.__originalIssue.labels - .filter(label => label.name !== DOCS_LABEL) - .map(label => label.name); - app.ghissues.editIssue( - pr.number, - {labels: updatedLabels}, - (err, body) => { - if (err) { - reject(err); - } else { - resolve(pr); - } - } - ); - }); - }); - - Promise.all(removeLabelsPromises).then(() => { - this.log('Done!'); - actionCB(); - }); - } - ); - } else { - actionCB(); - } - } - ); - }); - }); - }); -}; diff --git a/scripts/release-manager/commands/init.js b/scripts/release-manager/commands/init.js deleted file mode 100644 index 5b392b7c1cfe6..0000000000000 --- a/scripts/release-manager/commands/init.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Command to init a project. This will create the .config.json file if it - * doesn't already exist. - */ - -'use strict'; - -const chalk = require('chalk'); -const fs = require('fs'); -const path = require('path'); -const untildify = require('untildify'); - -module.exports = function(vorpal, app) { - vorpal - .command('init') - .description('Initializes a .config.json file for use') - .action(function(args) { - return new Promise((resolve, reject) => { - fs.stat(app.PATH_TO_CONFIG, (err, stats) => { - if (stats) { - this.log('Config file exists, nothing to do.'); - reject(); - return; - } - - this.prompt([ - { - name: 'githubToken', - type: 'input', - message: `${chalk.bold('GitHub token?')} ${chalk.grey('(needs "repo" privs)')} `, - }, - { - name: 'reactPath', - type: 'input', - message: `${chalk.bold('Location of local React checkout?')} `, - validate: input => { - let npath = path.normalize(untildify(input)); - - if (npath === '.') { - return 'Cannot be `.`'; - } - - let stats; - try { - stats = fs.statSync(npath); - } catch (e) { - return `Error: ${e}`; - } - - if (!stats.isDirectory()) { - return `${npath} is not a directory.`; - } - - // TODO: Look for markers indicating this is a React checkout. - return true; - }, - }, - ]).then(answers => { - fs.writeFile( - app.PATH_TO_CONFIG, - JSON.stringify(answers, null, 2), - err => { - if (err) { - this.log('Error writing config file.', err); - reject(); - } - resolve(); - } - ); - }); - }); - }); - }); -}; diff --git a/scripts/release-manager/commands/npm-check-access.js b/scripts/release-manager/commands/npm-check-access.js deleted file mode 100644 index cfc8c3a4f2d50..0000000000000 --- a/scripts/release-manager/commands/npm-check-access.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const npmUtils = require('./utils/npm'); -const chalk = require('chalk'); -const opn = require('opn'); - -module.exports = function(vorpal, app) { - vorpal - .command('npm-check-access') - .description('Check to ensure you have correct access to npm packages') - .action(function(args) { - return new Promise((resolve, reject) => { - const username = npmUtils.whoami(app); - if (!username) { - return reject( - `${chalk.red('FAILED')} You aren't logged in to npm. Please run ` + - `${chalk.underline(`npm adduser`)} and try again.` - ); - } - - this.log(`${chalk.green('OK')} Logged in as ${chalk.bold(username)}`); - - const packagesNeedingAccess = npmUtils.packagesNeedingAccess( - app, - username - ); - - if (packagesNeedingAccess.length) { - this.log( - `${chalk.red('FAILED')} You don't have access to all of the packages ` + - `you need. We just opened a URL to file a new issue requesting access.` - ); - opn( - npmUtils.generateAccessNeededIssue(username, packagesNeedingAccess), - {wait: false} - ).then(resolve); - } else { - this.log(`${chalk.green('OK')} You can publish all React packages`); - resolve(); - } - }); - }); -}; diff --git a/scripts/release-manager/commands/npm-grant-access.js b/scripts/release-manager/commands/npm-grant-access.js deleted file mode 100644 index 0a1ffd3d26be7..0000000000000 --- a/scripts/release-manager/commands/npm-grant-access.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -const npmUtils = require('./utils/npm'); -const chalk = require('chalk'); - -module.exports = function(vorpal, app) { - vorpal - .command('npm-grant-access') - .description( - 'Grant access to somebody to publish React. Assumes you ran "npm-check-access" first.' - ) - .action(function(args) { - return new Promise((resolve, reject) => { - this.prompt({ - type: 'input', - message: 'Who would you like to grant access to? ', - name: 'username', - }).then(answers => { - if (!answers.username) { - return reject('ABORTING'); - } - - const packagesNeedingAccess = npmUtils.packagesNeedingAccess( - app, - answers.username - ); - - if (packagesNeedingAccess.length) { - this.log( - `${chalk.yellow('PENDING')} Granting access to ${packagesNeedingAccess}` - ); - npmUtils.grantAccess(app, answers.username, packagesNeedingAccess); - this.log( - `${chalk.green('OK')} Access has been granted to ${answers.username}.` - ); - resolve(); - } else { - this.log( - `${chalk.green('OK')} ${answers.username} already has access.` - ); - resolve(); - } - }); - }); - }); -}; diff --git a/scripts/release-manager/commands/npm-publish.js b/scripts/release-manager/commands/npm-publish.js deleted file mode 100644 index 5267a8693741e..0000000000000 --- a/scripts/release-manager/commands/npm-publish.js +++ /dev/null @@ -1,74 +0,0 @@ -// Publishes the built npm packages from build/packages -// 1. Show checklist (automate later) -// 2. Prompt to ensure build is complete -// 3. Prompt for dist-tag? - -'use strict'; - -const path = require('path'); -const semver = require('semver'); - -const glob = require('glob'); - -module.exports = function(vorpal, app) { - vorpal - .command('npm-publish') - .description("After you've run grunt release, publishes the npm packages") - .action(function(args) { - return new Promise((resolve, reject) => { - const currentVersion = app.getReactVersion(); - const isStable = semver.prerelease(currentVersion) === null; - - this.log(`Preparing to publish v${currentVersion}…`); - if (isStable) { - this.log(`"latest" dist-tag will be added to this version`); - } - - // TODO: show checklist - this.prompt([ - { - type: 'confirm', - message: 'Did you run `grunt build` or `grunt release` and bump the version number?', - default: false, - name: 'checklist', - }, - ]).then(answers => { - if (!answers.checklist) { - return reject('Complete the build process first'); - } - - // We'll grab all the tarballs and publish those directly. This - // is how we've historically done it, though in the past it was - // just npm publish pkg1.tgz && npm publish pkg2.tgz. This - // avoided the need to cd and publish. - const tgz = glob.sync('build/packages/*.tgz', { - cwd: app.config.reactPath, - }); - - // Just in case they didn't actually prep this. - // TODO: verify packages? - if (tgz.length === 0) { - reject('No built packages found'); - } - - // TODO: track success - tgz.forEach(file => { - this.log(app.execInRepo(`npm publish ${file} --tag=next`)); - }); - - if (isStable) { - tgz.forEach(file => { - const pkg = path.parse(file).name; - this.log( - app.execInRepo( - `npm dist-tag add ${pkg}@${currentVersion} latest` - ) - ); - }); - } - - resolve(); - }); - }); - }); -}; diff --git a/scripts/release-manager/commands/q.js b/scripts/release-manager/commands/q.js deleted file mode 100644 index c32a27c579b3a..0000000000000 --- a/scripts/release-manager/commands/q.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Stupid command to run exit. 'q' is way shorter, like less. - */ - -'use strict'; - -module.exports = function(vorpal, config) { - vorpal.command('q').hidden().action((args, cb) => { - vorpal.exec('exit').then(cb); - }); -}; diff --git a/scripts/release-manager/commands/stable-prs.js b/scripts/release-manager/commands/stable-prs.js deleted file mode 100644 index 586232adabfdd..0000000000000 --- a/scripts/release-manager/commands/stable-prs.js +++ /dev/null @@ -1,286 +0,0 @@ -'use strict'; - -const chalk = require('chalk'); -const pify = require('pify'); - -const git = require('./utils/git'); - -const SEMVER_LABELS = [ - 'semver-major', - 'semver-minor', - 'semver-patch', - 'semver-exempt', -]; - -module.exports = function(vorpal, app) { - vorpal - .command('stable-prs') - .description( - 'Get list of stable pull requests that need to be merged to the stable branch' - ) - .action(function(args) { - // This makes the chaining easier but obfuscates the actual API, which is - // unfortunate. The standalone API will return the right data but - // promisified will get the response object and then we need to pull data - // off of that. - let listMilestones = pify(app.ghissues.listMilestones.bind(app.ghissues)); - let listIssues = pify(app.ghissues.listIssues.bind(app.ghissues)); - let editIssue = pify(app.ghissues.editIssue.bind(app.ghissues)); - let getPullRequest = pify(app.ghrepo.getPullRequest.bind(app.ghrepo)); - - let targetMilestone = null; - - return new Promise((resolveAction, rejectAction) => { - listMilestones(null) - .then(milestones => { - app.writeTo('milestones.json', milestones); - - // Turn the milestones into choices for Inquirer - let milestoneChoices = milestones.map(milestone => { - return { - value: milestone.number, - name: milestone.title, - }; - }); - - // We need label choices too - let labelChoices = SEMVER_LABELS.map(label => { - return { - value: label, - name: label.split('-')[1], // "major" instead of "semver-major" - }; - }); - - // Ask about source milestone - // Ask about dest milestone - // TODO: allow creation of milestone here. - // Ask about which labels to pull from - - return this.prompt([ - { - name: 'srcMilestone', - type: 'list', - message: 'Which milestone should we pull PRs from?', - choices: milestoneChoices, - }, - { - name: 'destMilestone', - type: 'list', - message: 'Which milestone should we assign PRs to upon completion?', - choices: milestoneChoices, - }, - { - name: 'labels', - type: 'checkbox', - message: 'Which PRs should we select (use spacebar to check all that apply)', - choices: labelChoices, - }, - ]).then(answers => { - // this.log(JSON.stringify(answers, null, 2)); - targetMilestone = answers.destMilestone; - let labels = {}; - answers.labels.forEach(label => { - labels[label] = true; - }); - return { - labels: labels, - query: { - milestone: answers.srcMilestone, - per_page: 100, - state: 'closed', - }, - }; - }); - }) - // Request issues, filter to applicable PRs - .then(({labels, query}) => { - return ( - listIssues(query) - .then(issues => { - app.writeTo('stable-issues.json', issues); - - // This API *could* return issues that aren't pull requests, so filter out - // issues that don't have pull_request set. Also filter out issues that - // aren't the right level of semver (eg if running a patch release) - let filteringLabels = Object.keys(labels).length > 0; - const pulls = issues.filter(issue => { - if (!issue.pull_request) { - return false; - } - - if (!filteringLabels) { - return true; - } - - return issue.labels.some(label => labels[label.name]); - }); - app.writeTo('stable-prs.json', pulls); - return pulls; - }) - // We need to convert the issues to PRs. We don't actually have enough - // info for the pull request data, so we need to get more. Then we'll - // do some filtering and sorting to make sure we apply merged PRs in - // the order they were originally committed to avoid conflicts as much - // as possible. - .then(pulls => { - return Promise.all( - pulls.map(pr => { - return getPullRequest(pr.number).then(richPR => { - app.writeTo(`pr-${pr.number}.json`, richPR); - richPR.__originalIssue = pr; - return richPR; - }); - }) - ).then(richPRs => { - return richPRs - .filter(pr => { - if (!pr.merged_at) { - this.log( - `${chalk.yellow.bold('WARNING')} ${pr.html_url} was not merged,` + - ` should have the milestone unset.` - ); - return false; - } - return true; - }) - .map(pr => { - pr.merged_at_date = new Date(pr.merged_at); - return pr; - }) - .sort((a, b) => a.merged_at_date - b.merged_at_date); - }); - }) - ); - }) - // Quick prompt to double check that we should proceed. - .then(pulls => { - this.log(`Found ${chalk.bold(pulls.length)} pull requests:`); - pulls.forEach(pr => { - this.log(`${pr.html_url}: ${chalk.bold(pr.title)}`); - }); - - return this.prompt({ - name: 'merge', - type: 'confirm', - message: `Merge these ${pulls.length} pull requests?`, - }).then(answers => { - return answers.merge ? pulls : rejectAction('cancelled'); - }); - }) - // Ok, now we finally have rich pull request data. We can start cherry picking… - .then(pulls => { - // We're going to do some error handling here so we don't get into a - // terrible state. - this.log(`Found ${chalk.bold(pulls.length)} pull requests:`); - return new Promise((resolve, reject) => { - cherryPickPRs - .call(this, app, pulls) - .then(results => { - resolve(results); - }) - .catch(err => { - this.log( - `${chalk.red.bold('ERROR')} Something went wrong and your repo is` + - ` probably in a bad state. Sorry.` - ); - resolve({ - successful: [], - skipped: [], - didAbort: true, - }); - }); - }); - }) - // Update the milestone on successful PRs - // TODO: maybe handle didAbort and git reset --hard to a rev we read when we start the process? - .then(({successful, aborted, didAbort}) => { - if (didAbort) { - return undefined; - } - - return Promise.all( - successful.map(pr => { - return editIssue(pr.number, {milestone: targetMilestone}); - }) - ); - }) - // yay, we're done - .then(() => { - resolveAction(); - }) - .catch(err => { - this.log('ERROR', err); - rejectAction(); - }); - }); - }); -}; - -function cherryPickPRs(app, prs) { - let successful = []; - let skipped = []; - return new Promise((resolve, reject) => { - // Build array of thenables - let promises = prs.map(pr => { - return () => - new Promise((res, rej) => { - this.log( - chalk.yellow(`Cherry-picking #${pr.number} (${pr.title})...`) - ); - let failed = false; - try { - git.cherryPickMerge(app, pr.merge_commit_sha); - } catch (e) { - failed = true; - } - - if (!failed) { - this.log(chalk.green`Success`); - successful.push(pr); - return res(); - } - - return this.prompt({ - name: 'handle', - type: 'list', - message: `${chalk.red`Failed!`} ${chalk.yellow('This must be resolved manually!')}`, - choices: [ - {value: 'ok', name: 'Continue, mark successful'}, - {value: 'skip', name: 'Continue, mark skipped'}, - { - value: 'abort', - name: 'Abort process. Will require manual resetting of git state.', - }, - ], - }).then(answers => { - switch (answers.handle) { - case 'ok': - successful.push(pr); - break; - case 'skip': - skipped.push(pr); - break; - case 'abort': - return rej(pr.number); - } - res(pr.number); - }); - }); - }); - - // Since promises run on creation and we don't actually want that, we create - // an array of functions that return promises. We'll chain them here, not - // actually creating the next promise until we're ready. - var p = promises[0](); - for (let i = 1; i < promises.length; i++) { - p = p.then(() => promises[i]()); - } - p - .then(() => { - resolve({successful, skipped, didAbort: false}); - }) - .catch(e => { - resolve({successful, skipped, didAbort: true}); - }); - }); -} diff --git a/scripts/release-manager/commands/start-release.js b/scripts/release-manager/commands/start-release.js deleted file mode 100644 index fcf014107de12..0000000000000 --- a/scripts/release-manager/commands/start-release.js +++ /dev/null @@ -1,46 +0,0 @@ -// fetch upstream -// checkout 15-dev, update -// merge upstream/15-stable in -// done - -'use strict'; - -const chalk = require('chalk'); - -var git = require('./utils/git'); - -module.exports = function(vorpal, app) { - vorpal - .command('start-release') - .description('Start the process for shipping the next release') - .action(function(args) { - return new Promise((resolve, reject) => { - // TODO: ensure that repo has upstream remote, correct branches setup. - - if (!git.isClean(app)) { - this.log('ERROR: repo not in clean state'); - return reject(); - } - - // Fetch upstream - this ensures upstream/15-stable is updated and we - // won't rely on the local branch. - git.fetch(app, 'upstream'); - - // Checkout 15-dev - git.checkout(app, '15-dev'); - - // Update to ensure latest commits are in. Will hit network again but - // shouldn't need to get anything. - git.pull(app); - - // Merge 15-stable in - git.merge(app, 'upstream/15-stable', false); - - this.log(chalk.green.bold(`OK!`)); - this.log( - `You can now start cherry-picking commits to this branch using the "stable-prs" command.` - ); - resolve(); - }); - }); -}; diff --git a/scripts/release-manager/commands/utils/git.js b/scripts/release-manager/commands/utils/git.js deleted file mode 100644 index c9d3f65a72a4e..0000000000000 --- a/scripts/release-manager/commands/utils/git.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -function isClean(app) { - return getStatus(app) === ''; -} - -function getStatus(app) { - return app.execInRepo(`git status --untracked-files=no --porcelain`); -} - -function getBranch(app) { - return app.execInRepo(`git symbolic-ref HEAD`); -} - -function fetch(app, remote) { - return app.execInRepo(`git fetch ${remote}`); -} - -function checkout(app, ref) { - return app.execInRepo(`git checkout ${ref}`); -} - -function pull(app, ref) { - ref = ref || ''; - return app.execInRepo(`git pull ${ref}`); -} - -function merge(app, ref, ff, msg) { - let opts = [ff ? '--ff-only' : '--no-ff']; - if (!msg) { - opts.push('--no-edit'); - } else { - opts.push(`-m '${msg}''`); - } - return app.execInRepo(`git merge ${opts.join(' ')} ${ref}`); -} - -function tag(app, tag, ref) { - ref = ref || ''; - return app.execInRepo(`git tag ${tag} ${ref}`); -} - -function commit(app, msg, all) { - return app.execInRepo(`git commit -m '${msg}' ${all ? '-a' : ''}`); -} - -function push(app, remote, refspec, tags) { - let opts = [remote, refspec, tags ? '--tags' : '']; - return app.execInRepo(`git push ${opts.join(' ')}`); -} - -/** - * Cherry picks a single sha to the given branch. Very crude, but establishes - * some API. We don't know if the sha is a merge or a squashed commit so just - * try both. - * - * Assume we're already on the right branch. - */ -function cherryPickMerge(app, ref) { - // console.log(`cherry picking ${sha}`) - // git cherry-pick -x sha || git cherry-pick -x -m1 sha - try { - app.execInRepo(`git cherry-pick -x ${ref}`); - } catch (e) { - // Assume for now this just means it was actually a merge. - // TODO: gracefully handle other cases, like possibility the commit was - // already cherry-picked and should be skipped. - - app.execInRepo(`git cherry-pick -x -m1 ${ref}`); - } -} - -module.exports = { - getBranch, - getStatus, - isClean, - - commit, - checkout, - fetch, - pull, - push, - merge, - tag, - - cherryPickMerge, -}; diff --git a/scripts/release-manager/commands/utils/npm.js b/scripts/release-manager/commands/utils/npm.js deleted file mode 100644 index 8126a9caa2eac..0000000000000 --- a/scripts/release-manager/commands/utils/npm.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const querystring = require('querystring'); - -const PACKAGES = [ - 'react-dom', - 'react-native-renderer', - 'react-test-renderer', - 'react', -]; - -function whoami(app) { - return app.execInRepo('npm whoami'); -} - -function packagesNeedingAccess(app, username) { - let packages = JSON.parse( - app.execInRepo(`npm access ls-packages ${username}`) - ); - return PACKAGES.filter(pkg => packages[pkg] !== 'read-write'); -} - -function generateAccessNeededIssue(username, packages) { - let data = { - title: `npm access request: ${username}`, - body: `In order to publish React to npm I need access to the following repositories: -${packages.map(pkg => `- [${pkg}](https://npm.im/${pkg})`).join('\n')}`, - }; - return `https://github.com/facebook/react/issues/new?${querystring.stringify(data)}`; -} - -function grantAccess(app, username, packages) { - packages.forEach(pkg => { - app.execInRepo(`npm owner add ${username} ${pkg}`); - }); -} - -module.exports.PACKAGES = PACKAGES; -module.exports.whoami = whoami; -module.exports.packagesNeedingAccess = packagesNeedingAccess; -module.exports.generateAccessNeededIssue = generateAccessNeededIssue; -module.exports.grantAccess = grantAccess; diff --git a/scripts/release-manager/commands/version.js b/scripts/release-manager/commands/version.js deleted file mode 100644 index 8e1f5532afaaf..0000000000000 --- a/scripts/release-manager/commands/version.js +++ /dev/null @@ -1,176 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const semver = require('semver'); - -const chalk = require('chalk'); - -const git = require('./utils/git'); - -// Overview -// 1. Display current version -// 2. Prompt for new version -// 3. Update appropriate files -// - package.json (version) -// - npm-shrinkwrap.json (version) -// - packages/react/package.json (version) -// - packages/react-addons/package.json (version, peerDependencies.react) -// - packages/react-dom/package.json (version, peerDependencies.react) -// - packages/react-native-renderer/package.json (version, peerDependencies.react) -// - packages/react-test-renderer/package.json (version, peerDependencies.react) -// - src/ReactVersion.js (module.exports) -// 4. Commit? - -function updateJSON(path, fields, value) { - let data; - try { - data = JSON.parse(fs.readFileSync(path, 'utf8')); - } catch (e) { - this.log(chalk.red('ERROR') + ` ${path} doesn't exist… skipping.`); - return; - } - fields.forEach(field => { - let fieldPath = field.split('.'); - if (fieldPath.length === 1) { - data[field] = value; - } else { - // assume length of 2 is some dep.react and we can just use ^ because we - // know it's true. do something more versatile later - data[fieldPath[0]][fieldPath[1]] = '^' + value; - } - }); - fs.writeFileSync(path, JSON.stringify(data, null, 2) + '\n'); -} - -module.exports = function(vorpal, app) { - vorpal - .command('version') - .description('Update the version of React, useful while publishing') - .action(function(args, actionCB) { - let currentVersion = app.getReactVersion(); - - // TODO: See if we can do a better job for handling pre* bumps. The ones - // semver adds are of the form -0, but we've used -alpha.0 or -rc.0. - // 'prerelease' will increment those properly (but otherwise has the same problem). - // Live with it for now since it won't be super common. Write docs. - let choices = ['prerelease', 'patch', 'minor', 'major'].map(release => { - let version = semver.inc(currentVersion, release); - return { - value: version, - name: `${chalk.bold(version)} (${release})`, - }; - }); - choices.push('Other'); - - this.prompt([ - { - type: 'list', - name: 'version', - choices: choices, - message: `New version (currently ${chalk.bold(currentVersion)}):`, - }, - { - type: 'input', - name: 'version', - message: `New version (currently ${chalk.bold(currentVersion)}): `, - when: res => res.version === 'Other', - }, - ]).then(res => { - let newVersion = semver.valid(res.version); - - if (!newVersion) { - return actionCB( - `${chalk.red('ERROR')} ${res.version} is not a semver-valid version` - ); - } - - this.log(`Updating to ${newVersion}`); - - // The JSON files. They're all updated the same way so batch. - [ - { - file: 'package.json', - fields: ['version'], - }, - { - file: 'npm-shrinkwrap.json', - fields: ['version'], - }, - { - file: 'packages/react/package.json', - fields: ['version'], - }, - { - file: 'packages/react-addons/package.json', - fields: ['version', 'peerDependencies.react'], - }, - { - file: 'packages/react-dom/package.json', - fields: ['version', 'peerDependencies.react'], - }, - { - file: 'packages/react-native-renderer/package.json', - fields: ['version', 'peerDependencies.react'], - }, - { - file: 'packages/react-noop-renderer/package.json', - fields: ['version'], - }, - { - file: 'packages/react-test-renderer/package.json', - fields: ['version', 'peerDependencies.react'], - }, - ].forEach(opts => { - updateJSON.apply(this, [ - path.join(app.config.reactPath, opts.file), - opts.fields, - newVersion, - ]); - }); - - // We also need to update src/ReactVersion.js which has the version in - // string form in JS code. We'll just do a string replace. - - const PATH_TO_REACTVERSION = path.join( - app.config.reactPath, - 'src/ReactVersion.js' - ); - - let reactVersionContents = fs.readFileSync( - PATH_TO_REACTVERSION, - 'utf8' - ); - - reactVersionContents = reactVersionContents.replace( - currentVersion, - newVersion - ); - fs.writeFileSync(PATH_TO_REACTVERSION, reactVersionContents); - - this.prompt([ - { - name: 'commit', - type: 'confirm', - message: 'Commit these changes (`git commit -a`)?', - default: true, - }, - { - name: 'tag', - type: 'confirm', - message: 'Tag the version commit (not necessary for non-stable releases)?', - default: true, - when: res => res.commit, - }, - ]).then(res => { - if (res.commit) { - git.commit(app, newVersion, true); - } - if (res.tag) { - git.tag(app, `v${newVersion}`); - } - actionCB(); - }); - }); - }); -}; diff --git a/scripts/release-manager/package.json b/scripts/release-manager/package.json deleted file mode 100644 index dd67d4896825e..0000000000000 --- a/scripts/release-manager/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "private": true, - "name": "react-release-manager", - "version": "1.0.0", - "description": "Tool to manage React releases", - "main": "cli.js", - "dependencies": { - "chalk": "^1.1.3", - "colors": "^1.1.2", - "github-api": "^2.2.0", - "glob": "^7.0.5", - "opn": "^4.0.2", - "pify": "^2.3.0", - "semver": "^5.3.0", - "untildify": "^3.0.2", - "vorpal": "^1.10.10" - }, - "devDependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Paul O'Shannessy ", - "license": "MIT" -} From 71ba581ea67b4ca296b31a2ceea9ed4e3426c7ba Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 15:51:47 -0700 Subject: [PATCH 06/16] Cleaned up release package.json --- scripts/release/package.json | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/scripts/release/package.json b/scripts/release/package.json index 7200b6df6b68e..34f8c57999291 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -1,13 +1,9 @@ { - "name": "release", - "version": "1.0.0", - "description": "TODO Document", - "main": "release.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", + "name": "react-release-script", + "version": "0.0.0", + "private": true, + "scripts": {}, + "license": "MIT", "dependencies": { "chalk": "^2.1.0", "child-process-promise": "^2.2.1", From d513b6f8a4caaf9608fc78f12448c05f653cca9d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 13 Oct 2017 15:55:45 -0700 Subject: [PATCH 07/16] Basic README instructions --- scripts/release/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/release/README.md b/scripts/release/README.md index a330cd98ef0f9..0253dedce9d54 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -1 +1,8 @@ -TODO Document \ No newline at end of file +# React Release Script + +At a high-level, the new release script runs in 2 passes: **build** and **publish**. The **build** script does the heavy lifting (eg checking CI, running automated tests, building Rollup bundles) and then prints instructions for manual verification. The **release** script then publishes the built artifacts to NPM and pushes to GitHub. + +Run a script without any parameters to see its usage, eg: +``` +node ./build.js +``` \ No newline at end of file From 5fe95ee52e36324f5874ab0138be316e5f40c5fb Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 12:40:06 -0700 Subject: [PATCH 08/16] Removed unnecessary 'async' keyword from a method --- scripts/release/build/check-package-dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/build/check-package-dependencies.js b/scripts/release/build/check-package-dependencies.js index 370343944d36e..6a63ae5234d50 100644 --- a/scripts/release/build/check-package-dependencies.js +++ b/scripts/release/build/check-package-dependencies.js @@ -21,7 +21,7 @@ const check = async ({cwd}) => { const invalidDependencies = []; - const checkModule = async module => { + const checkModule = module => { const rootVersion = rootPackage.devDependencies[module]; projectPackages.forEach(projectPackage => { From 70eae06cfb870f92ad097730473b6e146a69f3e6 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 12:43:13 -0700 Subject: [PATCH 09/16] Wordsmithing --- scripts/release/build/print-post-build-summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/build/print-post-build-summary.js b/scripts/release/build/print-post-build-summary.js index ca2b088f9dac4..92cceff5140f0 100644 --- a/scripts/release/build/print-post-build-summary.js +++ b/scripts/release/build/print-post-build-summary.js @@ -33,7 +33,7 @@ module.exports = params => { 2. It should say {italic "Hello world!"} 3. Next go to {yellow.bold fixtures/packaging} and run {bold node build-all.js} 4. Install the "serve" module ({bold npm install -g serve}) - 5. Go to the repo root and {bold run serve -s .} + 5. Go to the repo root and {bold serve -s .} 6. Open {blue.bold http://localhost:5000/fixtures/packaging} 7. Verify every iframe shows {italic "Hello world!"} From bc093c97b370b65aa4fe13478caae84b8b2b1984 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 13:26:29 -0700 Subject: [PATCH 10/16] Tweaked README --- scripts/release/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/release/README.md b/scripts/release/README.md index 0253dedce9d54..510316dc04824 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -1,8 +1,13 @@ # React Release Script -At a high-level, the new release script runs in 2 passes: **build** and **publish**. The **build** script does the heavy lifting (eg checking CI, running automated tests, building Rollup bundles) and then prints instructions for manual verification. The **release** script then publishes the built artifacts to NPM and pushes to GitHub. +At a high-level, the new release script runs in 2 passes: **build** and **publish**. +1. The **build** script does the heavy lifting (eg checking CI, running automated tests, building Rollup bundles) and then prints instructions for manual verification. +1. The **release** script then publishes the built artifacts to NPM and pushes to GitHub. Run a script without any parameters to see its usage, eg: ``` -node ./build.js -``` \ No newline at end of file +./scripts/release/build.js +./scripts/release/publish.js +``` + +Each script will guide the release engineer through any necessary steps (including environment setup and manual testing steps). \ No newline at end of file From 89d72b49732c0040f2fdc9067b4b42368b320b8c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 13:47:59 -0700 Subject: [PATCH 11/16] Renamed build -> build-commands and publish -> publish-commands to avoid conflict with .gitignore --- .../build-artifacts.js | 0 .../check-circle-ci-status.js | 0 .../check-environment-variables.js | 0 .../check-npm-permissions.js | 0 .../check-package-dependencies.js | 0 .../check-uncommitted-changes.js | 0 .../install-yarn-dependencies.js | 0 .../parse-build-parameters.js | 0 .../print-post-build-summary.js | 0 .../run-automated-tests.js | 0 .../{build => build-commands}/update-git.js | 0 .../update-package-versions.js | 0 .../update-yarn-dependencies.js | 0 .../build-commands/validate-version.js | 18 ++++++++++++ scripts/release/build.js | 28 +++++++++---------- .../check-build-status.js | 0 .../commit-changelog.js | 0 .../parse-publish-params.js | 0 .../print-post-publish-summary.js | 0 .../publish-to-npm.js | 0 .../push-git-remote.js | 0 scripts/release/publish.js | 12 ++++---- 22 files changed, 38 insertions(+), 20 deletions(-) rename scripts/release/{build => build-commands}/build-artifacts.js (100%) rename scripts/release/{build => build-commands}/check-circle-ci-status.js (100%) rename scripts/release/{build => build-commands}/check-environment-variables.js (100%) rename scripts/release/{build => build-commands}/check-npm-permissions.js (100%) rename scripts/release/{build => build-commands}/check-package-dependencies.js (100%) rename scripts/release/{build => build-commands}/check-uncommitted-changes.js (100%) rename scripts/release/{build => build-commands}/install-yarn-dependencies.js (100%) rename scripts/release/{build => build-commands}/parse-build-parameters.js (100%) rename scripts/release/{build => build-commands}/print-post-build-summary.js (100%) rename scripts/release/{build => build-commands}/run-automated-tests.js (100%) rename scripts/release/{build => build-commands}/update-git.js (100%) rename scripts/release/{build => build-commands}/update-package-versions.js (100%) rename scripts/release/{build => build-commands}/update-yarn-dependencies.js (100%) create mode 100644 scripts/release/build-commands/validate-version.js rename scripts/release/{publish => publish-commands}/check-build-status.js (100%) rename scripts/release/{publish => publish-commands}/commit-changelog.js (100%) rename scripts/release/{publish => publish-commands}/parse-publish-params.js (100%) rename scripts/release/{publish => publish-commands}/print-post-publish-summary.js (100%) rename scripts/release/{publish => publish-commands}/publish-to-npm.js (100%) rename scripts/release/{publish => publish-commands}/push-git-remote.js (100%) diff --git a/scripts/release/build/build-artifacts.js b/scripts/release/build-commands/build-artifacts.js similarity index 100% rename from scripts/release/build/build-artifacts.js rename to scripts/release/build-commands/build-artifacts.js diff --git a/scripts/release/build/check-circle-ci-status.js b/scripts/release/build-commands/check-circle-ci-status.js similarity index 100% rename from scripts/release/build/check-circle-ci-status.js rename to scripts/release/build-commands/check-circle-ci-status.js diff --git a/scripts/release/build/check-environment-variables.js b/scripts/release/build-commands/check-environment-variables.js similarity index 100% rename from scripts/release/build/check-environment-variables.js rename to scripts/release/build-commands/check-environment-variables.js diff --git a/scripts/release/build/check-npm-permissions.js b/scripts/release/build-commands/check-npm-permissions.js similarity index 100% rename from scripts/release/build/check-npm-permissions.js rename to scripts/release/build-commands/check-npm-permissions.js diff --git a/scripts/release/build/check-package-dependencies.js b/scripts/release/build-commands/check-package-dependencies.js similarity index 100% rename from scripts/release/build/check-package-dependencies.js rename to scripts/release/build-commands/check-package-dependencies.js diff --git a/scripts/release/build/check-uncommitted-changes.js b/scripts/release/build-commands/check-uncommitted-changes.js similarity index 100% rename from scripts/release/build/check-uncommitted-changes.js rename to scripts/release/build-commands/check-uncommitted-changes.js diff --git a/scripts/release/build/install-yarn-dependencies.js b/scripts/release/build-commands/install-yarn-dependencies.js similarity index 100% rename from scripts/release/build/install-yarn-dependencies.js rename to scripts/release/build-commands/install-yarn-dependencies.js diff --git a/scripts/release/build/parse-build-parameters.js b/scripts/release/build-commands/parse-build-parameters.js similarity index 100% rename from scripts/release/build/parse-build-parameters.js rename to scripts/release/build-commands/parse-build-parameters.js diff --git a/scripts/release/build/print-post-build-summary.js b/scripts/release/build-commands/print-post-build-summary.js similarity index 100% rename from scripts/release/build/print-post-build-summary.js rename to scripts/release/build-commands/print-post-build-summary.js diff --git a/scripts/release/build/run-automated-tests.js b/scripts/release/build-commands/run-automated-tests.js similarity index 100% rename from scripts/release/build/run-automated-tests.js rename to scripts/release/build-commands/run-automated-tests.js diff --git a/scripts/release/build/update-git.js b/scripts/release/build-commands/update-git.js similarity index 100% rename from scripts/release/build/update-git.js rename to scripts/release/build-commands/update-git.js diff --git a/scripts/release/build/update-package-versions.js b/scripts/release/build-commands/update-package-versions.js similarity index 100% rename from scripts/release/build/update-package-versions.js rename to scripts/release/build-commands/update-package-versions.js diff --git a/scripts/release/build/update-yarn-dependencies.js b/scripts/release/build-commands/update-yarn-dependencies.js similarity index 100% rename from scripts/release/build/update-yarn-dependencies.js rename to scripts/release/build-commands/update-yarn-dependencies.js diff --git a/scripts/release/build-commands/validate-version.js b/scripts/release/build-commands/validate-version.js new file mode 100644 index 0000000000000..04639bbfaf86a --- /dev/null +++ b/scripts/release/build-commands/validate-version.js @@ -0,0 +1,18 @@ +'use strict'; + +const chalk = require('chalk'); +const {readJson} = require('fs-extra'); +const {join} = require('path'); +const semver = require('semver'); + +module.exports = async ({cwd, version}) => { + if (!semver.valid(version)) { + throw Error('Invalid version specified'); + } + + const rootPackage = await readJson(join(cwd, 'package.json')); + + if (!semver.gt(version, rootPackage.version)) { + throw Error(chalk`Version {white ${rootPackage.version}} has already been published`); + } +}; \ No newline at end of file diff --git a/scripts/release/build.js b/scripts/release/build.js index ed6c14af055b0..bcd695fbbae68 100755 --- a/scripts/release/build.js +++ b/scripts/release/build.js @@ -5,20 +5,20 @@ const chalk = require('chalk'); const logUpdate = require('log-update'); -const buildArtifacts = require('./build/build-artifacts'); -const checkCircleCiStatus = require('./build/check-circle-ci-status'); -const checkEnvironmentVariables = require('./build/check-environment-variables'); -const checkNpmPermissions = require('./build/check-npm-permissions'); -const checkPackageDependencies = require('./build/check-package-dependencies'); -const checkUncommittedChanges = require('./build/check-uncommitted-changes'); -const installYarnDependencies = require('./build/install-yarn-dependencies'); -const parseBuildParameters = require('./build/parse-build-parameters'); -const printPostBuildSummary = require('./build/print-post-build-summary'); -const runAutomatedTests = require('./build/run-automated-tests'); -const updateGit = require('./build/update-git'); -const updatePackageVersions = require('./build/update-package-versions'); -const updateYarnDependencies = require('./build/update-yarn-dependencies'); -const validateVersion = require('./build/validate-version'); +const buildArtifacts = require('./build-commands/build-artifacts'); +const checkCircleCiStatus = require('./build-commands/check-circle-ci-status'); +const checkEnvironmentVariables = require('./build-commands/check-environment-variables'); +const checkNpmPermissions = require('./build-commands/check-npm-permissions'); +const checkPackageDependencies = require('./build-commands/check-package-dependencies'); +const checkUncommittedChanges = require('./build-commands/check-uncommitted-changes'); +const installYarnDependencies = require('./build-commands/install-yarn-dependencies'); +const parseBuildParameters = require('./build-commands/parse-build-parameters'); +const printPostBuildSummary = require('./build-commands/print-post-build-summary'); +const runAutomatedTests = require('./build-commands/run-automated-tests'); +const updateGit = require('./build-commands/update-git'); +const updatePackageVersions = require('./build-commands/update-package-versions'); +const updateYarnDependencies = require('./build-commands/update-yarn-dependencies'); +const validateVersion = require('./build-commands/validate-version'); // Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { diff --git a/scripts/release/publish/check-build-status.js b/scripts/release/publish-commands/check-build-status.js similarity index 100% rename from scripts/release/publish/check-build-status.js rename to scripts/release/publish-commands/check-build-status.js diff --git a/scripts/release/publish/commit-changelog.js b/scripts/release/publish-commands/commit-changelog.js similarity index 100% rename from scripts/release/publish/commit-changelog.js rename to scripts/release/publish-commands/commit-changelog.js diff --git a/scripts/release/publish/parse-publish-params.js b/scripts/release/publish-commands/parse-publish-params.js similarity index 100% rename from scripts/release/publish/parse-publish-params.js rename to scripts/release/publish-commands/parse-publish-params.js diff --git a/scripts/release/publish/print-post-publish-summary.js b/scripts/release/publish-commands/print-post-publish-summary.js similarity index 100% rename from scripts/release/publish/print-post-publish-summary.js rename to scripts/release/publish-commands/print-post-publish-summary.js diff --git a/scripts/release/publish/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js similarity index 100% rename from scripts/release/publish/publish-to-npm.js rename to scripts/release/publish-commands/publish-to-npm.js diff --git a/scripts/release/publish/push-git-remote.js b/scripts/release/publish-commands/push-git-remote.js similarity index 100% rename from scripts/release/publish/push-git-remote.js rename to scripts/release/publish-commands/push-git-remote.js diff --git a/scripts/release/publish.js b/scripts/release/publish.js index f9de677977f76..6c8170c86af3a 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -5,12 +5,12 @@ const chalk = require('chalk'); const logUpdate = require('log-update'); -const checkBuildStatus = require('./publish/check-build-status'); -const commitChangelog = require('./publish/commit-changelog'); -const parsePublishParams = require('./publish/parse-publish-params'); -const printPostPublishSummary = require('./publish/print-post-publish-summary'); -const pushGitRemote = require('./publish/push-git-remote'); -const publishToNpm = require('./publish/publish-to-npm'); +const checkBuildStatus = require('./publish-commands/check-build-status'); +const commitChangelog = require('./publish-commands/commit-changelog'); +const parsePublishParams = require('./publish-commands/parse-publish-params'); +const printPostPublishSummary = require('./publish-commands/print-post-publish-summary'); +const pushGitRemote = require('./publish-commands/push-git-remote'); +const publishToNpm = require('./publish-commands/publish-to-npm'); // Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { From c6af68f87a12c74c3c9bcbeaa5eb4e65b0ca3822 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 14:02:54 -0700 Subject: [PATCH 12/16] Bump pre-release package versions differently --- .../build-commands/update-package-versions.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/release/build-commands/update-package-versions.js b/scripts/release/build-commands/update-package-versions.js index b061b4d8f8dd4..8be55a7e6d25c 100644 --- a/scripts/release/build-commands/update-package-versions.js +++ b/scripts/release/build-commands/update-package-versions.js @@ -7,6 +7,7 @@ const {exec} = require('child-process-promise'); const {readFileSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); const {join} = require('path'); +const semver = require('semver'); const {projects} = require('../config'); const {execUnlessDry, logPromise} = require('../utils'); @@ -30,7 +31,16 @@ const update = async ({cwd, dry, version}) => { const updateProjectPackage = async project => { const path = join(cwd, 'packages', project, 'package.json'); const json = await readJson(path); - json.version = version; + + // Unstable packages (eg version < 1.0) are treated differently. + // In order to simplify DX for the release engineer, + // These packages are auto-incremented by a minor version number. + if (semver.lt(json.version, '1.0.0')) { + json.version = `0.${semver.minor(json.version) + 1}.0`; + } else { + json.version = version; + } + if (project !== 'react') { json.peerDependencies.react = `^${version}`; } From 2bedf9ee0022fe9b3a6962dbc148e5ce47da8713 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 14:04:57 -0700 Subject: [PATCH 13/16] Prettier --- scripts/release/build-commands/validate-version.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release/build-commands/validate-version.js b/scripts/release/build-commands/validate-version.js index 04639bbfaf86a..8329d3c38dbb1 100644 --- a/scripts/release/build-commands/validate-version.js +++ b/scripts/release/build-commands/validate-version.js @@ -13,6 +13,8 @@ module.exports = async ({cwd, version}) => { const rootPackage = await readJson(join(cwd, 'package.json')); if (!semver.gt(version, rootPackage.version)) { - throw Error(chalk`Version {white ${rootPackage.version}} has already been published`); + throw Error( + chalk`Version {white ${rootPackage.version}} has already been published` + ); } -}; \ No newline at end of file +}; From cbd68f7ef451950b4ef42029454ad12437699f54 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 14:10:58 -0700 Subject: [PATCH 14/16] Improved CircleCI API token setup instructions message --- .../build-commands/check-environment-variables.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/release/build-commands/check-environment-variables.js b/scripts/release/build-commands/check-environment-variables.js index 4a1a4ca26ae74..4305af29f960f 100644 --- a/scripts/release/build-commands/check-environment-variables.js +++ b/scripts/release/build-commands/check-environment-variables.js @@ -5,16 +5,22 @@ const chalk = require('chalk'); module.exports = () => { - if (!process.env.CIRCLE_CI_API_TOKEN) { + if (!process.env.CIRCLE_CI_API_TOKENN) { throw Error( chalk` {red Missing CircleCI API token} {white The CircleCI API is used to check the status of the latest commit.} - {white This API requires a token which must be exposed via {yellow.bold CIRCLE_CI_API_TOKEN}} - {white For instructions on creating this token check out the link below:} + {white This API requires a token which must be exposed via a {yellow.bold CIRCLE_CI_API_TOKEN} environment variable.} + {white In order to run this script you will need to create your own API token.} + {white Instructions can be found at:} {blue.bold https://circleci.com/docs/api/v1-reference/#getting-started} + + {white To make this token available to the release script, add it to your {yellow.bold .bash_profile} like so:} + + {gray # React release script} + {white export CIRCLE_CI_API_TOKEN=} ` ); } From 1c9a4303407a25733917e3b7cd63bb7c22b2b4d7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 14:23:02 -0700 Subject: [PATCH 15/16] Lint fix --- scripts/release/build-commands/check-environment-variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/build-commands/check-environment-variables.js b/scripts/release/build-commands/check-environment-variables.js index 4305af29f960f..d9dcd9d97f9c2 100644 --- a/scripts/release/build-commands/check-environment-variables.js +++ b/scripts/release/build-commands/check-environment-variables.js @@ -11,7 +11,7 @@ module.exports = () => { {red Missing CircleCI API token} {white The CircleCI API is used to check the status of the latest commit.} - {white This API requires a token which must be exposed via a {yellow.bold CIRCLE_CI_API_TOKEN} environment variable.} + {white This API requires a token which must be exposed via a {yellow.bold CIRCLE_CI_API_TOKEN} environment var.} {white In order to run this script you will need to create your own API token.} {white Instructions can be found at:} From 558c8ef3644a8b2cc288ad96b01c7e689229309c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 16 Oct 2017 14:43:51 -0700 Subject: [PATCH 16/16] Typofix --- scripts/release/build-commands/check-environment-variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/build-commands/check-environment-variables.js b/scripts/release/build-commands/check-environment-variables.js index d9dcd9d97f9c2..a4318a5fa3d20 100644 --- a/scripts/release/build-commands/check-environment-variables.js +++ b/scripts/release/build-commands/check-environment-variables.js @@ -5,7 +5,7 @@ const chalk = require('chalk'); module.exports = () => { - if (!process.env.CIRCLE_CI_API_TOKENN) { + if (!process.env.CIRCLE_CI_API_TOKEN) { throw Error( chalk` {red Missing CircleCI API token}