From b55f38e92954f6ddadc171b37314c33a9c0f92fa Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 13 Nov 2018 15:38:15 -0800 Subject: [PATCH 01/22] Download Circle CI artifact and prepare canary release --- .../print-post-build-summary.js | 6 +- scripts/release/build.js | 19 +----- .../download-build-artifacts.js | 60 +++++++++++++++++ .../prepare-canary-commands/parse-params.js | 65 +++++++++++++++++++ .../prepare-canary-commands/print-summary.js | 40 ++++++++++++ scripts/release/prepare-canary.js | 24 +++++++ scripts/release/publish.js | 17 +---- .../check-environment-variables.js | 0 scripts/release/utils.js | 16 +++++ 9 files changed, 213 insertions(+), 34 deletions(-) create mode 100644 scripts/release/prepare-canary-commands/download-build-artifacts.js create mode 100644 scripts/release/prepare-canary-commands/parse-params.js create mode 100644 scripts/release/prepare-canary-commands/print-summary.js create mode 100755 scripts/release/prepare-canary.js rename scripts/release/{build-commands => shared-commands}/check-environment-variables.js (100%) diff --git a/scripts/release/build-commands/print-post-build-summary.js b/scripts/release/build-commands/print-post-build-summary.js index a9c660d0c58bf..688159c19b622 100644 --- a/scripts/release/build-commands/print-post-build-summary.js +++ b/scripts/release/build-commands/print-post-build-summary.js @@ -43,9 +43,9 @@ module.exports = ({cwd, dry, path, version}) => { 1. Open {yellow.bold ${standaloneFixturePath}} in the browser. 2. It should say {italic "Hello world!"} 3. Next go to {yellow.bold ${packagingFixturesPath}} 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 serve -s .} - 6. Open {blue.bold http://localhost:5000/fixtures/packaging} + 4. Install the "pushstate-server" module ({bold npm install -g pushstate-server}) + 5. Go to the repo root and {bold pushstate-server -s .} + 6. Open {blue.bold http://localhost:9000/fixtures/packaging} 7. Verify every iframe shows {italic "Hello world!"} After completing the above steps, resume the release process by running: diff --git a/scripts/release/build.js b/scripts/release/build.js index 9288af14bf27d..f69a2c3d95703 100755 --- a/scripts/release/build.js +++ b/scripts/release/build.js @@ -6,14 +6,12 @@ const {exec} = require('child_process'); // Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { - const chalk = require('chalk'); - const logUpdate = require('log-update'); - const {getPublicPackages, getPackages} = require('./utils'); + const {getPublicPackages, getPackages, handleError} = require('./utils'); const addGitTag = require('./build-commands/add-git-tag'); 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 checkEnvironmentVariables = require('./shared-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'); @@ -55,18 +53,7 @@ const run = async () => { await addGitTag(params); await printPostBuildSummary(params); } catch (error) { - logUpdate.clear(); - - 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); + handleError(error); } }; diff --git a/scripts/release/prepare-canary-commands/download-build-artifacts.js b/scripts/release/prepare-canary-commands/download-build-artifacts.js new file mode 100644 index 0000000000000..e047b59732573 --- /dev/null +++ b/scripts/release/prepare-canary-commands/download-build-artifacts.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const http = require('request-promise-json'); +const {exec} = require('child-process-promise'); +const {readdirSync} = require('fs'); +const {readJsonSync} = require('fs-extra'); +const {logPromise} = require('../utils'); + +const run = async ({build, cwd}) => { + // https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci + const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${build}/artifacts?circle-token=${ + process.env.CIRCLE_CI_API_TOKEN + }`; + const metadata = await http.get(metadataURL, true); + const nodeModulesURL = metadata.find( + entry => entry.path === 'home/circleci/project/node_modules.tgz' + ).url; + + // Download and extract artifact + await exec(`rm -rf ${cwd}/build/node_modules*`); + await exec(`curl ${nodeModulesURL} --output ${cwd}/build/node_modules.tgz`); + await exec(`mkdir ${cwd}/build/node_modules`); + await exec( + `tar zxvf ${cwd}/build/node_modules.tgz -C ${cwd}/build/node_modules/` + ); + + // Unpack packages and parepare to publish + const compressedPackages = readdirSync('build/node_modules/'); + for (let i = 0; i < compressedPackages.length; i++) { + await exec( + `tar zxvf ${cwd}/build/node_modules/${ + compressedPackages[i] + } -C ${cwd}/build/node_modules/` + ); + const packageJSON = readJsonSync( + `${cwd}/build/node_modules/package/package.json` + ); + await exec( + `mv ${cwd}/build/node_modules/package ${cwd}/build/node_modules/${ + packageJSON.name + }` + ); + } + + // Cleanup + await exec(`rm ${cwd}/build/node_modules.tgz`); + await exec(`rm ${cwd}/build/node_modules/*.tgz`); +}; + +module.exports = async ({build, cwd}) => { + return logPromise( + run({build, cwd}), + `Downloading artifacts from Circle CI for build ${chalk.yellow.bold( + `${build}` + )}` + ); +}; diff --git a/scripts/release/prepare-canary-commands/parse-params.js b/scripts/release/prepare-canary-commands/parse-params.js new file mode 100644 index 0000000000000..1e27fe95dd372 --- /dev/null +++ b/scripts/release/prepare-canary-commands/parse-params.js @@ -0,0 +1,65 @@ +#!/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 = [ + { + name: 'build', + type: Number, + description: + 'Circle CI build identifier (e.g. https://circleci.com/gh/facebook/react/)', + defaultValue: false, + }, + { + name: 'path', + type: String, + alias: 'p', + description: + 'Location of React repository to release; defaults to [bold]{cwd}', + defaultValue: '.', + }, +]; + +module.exports = () => { + const params = commandLineArgs(paramDefinitions); + + if (!params.build) { + const usage = commandLineUsage([ + { + content: chalk + .hex('#61dafb') + .bold(figlet.textSync('react', {font: 'Graffiti'})), + raw: true, + }, + { + content: + 'Prepare a Circle CI build to be published to NPM as a canary.', + }, + { + header: 'Options', + optionList: paramDefinitions, + }, + { + header: 'Examples', + content: [ + { + desc: 'Example:', + example: '$ ./prepare-canary.js [bold]{--build=}[underline]{12639}', + }, + ], + }, + ]); + console.log(usage); + process.exit(1); + } + + return { + ...params, + cwd: params.path, // For script convenience + }; +}; diff --git a/scripts/release/prepare-canary-commands/print-summary.js b/scripts/release/prepare-canary-commands/print-summary.js new file mode 100644 index 0000000000000..950f2e1a300da --- /dev/null +++ b/scripts/release/prepare-canary-commands/print-summary.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {join, relative} = require('path'); + +module.exports = ({cwd, build, path}) => { + const publishPath = relative( + process.env.PWD, + join(__dirname, '../publish.js') + ); + const command = `${publishPath}` + (path ? ` -p ${path}` : ''); + + const packagingFixturesPath = join(cwd, 'fixtures/packaging'); + const standaloneFixturePath = join( + cwd, + 'fixtures/packaging/babel-standalone/dev.html' + ); + + console.log( + chalk` + {green.bold A potential canary has been prepared!} + Next there are a couple of manual steps: + + {bold.underline Smoke test the packages} + + 1. Open {yellow.bold ${standaloneFixturePath}} in the browser. + 2. It should say {italic "Hello world!"} + 3. Next go to {yellow.bold ${packagingFixturesPath}} and run {bold node build-all.js} + 4. Install the "pushstate-server" module ({bold npm install -g pushstate-server}) + 5. Go to the repo root and {bold pushstate-server -s .} + 6. Open {blue.bold http://localhost:9000/fixtures/packaging} + 7. Verify every iframe shows {italic "Hello world!"} + + After completing the above steps, you can publish this canary by running: + {yellow.bold ${command}} + `.replace(/\n +/g, '\n') + ); +}; diff --git a/scripts/release/prepare-canary.js b/scripts/release/prepare-canary.js new file mode 100755 index 0000000000000..e078021bd5252 --- /dev/null +++ b/scripts/release/prepare-canary.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +'use strict'; + +const {handleError} = require('./utils'); + +const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); +const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts'); +const parseParams = require('./prepare-canary-commands/parse-params'); +const printSummary = require('./prepare-canary-commands/print-summary'); + +const run = async () => { + try { + const params = parseParams(); + + await checkEnvironmentVariables(params); + await downloadBuildArtifacts(params); + await printSummary(params); + } catch (error) { + handleError(error); + } +}; + +run(); diff --git a/scripts/release/publish.js b/scripts/release/publish.js index c3d980b0f248c..4f242041488f8 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -2,9 +2,7 @@ 'use strict'; -const chalk = require('chalk'); -const logUpdate = require('log-update'); -const {getPublicPackages} = require('./utils'); +const {getPublicPackages, handleError} = require('./utils'); const checkBuildStatus = require('./publish-commands/check-build-status'); const commitChangelog = require('./publish-commands/commit-changelog'); @@ -27,18 +25,7 @@ const run = async () => { await publishToNpm(params); await printPostPublishSummary(params); } catch (error) { - logUpdate.clear(); - - 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); + handleError(error); } }; diff --git a/scripts/release/build-commands/check-environment-variables.js b/scripts/release/shared-commands/check-environment-variables.js similarity index 100% rename from scripts/release/build-commands/check-environment-variables.js rename to scripts/release/shared-commands/check-environment-variables.js diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 2a9ecff5e895c..6f3f5500253bd 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -67,6 +67,21 @@ const getUnexecutedCommands = () => { } }; +const handleError = error => { + logUpdate.clear(); + + 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); +}; + const logPromise = async (promise, text, isLongRunningTask = false) => { const {frames, interval} = dots; @@ -119,6 +134,7 @@ module.exports = { getPackages, getPublicPackages, getUnexecutedCommands, + handleError, logPromise, runYarnTask, }; From 0240640e1ae410ab3c70a5c398f50c39dc64a5d3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 15 Nov 2018 12:52:57 -0800 Subject: [PATCH 02/22] New create-canary script Also updated Circle CI build script to generate and add a build-info.json file to artifacts --- packages/create-subscription/package.json | 1 + .../eslint-plugin-react-hooks/package.json | 1 + packages/jest-react/package.json | 1 + packages/react-art/package.json | 1 + packages/react-cache/package.json | 1 + packages/react-debug-tools/package.json | 1 + packages/react-dom/package.json | 1 + packages/react-is/package.json | 1 + packages/react-noop-renderer/package.json | 1 + packages/react-reconciler/package.json | 1 + packages/react-test-renderer/package.json | 1 + packages/react/package.json | 1 + packages/scheduler/package.json | 1 + scripts/circleci/add_build_info_json.sh | 5 + scripts/circleci/test_entry_point.sh | 2 + scripts/circleci/update_package_versions.sh | 5 + .../release/build-commands/build-artifacts.js | 32 ------- scripts/release/build.js | 2 +- scripts/release/ci-add-build-info-json.js | 48 ++++++++++ scripts/release/ci-update-package-versions.js | 27 ++++++ .../add-build-info-json.js | 35 +++++++ .../create-build-commands/build-artifacts.js | 25 +++++ .../copy-repo-to-temp-directory.js | 34 +++++++ .../npm-pack-and-unpack.js | 54 +++++++++++ .../update-version-numbers.js | 13 +++ scripts/release/create-canary.js | 40 ++++++++ scripts/release/package.json | 1 + scripts/release/prepare-canary.js | 4 +- .../print-canary-summary.js} | 0 scripts/release/utils.js | 96 ++++++++++++++++++- scripts/release/yarn.lock | 51 ++++++++++ 31 files changed, 448 insertions(+), 39 deletions(-) create mode 100755 scripts/circleci/add_build_info_json.sh create mode 100755 scripts/circleci/update_package_versions.sh delete mode 100644 scripts/release/build-commands/build-artifacts.js create mode 100755 scripts/release/ci-add-build-info-json.js create mode 100755 scripts/release/ci-update-package-versions.js create mode 100644 scripts/release/create-build-commands/add-build-info-json.js create mode 100644 scripts/release/create-build-commands/build-artifacts.js create mode 100644 scripts/release/create-build-commands/copy-repo-to-temp-directory.js create mode 100644 scripts/release/create-build-commands/npm-pack-and-unpack.js create mode 100644 scripts/release/create-build-commands/update-version-numbers.js create mode 100755 scripts/release/create-canary.js rename scripts/release/{prepare-canary-commands/print-summary.js => shared-commands/print-canary-summary.js} (100%) diff --git a/packages/create-subscription/package.json b/packages/create-subscription/package.json index b28cd18dd8397..31f4c2b7d8046 100644 --- a/packages/create-subscription/package.json +++ b/packages/create-subscription/package.json @@ -6,6 +6,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/" ], diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 598758798faa8..efac100e7edbf 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -7,6 +7,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs" ], diff --git a/packages/jest-react/package.json b/packages/jest-react/package.json index dc5cf76d0d7b9..d0604366855cd 100644 --- a/packages/jest-react/package.json +++ b/packages/jest-react/package.json @@ -22,6 +22,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/" ] diff --git a/packages/react-art/package.json b/packages/react-art/package.json index 02a608546335c..32db7ca763780 100644 --- a/packages/react-art/package.json +++ b/packages/react-art/package.json @@ -31,6 +31,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/", "umd/", diff --git a/packages/react-cache/package.json b/packages/react-cache/package.json index b608fb3128694..d31f140c42c0d 100644 --- a/packages/react-cache/package.json +++ b/packages/react-cache/package.json @@ -7,6 +7,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/", "umd/" diff --git a/packages/react-debug-tools/package.json b/packages/react-debug-tools/package.json index ea013d1aba2bd..405705dde59d5 100644 --- a/packages/react-debug-tools/package.json +++ b/packages/react-debug-tools/package.json @@ -12,6 +12,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/" ], diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 0900718323f57..d92c95f7d3bc2 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -24,6 +24,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "profiling.js", "server.js", diff --git a/packages/react-is/package.json b/packages/react-is/package.json index 5ff1d68029541..70db5a95add56 100644 --- a/packages/react-is/package.json +++ b/packages/react-is/package.json @@ -15,6 +15,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/", "umd/" diff --git a/packages/react-noop-renderer/package.json b/packages/react-noop-renderer/package.json index 22f5d5d8f9ed9..51b668a366b76 100644 --- a/packages/react-noop-renderer/package.json +++ b/packages/react-noop-renderer/package.json @@ -18,6 +18,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "persistent.js", "cjs/" diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index 1d4bbe9416be0..906a58ed40006 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -11,6 +11,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "persistent.js", "reflection.js", diff --git a/packages/react-test-renderer/package.json b/packages/react-test-renderer/package.json index 2525b17a43097..6012653435e84 100644 --- a/packages/react-test-renderer/package.json +++ b/packages/react-test-renderer/package.json @@ -26,6 +26,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "shallow.js", "cjs/", diff --git a/packages/react/package.json b/packages/react/package.json index 7c5990406298f..623b1d1808133 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -11,6 +11,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "cjs/", "umd/" diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index 380a3c8e92a6f..cbba2e887964e 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -19,6 +19,7 @@ "files": [ "LICENSE", "README.md", + "build-info.json", "index.js", "tracing.js", "tracing-profiling.js", diff --git a/scripts/circleci/add_build_info_json.sh b/scripts/circleci/add_build_info_json.sh new file mode 100755 index 0000000000000..122d859535b4a --- /dev/null +++ b/scripts/circleci/add_build_info_json.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +node ./scripts/release/ci-add-build-info-json.js diff --git a/scripts/circleci/test_entry_point.sh b/scripts/circleci/test_entry_point.sh index c452eebc00c45..5b46ec45d3b6e 100755 --- a/scripts/circleci/test_entry_point.sh +++ b/scripts/circleci/test_entry_point.sh @@ -25,6 +25,8 @@ if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then fi if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then + COMMANDS_TO_RUN+=('./scripts/circleci/add_build_info_json.sh') + COMMANDS_TO_RUN+=('./scripts/circleci/update_package_versions.sh') COMMANDS_TO_RUN+=('./scripts/circleci/build.sh') COMMANDS_TO_RUN+=('yarn test-build --maxWorkers=2') COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2') diff --git a/scripts/circleci/update_package_versions.sh b/scripts/circleci/update_package_versions.sh new file mode 100755 index 0000000000000..3661e80cdef77 --- /dev/null +++ b/scripts/circleci/update_package_versions.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +node ./scripts/release/ci-update-package-versions.js diff --git a/scripts/release/build-commands/build-artifacts.js b/scripts/release/build-commands/build-artifacts.js deleted file mode 100644 index 8687c90d40113..0000000000000 --- a/scripts/release/build-commands/build-artifacts.js +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {exec} = require('child-process-promise'); -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 (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 execUnlessDry('git add scripts/rollup/results.json', {cwd, dry}); - await execUnlessDry( - `git commit -m "Update bundle sizes for ${version} release"`, - {cwd, dry} - ); - } -}; - -module.exports = async params => { - return logPromise(run(params), 'Building artifacts', true); -}; diff --git a/scripts/release/build.js b/scripts/release/build.js index f69a2c3d95703..721770778a218 100755 --- a/scripts/release/build.js +++ b/scripts/release/build.js @@ -9,7 +9,7 @@ const run = async () => { const {getPublicPackages, getPackages, handleError} = require('./utils'); const addGitTag = require('./build-commands/add-git-tag'); - const buildArtifacts = require('./build-commands/build-artifacts'); + const buildArtifacts = require('./create-release-commands/build-artifacts'); const checkCircleCiStatus = require('./build-commands/check-circle-ci-status'); const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); const checkNpmPermissions = require('./build-commands/check-npm-permissions'); diff --git a/scripts/release/ci-add-build-info-json.js b/scripts/release/ci-add-build-info-json.js new file mode 100755 index 0000000000000..53444d2ecba1d --- /dev/null +++ b/scripts/release/ci-add-build-info-json.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child_process'); +const {existsSync} = require('fs'); +const {join} = require('path'); + +const run = async () => { + const {writeJson} = require('fs-extra'); + const {getBuildInfo, getPackages} = require('./utils'); + + const cwd = join(__dirname, '..', '..'); + + const {checksum, commit, branch} = await getBuildInfo(); + + const packages = getPackages(join(cwd, 'packages')); + const packagesDir = join(cwd, 'packages'); + + const buildInfoJSON = { + branch, + checksum, + commit, + environment: 'ci', + }; + + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packagePath = join(packagesDir, packageName); + + // Add build info JSON to package + if (existsSync(join(packagePath, 'npm'))) { + const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json'); + await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2}); + } + } +}; + +// Install (or update) release script dependencies before proceeding. +// This needs to be done before we require() the first NPM module. +exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => { + if (error) { + console.error(error); + process.exit(1); + } else { + run(); + } +}); diff --git a/scripts/release/ci-update-package-versions.js b/scripts/release/ci-update-package-versions.js new file mode 100755 index 0000000000000..b4441f3f39c33 --- /dev/null +++ b/scripts/release/ci-update-package-versions.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child_process'); +const {join} = require('path'); + +const run = async () => { + const {getBuildInfo, updateVersionsForCanary} = require('./utils'); + + const cwd = join(__dirname, '..', '..'); + + const {version} = await getBuildInfo(); + + await updateVersionsForCanary(cwd, version); +}; + +// Install (or update) release script dependencies before proceeding. +// This needs to be done before we require() the first NPM module. +exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => { + if (error) { + console.error(error); + process.exit(1); + } else { + run(); + } +}); diff --git a/scripts/release/create-build-commands/add-build-info-json.js b/scripts/release/create-build-commands/add-build-info-json.js new file mode 100644 index 0000000000000..816b2a146eb18 --- /dev/null +++ b/scripts/release/create-build-commands/add-build-info-json.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +'use strict'; + +const {existsSync} = require('fs'); +const {writeJson} = require('fs-extra'); +const {join} = require('path'); +const {getPackages, logPromise} = require('../utils'); + +const run = async ({branch, checksum, commit, tempDirectory}) => { + const packages = getPackages(join(tempDirectory, 'packages')); + const packagesDir = join(tempDirectory, 'packages'); + + const buildInfoJSON = { + branch, + checksum, + commit, + environment: 'local', + }; + + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packagePath = join(packagesDir, packageName); + + // Add build info JSON to package + if (existsSync(join(packagePath, 'npm'))) { + const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json'); + await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2}); + } + } +}; + +module.exports = async params => { + return logPromise(run(params), 'Adding build metadata to packages'); +}; diff --git a/scripts/release/create-build-commands/build-artifacts.js b/scripts/release/create-build-commands/build-artifacts.js new file mode 100644 index 0000000000000..3435a625b584d --- /dev/null +++ b/scripts/release/create-build-commands/build-artifacts.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child-process-promise'); +const {join} = require('path'); +const {logPromise} = require('../utils'); + +const run = async ({cwd, dry, tempDirectory}) => { + const defaultOptions = { + cwd: tempDirectory, + }; + + await exec('yarn install', defaultOptions); + await exec('yarn build -- --extract-errors', defaultOptions); + + const tempNodeModulesPath = join(tempDirectory, 'build', 'node_modules'); + const buildPath = join(cwd, 'build'); + + await exec(`cp -r ${tempNodeModulesPath} ${buildPath}`); +}; + +module.exports = async params => { + return logPromise(run(params), 'Building artifacts', true); +}; diff --git a/scripts/release/create-build-commands/copy-repo-to-temp-directory.js b/scripts/release/create-build-commands/copy-repo-to-temp-directory.js new file mode 100644 index 0000000000000..0490efbe67d17 --- /dev/null +++ b/scripts/release/create-build-commands/copy-repo-to-temp-directory.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {join} = require('path'); +const {tmpdir} = require('os'); +const {logPromise} = require('../utils'); + +const run = async ({commit, cwd, tempDirectory}) => { + const directory = `react-${commit}`; + const temp = tmpdir(); + + if (tempDirectory !== join(tmpdir(), directory)) { + throw Error(`Unexpected temporary directory "${tempDirectory}"`); + } + + await exec(`rm -rf ${directory}`, {cwd: temp}); + await exec(`git archive --format=tar --output=${temp}/react.tgz ${commit}`, { + cwd, + }); + await exec(`mkdir ${directory}`, {cwd: temp}); + await exec(`tar -xf ./react.tgz -C ./${directory}`, {cwd: temp}); +}; + +module.exports = async params => { + return logPromise( + run(params), + `Copying React repo to temporary directory (${chalk.gray( + params.tempDirectory + )})` + ); +}; diff --git a/scripts/release/create-build-commands/npm-pack-and-unpack.js b/scripts/release/create-build-commands/npm-pack-and-unpack.js new file mode 100644 index 0000000000000..76f3cf85cf882 --- /dev/null +++ b/scripts/release/create-build-commands/npm-pack-and-unpack.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +'use strict'; + +const {join} = require('path'); +const {exec} = require('child-process-promise'); +const {readdirSync} = require('fs'); +const {readJsonSync} = require('fs-extra'); +const {logPromise} = require('../utils'); + +const run = async ({cwd, dry, tempDirectory}) => { + // Cleanup from previous build. + await exec(`rm -rf ${cwd}/build`); + + // NPM pack all built packages. + // We do this to ensure that the package.json files array is correct. + const builtPackages = readdirSync(join(tempDirectory, 'build/node_modules/')); + for (let i = 0; i < builtPackages.length; i++) { + await exec(`npm pack ./${builtPackages[i]}`, { + cwd: `${tempDirectory}/build/node_modules/`, + }); + } + + await exec('mkdir build'); + await exec('mkdir build/node_modules'); + await exec( + `cp -r ${tempDirectory}/build/node_modules/*.tgz ${cwd}/build/node_modules/` + ); + + // Unpack packages and parepare to publish. + const compressedPackages = readdirSync('build/node_modules/'); + for (let i = 0; i < compressedPackages.length; i++) { + await exec( + `tar -zxvf ${cwd}/build/node_modules/${ + compressedPackages[i] + } -C ${cwd}/build/node_modules/` + ); + const packageJSON = readJsonSync( + `${cwd}/build/node_modules/package/package.json` + ); + await exec( + `mv ${cwd}/build/node_modules/package ${cwd}/build/node_modules/${ + packageJSON.name + }` + ); + } + + // Cleanup. + await exec(`rm ${cwd}/build/node_modules/*.tgz`); +}; + +module.exports = async params => { + return logPromise(run(params), 'Packing artifacts'); +}; diff --git a/scripts/release/create-build-commands/update-version-numbers.js b/scripts/release/create-build-commands/update-version-numbers.js new file mode 100644 index 0000000000000..f0adc9ae77237 --- /dev/null +++ b/scripts/release/create-build-commands/update-version-numbers.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {logPromise, updateVersionsForCanary} = require('../utils'); + +module.exports = async ({tempDirectory, version}) => { + return logPromise( + updateVersionsForCanary(tempDirectory, version), + `Updating version numbers (${chalk.yellow.bold(version)})` + ); +}; diff --git a/scripts/release/create-canary.js b/scripts/release/create-canary.js new file mode 100755 index 0000000000000..0c9652d84fa3e --- /dev/null +++ b/scripts/release/create-canary.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +'use strict'; + +const {tmpdir} = require('os'); +const {join} = require('path'); +const {getBuildInfo, handleError} = require('./utils'); + +// This local build script exists for special case, manual builds. +// The typical suggesgted release process is to create a canary from a CI artifact. +// This build script is optimized for speed and simplicity. +// It doesn't run all of the tests that the CI environment runs. +// You're expected to run those manually before publishing a release. + +const addBuildInfoJSON = require('./create-build-commands/add-build-info-json'); +const buildArtifacts = require('./create-build-commands/build-artifacts'); +const copyRepoToTempDirectory = require('./create-build-commands/copy-repo-to-temp-directory'); +const npmPackAndUnpack = require('./create-build-commands/npm-pack-and-unpack'); +const printCanarySummary = require('./shared-commands/print-canary-summary'); +const updateVersionNumbers = require('./create-build-commands/update-version-numbers'); + +const run = async () => { + try { + const cwd = join(__dirname, '..', '..'); + const {branch, checksum, commit, version} = await getBuildInfo(); + const tempDirectory = join(tmpdir(), `react-${commit}`); + const params = {branch, checksum, commit, cwd, tempDirectory, version}; + + await copyRepoToTempDirectory(params); + await updateVersionNumbers(params); + await addBuildInfoJSON(params); + await buildArtifacts(params); + await npmPackAndUnpack(params); + await printCanarySummary(params); + } catch (error) { + handleError(error); + } +}; + +run(); diff --git a/scripts/release/package.json b/scripts/release/package.json index c5f9f99af3207..21e2d3b2ddc56 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -11,6 +11,7 @@ "command-line-args": "^4.0.7", "command-line-usage": "^4.0.1", "figlet": "^1.2.0", + "folder-hash": "^2.1.2", "fs-extra": "^4.0.2", "log-update": "^2.1.0", "prompt-promise": "^1.0.3", diff --git a/scripts/release/prepare-canary.js b/scripts/release/prepare-canary.js index e078021bd5252..2a459722d360b 100755 --- a/scripts/release/prepare-canary.js +++ b/scripts/release/prepare-canary.js @@ -7,7 +7,7 @@ const {handleError} = require('./utils'); const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts'); const parseParams = require('./prepare-canary-commands/parse-params'); -const printSummary = require('./prepare-canary-commands/print-summary'); +const printCanarySummary = require('./shared-commands/print-canary-summary'); const run = async () => { try { @@ -15,7 +15,7 @@ const run = async () => { await checkEnvironmentVariables(params); await downloadBuildArtifacts(params); - await printSummary(params); + await printCanarySummary(params); } catch (error) { handleError(error); } diff --git a/scripts/release/prepare-canary-commands/print-summary.js b/scripts/release/shared-commands/print-canary-summary.js similarity index 100% rename from scripts/release/prepare-canary-commands/print-summary.js rename to scripts/release/shared-commands/print-canary-summary.js diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 6f3f5500253bd..c909c8679b818 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -2,8 +2,10 @@ const chalk = require('chalk'); const {dots} = require('cli-spinners'); -const {exec} = require('child-process-promise'); -const {readdirSync, readFileSync, statSync} = require('fs'); +const {exec, spawn} = require('child-process-promise'); +const {hashElement} = require('folder-hash'); +const {readdirSync, readFileSync, statSync, writeFileSync} = require('fs'); +const {readJson, writeJson} = require('fs-extra'); const logUpdate = require('log-update'); const {join} = require('path'); @@ -23,9 +25,31 @@ const execUnlessDry = async (command, {cwd, dry}) => { } }; -const getPackages = () => { - const packagesRoot = join(__dirname, '..', '..', 'packages'); +const getBuildInfo = async () => { + const cwd = join(__dirname, '..', '..'); + + const branch = await execRead('git branch | grep \\* | cut -d " " -f2', { + cwd, + }); + const commit = await execRead('git show -s --format=%h', {cwd}); + const checksum = await getChecksumForCurrentRevision(cwd); + const version = `0.0.0-${commit}`; + + return {branch, checksum, commit, version}; +}; +const getChecksumForCurrentRevision = async cwd => { + const packagesDir = join(cwd, 'packages'); + const hashedPackages = await hashElement(packagesDir, { + encoding: 'hex', + files: {exclude: ['.DS_Store']}, + }); + return hashedPackages.hash.slice(0, 7); +}; + +const getPackages = ( + packagesRoot = join(__dirname, '..', '..', 'packages') +) => { return readdirSync(packagesRoot).filter(dir => { const packagePath = join(packagesRoot, dir, 'package.json'); @@ -128,13 +152,77 @@ const runYarnTask = async (cwd, task, errorMessage) => { } }; +const spawnCommand = (command, options) => + spawn(command, { + cwd: join(__dirname, '..', '..'), + encoding: 'utf-8', + env: process.env, + shell: true, + stdio: [process.stdin, process.stdout, process.stderr], + ...options, + }); + +const updateVersionsForCanary = async (cwd, version) => { + const packages = getPackages(join(cwd, 'packages')); + const packagesDir = join(cwd, 'packages'); + + // Update the shared React version source file. + // This is bundled into built renderers. + // The promote script will replace this with a final version later. + const reactVersionPath = join(cwd, 'packages/shared/ReactVersion.js'); + const reactVersion = readFileSync(reactVersionPath, 'utf8').replace( + /module\.exports = '[^']+';/, + `module.exports = '${version}';` + ); + writeFileSync(reactVersionPath, reactVersion); + + // Update the root package.json. + // This is required to pass a later version check script. + { + const packageJSONPath = join(cwd, 'package.json'); + const packageJSON = await readJson(packageJSONPath); + packageJSON.version = version; + await writeJson(packageJSONPath, packageJSON, {spaces: 2}); + } + + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packagePath = join(packagesDir, packageName); + + // Update version numbers in package JSONs + const packageJSONPath = join(packagePath, 'package.json'); + const packageJSON = await readJson(packageJSONPath); + packageJSON.version = version; + + // Also update inter-package dependencies. + // Canary releases always have exact version matches. + // The promote script may later relax these (e.g. "^x.x.x") based on source package JSONs. + const {dependencies, peerDependencies} = packageJSON; + for (let j = 0; j < packages.length; j++) { + const dependencyName = packages[j]; + if (dependencies && dependencies[dependencyName]) { + dependencies[dependencyName] = version; + } + if (peerDependencies && peerDependencies[dependencyName]) { + peerDependencies[dependencyName] = version; + } + } + + await writeJson(packageJSONPath, packageJSON, {spaces: 2}); + } +}; + module.exports = { execRead, execUnlessDry, + getBuildInfo, + getChecksumForCurrentRevision, getPackages, getPublicPackages, getUnexecutedCommands, handleError, logPromise, runYarnTask, + spawnCommand, + updateVersionsForCanary, }; diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index 44ea4e5703a2f..240210db6c7e4 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -63,6 +63,11 @@ aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + bcrypt-pbkdf@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" @@ -81,6 +86,14 @@ boom@5.x.x: dependencies: hoek "4.x.x" +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -148,6 +161,11 @@ command-line-usage@^4.0.1: table-layout "^0.4.1" typical "^2.6.1" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + 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" @@ -171,6 +189,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + deep-extend@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803" @@ -212,6 +237,15 @@ find-replace@^1.0.3: array-back "^1.0.4" test-value "^2.1.0" +folder-hash@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/folder-hash/-/folder-hash-2.1.2.tgz#7109f9cd0cbca271936d1b5544b156d6571e6cfd" + integrity sha512-PmMwEZyNN96EMshf7sek4OIB7ADNsHOJ7VIw7pO0PBI0BNfEsi7U8U56TBjjqqwQ0WuBv8se0HEfmbw5b/Rk+w== + dependencies: + debug "^3.1.0" + graceful-fs "~4.1.11" + minimatch "~3.0.4" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -242,6 +276,11 @@ 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" +graceful-fs@~4.1.11: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -372,6 +411,18 @@ mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" +minimatch@~3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + native-or-another@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/native-or-another/-/native-or-another-2.0.0.tgz#17a567f92beea9cd71acff96a7681a735eca3bff" From c197be7cce468558740dc5cb2213bfbf95e24c57 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 16 Nov 2018 10:37:10 -0800 Subject: [PATCH 03/22] New prepare-canary release script --- scripts/release/ci-add-build-info-json.js | 4 + scripts/release/ci-update-package-versions.js | 4 + scripts/release/create-canary.js | 4 +- scripts/release/package.json | 1 + scripts/release/prepare-canary.js | 4 +- .../check-out-packages.js | 29 ++++ .../confirm-stable-version-numbers.js | 56 +++++++ .../guess-stable-version-numbers.js | 34 +++++ .../prepare-stable-commands/parse-params.js | 52 +++++++ .../update-stable-version-numbers.js | 144 ++++++++++++++++++ scripts/release/prepare-stable.js | 36 +++++ ...summary.js => print-prerelease-summary.js} | 14 +- scripts/release/utils.js | 11 ++ scripts/release/yarn.lock | 12 ++ 14 files changed, 394 insertions(+), 11 deletions(-) create mode 100644 scripts/release/prepare-stable-commands/check-out-packages.js create mode 100644 scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js create mode 100644 scripts/release/prepare-stable-commands/guess-stable-version-numbers.js create mode 100644 scripts/release/prepare-stable-commands/parse-params.js create mode 100644 scripts/release/prepare-stable-commands/update-stable-version-numbers.js create mode 100755 scripts/release/prepare-stable.js rename scripts/release/shared-commands/{print-canary-summary.js => print-prerelease-summary.js} (71%) diff --git a/scripts/release/ci-add-build-info-json.js b/scripts/release/ci-add-build-info-json.js index 53444d2ecba1d..a2f30fc4492f7 100755 --- a/scripts/release/ci-add-build-info-json.js +++ b/scripts/release/ci-add-build-info-json.js @@ -2,6 +2,10 @@ 'use strict'; +// This script is run by Circle CI (see ../scripts/circleci). +// It is not meant to be run as part of the local build or publish process. +// It exists to share code between the Node release scripts and CI bash scripts. + const {exec} = require('child_process'); const {existsSync} = require('fs'); const {join} = require('path'); diff --git a/scripts/release/ci-update-package-versions.js b/scripts/release/ci-update-package-versions.js index b4441f3f39c33..0d5e65ec04a7e 100755 --- a/scripts/release/ci-update-package-versions.js +++ b/scripts/release/ci-update-package-versions.js @@ -2,6 +2,10 @@ 'use strict'; +// This script is run by Circle CI (see ../scripts/circleci). +// It is not meant to be run as part of the local build or publish process. +// It exists to share code between the Node release scripts and CI bash scripts. + const {exec} = require('child_process'); const {join} = require('path'); diff --git a/scripts/release/create-canary.js b/scripts/release/create-canary.js index 0c9652d84fa3e..3a3584276e853 100755 --- a/scripts/release/create-canary.js +++ b/scripts/release/create-canary.js @@ -16,7 +16,7 @@ const addBuildInfoJSON = require('./create-build-commands/add-build-info-json'); const buildArtifacts = require('./create-build-commands/build-artifacts'); const copyRepoToTempDirectory = require('./create-build-commands/copy-repo-to-temp-directory'); const npmPackAndUnpack = require('./create-build-commands/npm-pack-and-unpack'); -const printCanarySummary = require('./shared-commands/print-canary-summary'); +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); const updateVersionNumbers = require('./create-build-commands/update-version-numbers'); const run = async () => { @@ -31,7 +31,7 @@ const run = async () => { await addBuildInfoJSON(params); await buildArtifacts(params); await npmPackAndUnpack(params); - await printCanarySummary(params); + await printPrereleaseSummary(params); } catch (error) { handleError(error); } diff --git a/scripts/release/package.json b/scripts/release/package.json index 21e2d3b2ddc56..dd40d3cfdbedc 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -14,6 +14,7 @@ "folder-hash": "^2.1.2", "fs-extra": "^4.0.2", "log-update": "^2.1.0", + "print-diff": "^0.1.1", "prompt-promise": "^1.0.3", "request-promise-json": "^1.0.4", "semver": "^5.4.1" diff --git a/scripts/release/prepare-canary.js b/scripts/release/prepare-canary.js index 2a459722d360b..5302bd95f251f 100755 --- a/scripts/release/prepare-canary.js +++ b/scripts/release/prepare-canary.js @@ -7,7 +7,7 @@ const {handleError} = require('./utils'); const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts'); const parseParams = require('./prepare-canary-commands/parse-params'); -const printCanarySummary = require('./shared-commands/print-canary-summary'); +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); const run = async () => { try { @@ -15,7 +15,7 @@ const run = async () => { await checkEnvironmentVariables(params); await downloadBuildArtifacts(params); - await printCanarySummary(params); + await printPrereleaseSummary(params); } catch (error) { handleError(error); } diff --git a/scripts/release/prepare-stable-commands/check-out-packages.js b/scripts/release/prepare-stable-commands/check-out-packages.js new file mode 100644 index 0000000000000..a49a37377fbcd --- /dev/null +++ b/scripts/release/prepare-stable-commands/check-out-packages.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {join} = require('path'); +const {logPromise} = require('../utils'); + +const run = async ({cwd, packages, version}) => { + // Cleanup from previous builds + await exec(`rm -rf ./build/node_modules*`, {cwd}); + await exec(`mkdir ./build/node_modules`, {cwd}); + + const nodeModulesPath = join(cwd, 'build/node_modules'); + + // Checkout canary release from NPM for all local packages + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + await exec(`npm i ${packageName}@${version}`, {cwd: nodeModulesPath}); + } +}; + +module.exports = async params => { + return logPromise( + run(params), + `Checking out canary from NPM ${chalk.yellow(params.version)}` + ); +}; diff --git a/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js b/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js new file mode 100644 index 0000000000000..a5b2e67c7684e --- /dev/null +++ b/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const prompt = require('prompt-promise'); +const semver = require('semver'); + +const run = async (params, versionsMap) => { + const groupedVersionsMap = new Map(); + + // Group packages with the same source versions. + // We want these to stay lock-synced anyway. + // This will require less redundant input from the user later, + // and reduce the likelihood of human error (entering the wrong version). + versionsMap.forEach((version, packageName) => { + if (!groupedVersionsMap.has(version)) { + groupedVersionsMap.set(version, [packageName]); + } else { + groupedVersionsMap.get(version).push(packageName); + } + }); + + // Prompt user to confirm or override each version group. + const entries = [...groupedVersionsMap.entries()]; + for (let i = 0; i < entries.length; i++) { + const [bestGuessVersion, packages] = entries[i]; + const packageNames = chalk.green(packages.join(', ')); + const defaultVersion = bestGuessVersion + ? chalk.yellow(` (default ${bestGuessVersion})`) + : ''; + const version = + (await prompt( + chalk`{green ✓} Version for ${packageNames}${defaultVersion}: ` + )) || bestGuessVersion; + prompt.done(); + + // Verify a valid version has been supplied. + try { + semver(version); + + packages.forEach(packageName => { + versionsMap.set(packageName, version); + }); + } catch (error) { + console.log(chalk`{red ✘ Version {white ${version}} is invalid.}`); + + // Prompt again + i--; + } + } +}; + +// Run this directly because it's fast, +// and logPromise would interfere with console prompting. +module.exports = run; diff --git a/scripts/release/prepare-stable-commands/guess-stable-version-numbers.js b/scripts/release/prepare-stable-commands/guess-stable-version-numbers.js new file mode 100644 index 0000000000000..11508489e4074 --- /dev/null +++ b/scripts/release/prepare-stable-commands/guess-stable-version-numbers.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +'use strict'; + +const semver = require('semver'); +const {execRead, logPromise} = require('../utils'); + +const run = async ({cwd, packages}, versionsMap) => { + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + + try { + // In case local package JSONs are outdated, + // guess the next version based on the latest NPM release. + const version = await execRead(`npm show ${packageName} version`); + const {major, minor, patch} = semver(version); + + // Guess the next version by incrementing patch. + // The script will confirm this later. + versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`); + } catch (error) { + // If the package has not yet been published, + // we'll require a version number to be entered later. + versionsMap.set(packageName, null); + } + } +}; + +module.exports = async (params, versionsMap) => { + return logPromise( + run(params, versionsMap), + 'Guessing stable version numbers' + ); +}; diff --git a/scripts/release/prepare-stable-commands/parse-params.js b/scripts/release/prepare-stable-commands/parse-params.js new file mode 100644 index 0000000000000..462b69bbae209 --- /dev/null +++ b/scripts/release/prepare-stable-commands/parse-params.js @@ -0,0 +1,52 @@ +#!/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 = [ + { + name: 'version', + type: String, + description: 'Version of published canary release (e.g. 0.0.0-ddaf2b07c)', + }, +]; + +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: 'Prepare a published canary release to be promoted to stable.', + }, + { + header: 'Options', + optionList: paramDefinitions, + }, + { + header: 'Examples', + content: [ + { + desc: 'Example:', + example: + '$ ./prepare-stable.js [bold]{--version=}[underline]{0.0.0-ddaf2b07c}', + }, + ], + }, + ]); + console.log(usage); + process.exit(1); + } + + return params; +}; diff --git a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js new file mode 100644 index 0000000000000..b088b504786ee --- /dev/null +++ b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js @@ -0,0 +1,144 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {readFileSync, writeFileSync} = require('fs'); +const {readJson, writeJson} = require('fs-extra'); +const {join} = require('path'); +const printDiff = require('print-diff'); +const {confirm, execRead} = require('../utils'); + +const run = async ({cwd, packages, version}, versionsMap) => { + const nodeModulesPath = join(cwd, 'build/node_modules'); + + // Cache all package JSONs for easy lookup below. + const sourcePackageJSONs = new Map(); + const targetPackageJSONs = new Map(); + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const sourcePackageJSON = await readJson( + join(cwd, 'packages', packageName, 'package.json') + ); + sourcePackageJSONs.set(packageName, sourcePackageJSON); + const targetPackageJSON = await readJson( + join(nodeModulesPath, packageName, 'package.json') + ); + targetPackageJSONs.set(packageName, targetPackageJSON); + } + + const updateDependencies = async (targetPackageJSON, key) => { + const targetDependencies = targetPackageJSON[key]; + if (targetDependencies) { + const sourceDependencies = sourcePackageJSONs.get(targetPackageJSON.name)[ + key + ]; + + for (let i = 0; i < packages.length; i++) { + const dependencyName = packages[i]; + const targetDependency = targetDependencies[dependencyName]; + + if (targetDependency) { + // For example, say we're updating react-dom's dependency on scheduler. + // We compare source packages to determine what the new scheduler dependency constraint should be. + // To do this, we look at both the local version of the scheduler (e.g. 0.11.0), + // and the dependency constraint in the local version of react-dom (e.g. scheduler@^0.11.0). + const sourceDependencyVersion = sourcePackageJSONs.get(dependencyName) + .version; + const sourceDependencyConstraint = sourceDependencies[dependencyName]; + + // If the source dependency's version and the constraint match, + // we will need to update the constraint to point at the dependency's new release version, + // (e.g. scheduler@^0.11.0 becomes scheduler@^0.12.0 when we release scheduler 0.12.0). + // Othewise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases). + // Note that in both cases, we must update the target package JSON, + // since canary releases are all locked to the canary version (e.g. 0.0.0-ddaf2b07c). + if ( + sourceDependencyVersion === + sourceDependencyConstraint.replace(/^[\^\~]/, '') + ) { + targetDependencies[ + dependencyName + ] = sourceDependencyConstraint.replace( + sourceDependencyVersion, + versionsMap.get(dependencyName) + ); + } else { + targetDependencies[dependencyName] = sourceDependencyConstraint; + } + } + } + } + }; + + // Update all package JSON versions and their dependencies/peerDependencies. + // This must be done in a way that respects semver constraints (e.g. 16.7.0, ^16.7.0, ^16.0.0). + // To do this, we use the dependencies defined in the source package JSONs, + // because the canary dependencies have already been falttened to an exact match (e.g. 0.0.0-ddaf2b07c). + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packageJSONPath = join(nodeModulesPath, packageName, 'package.json'); + const packageJSON = await readJson(packageJSONPath); + packageJSON.version = versionsMap.get(packageName); + + await updateDependencies(packageJSON, 'dependencies'); + await updateDependencies(packageJSON, 'peerDependencies'); + + await writeJson(packageJSONPath, packageJSON, {spaces: 2}); + } + + // Print the map of versions and their dependencies for confirmation. + const printDependencies = (maybeDependency, label) => { + if (maybeDependency) { + for (let dependencyName in maybeDependency) { + if (packages.includes(dependencyName)) { + console.log( + chalk`• {green ${dependencyName}} @ {yellow ${ + maybeDependency[dependencyName] + }} (${label})` + ); + } + } + } + }; + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packageJSONPath = join(nodeModulesPath, packageName, 'package.json'); + const packageJSON = await readJson(packageJSONPath); + console.log( + chalk`\n{green ${packageName}} @ {yellow ${chalk.yellow( + versionsMap.get(packageName) + )}}` + ); + printDependencies(packageJSON.dependencies, 'dependency'); + printDependencies(packageJSON.peerDependencies, 'peer'); + } + await confirm('Do the versions above look correct?'); + + // Find-and-replace hard coded version (in built JS) for renderers. + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packagePath = join(nodeModulesPath, packageName); + + let files = await execRead( + `find ${packagePath} -name '*.js' -exec echo {} \\;`, + {cwd} + ); + files = files.split('\n'); + files.forEach(path => { + const beforeContents = readFileSync(path, 'utf8', {cwd}); + const afterContents = beforeContents.replace( + new RegExp(version, 'g'), + versionsMap.get(packageName) + ); + if (beforeContents !== afterContents) { + printDiff(beforeContents, afterContents); + writeFileSync(path, afterContents, {cwd}); + } + }); + } + await confirm('Do the replacements above look correct?'); +}; + +// Run this directly because logPromise would interfere with printing package dependencies. +module.exports = run; diff --git a/scripts/release/prepare-stable.js b/scripts/release/prepare-stable.js new file mode 100755 index 0000000000000..733cb109e9bf5 --- /dev/null +++ b/scripts/release/prepare-stable.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +'use strict'; + +const {join} = require('path'); +const {getPublicPackages, handleError} = require('./utils'); + +const checkOutPackages = require('./prepare-stable-commands/check-out-packages'); +const confirmStableVersionNumbers = require('./prepare-stable-commands/confirm-stable-version-numbers'); +const guessStableVersionNumbers = require('./prepare-stable-commands/guess-stable-version-numbers'); +const parseParams = require('./prepare-stable-commands/parse-params'); +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); +const updateStableVersionNumbers = require('./prepare-stable-commands/update-stable-version-numbers'); + +const run = async () => { + try { + const params = parseParams(); + params.cwd = join(__dirname, '..', '..'); + params.packages = await getPublicPackages(); + + // Map of package name to upcoming stable version. + // This Map is initially populated with guesses based on local versions. + // The developer running the release later confirms or overrides each version. + const versionsMap = new Map(); + + await checkOutPackages(params); + await guessStableVersionNumbers(params, versionsMap); + await confirmStableVersionNumbers(params, versionsMap); + await updateStableVersionNumbers(params, versionsMap); + await printPrereleaseSummary(params); + } catch (error) { + handleError(error); + } +}; + +run(); diff --git a/scripts/release/shared-commands/print-canary-summary.js b/scripts/release/shared-commands/print-prerelease-summary.js similarity index 71% rename from scripts/release/shared-commands/print-canary-summary.js rename to scripts/release/shared-commands/print-prerelease-summary.js index 950f2e1a300da..b7a1691c5efb3 100644 --- a/scripts/release/shared-commands/print-canary-summary.js +++ b/scripts/release/shared-commands/print-prerelease-summary.js @@ -5,12 +5,11 @@ const chalk = require('chalk'); const {join, relative} = require('path'); -module.exports = ({cwd, build, path}) => { +module.exports = ({cwd}) => { const publishPath = relative( process.env.PWD, join(__dirname, '../publish.js') ); - const command = `${publishPath}` + (path ? ` -p ${path}` : ''); const packagingFixturesPath = join(cwd, 'fixtures/packaging'); const standaloneFixturePath = join( @@ -20,10 +19,11 @@ module.exports = ({cwd, build, path}) => { console.log( chalk` - {green.bold A potential canary has been prepared!} - Next there are a couple of manual steps: + {green.bold A potential release has been prepared!} - {bold.underline Smoke test the packages} + You can review the contents of this release in {yellow.bold ./build/node_modules/} + + {bold.underline Before publishing, please smoke test the packages} 1. Open {yellow.bold ${standaloneFixturePath}} in the browser. 2. It should say {italic "Hello world!"} @@ -33,8 +33,8 @@ module.exports = ({cwd, build, path}) => { 6. Open {blue.bold http://localhost:9000/fixtures/packaging} 7. Verify every iframe shows {italic "Hello world!"} - After completing the above steps, you can publish this canary by running: - {yellow.bold ${command}} + After completing the above steps, you can publish this release by running: + {yellow.bold ${publishPath}} `.replace(/\n +/g, '\n') ); }; diff --git a/scripts/release/utils.js b/scripts/release/utils.js index c909c8679b818..261df04764bb1 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -8,6 +8,16 @@ const {readdirSync, readFileSync, statSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); const logUpdate = require('log-update'); const {join} = require('path'); +const prompt = require('prompt-promise'); + +const confirm = async message => { + const confirmation = await prompt(chalk`\n${message} {yellow (y/N)} `); + prompt.done(); + if (confirmation !== 'y' && confirmation !== 'Y') { + console.log(chalk.red('Release cancelled.')); + process.exit(0); + } +}; const execRead = async (command, options) => { const {stdout} = await exec(command, options); @@ -213,6 +223,7 @@ const updateVersionsForCanary = async (cwd, version) => { }; module.exports = { + confirm, execRead, execUnlessDry, getBuildInfo, diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index 240210db6c7e4..f3eecb747cc1e 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -204,6 +204,11 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" +diff@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" + integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -451,6 +456,13 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" +print-diff@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/print-diff/-/print-diff-0.1.1.tgz#e32e59d89f753208629ff74d9a7430df4f3cc899" + integrity sha512-dp36GezMEivgKH/zcLB4eBhJmQM3ewAa1UAqEPXMzU69NQ5wCP8puVBZlDFyt1WEtR5k2UC+3ahg+T5BfmRVGw== + dependencies: + diff "^1.2.1" + promise-polyfill@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.0.2.tgz#d9c86d3dc4dc2df9016e88946defd69b49b41162" From 71c1046b2dfd0be5ff6cfab6a04d7c7c4c7be1f6 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 16 Nov 2018 11:44:01 -0800 Subject: [PATCH 04/22] Added release script README --- scripts/release/README.md | 63 ++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/scripts/release/README.md b/scripts/release/README.md index 0ea523e98b1f2..6cc5a666f885b 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -1,13 +1,62 @@ # React Release Scripts -At a high-level, the release process uses two scripts: **build** and **publish**. -1. The **build** script does the heavy lifting (e.g., checking CI, running automated tests, building Rollup bundles) and then prints instructions for manual verification. -1. The **publish** script then publishes the built artifacts to npm and pushes to GitHub. +The release process consists of several phases, each one represented by one of the scripts below. -Run either script without parameters to see its usage, e.g.: +A typical release goes like this: +1. When a commit is pushed to the React repo, [Circle CI](https://circleci.com/gh/facebook/react/) will build all release bundles and run unit tests against both the source code and the built bundles. +2. Next the release is published as a canary using the [`prepare-canary`](#prepare-canary) and [`publish`](#publish) scripts. (Currently this process is manual but will be automated in the future using [GitHub "actions"](https://github.com/features/actions).) +3. Finally, a canary releases can be promoted to stable using the [`prepare-stable`](#prepare-stable) and [`publish`](#publish) scripts. (This process is always manual.) + +One or more release scripts are used for each of the above phases. Learn more about these scripts below: +* [`create-canary`](#create-canary) +* [`prepare-canary`](#prepare-canary) +* [`prepare-stable`](#prepare-stable) +* [`publish`](#publish) + +## `create-canary` +Creates a canary build from the current (local) Git revision. + +**This script is an escape hatch.** It allows a canary release to be created without pushing a commit to be verified by Circle CI. **It does not run any automated unit tests.** Testing is solely the responsibility of the release engineer. + +#### Example usage +To create a canary from the current branch and revision: +```sh +scripts/release/create-canary.js ``` -./scripts/release/build.js -./scripts/release/publish.js + +## `prepare-canary` +Downloads build artifacts from Circle CI in preparation to be published to NPM as a canary release. + +All artifacts built by Circle CI have already been unit-tested (both source and bundles) but canaries should **always be manually tested** before being published. Upon completion, this script prints manual testing instructions. + +#### Example usage +To prepare the artifacts created by [Circle CI build 12677](https://circleci.com/gh/facebook/react/12677#artifacts/containers/0) you would run: +```sh +scripts/release/prepare-canary.js --build=12677 ``` -Each script will guide the release engineer through any necessary steps (including environment setup and manual testing steps). +## `prepare-stable` +Checks out a canary release from NPM and prepares it to be published as a stable release. + +This script prompts for new (stable) release versions for each public package and updates the package contents (both `package.json` and inline version numbers) to match. It also updates inter-package dependencies to account for the new versions. + +Canary release have already been tested but it is still a good idea to **manually test and verify a release** before publishing to ensure that e.g. version numbers are correct. Upon completion, this script prints manual testing instructions. + +#### Example usage +To promote the canary release `0.0.0-5bf84d292` (aka commit [5bf84d292](https://github.com/facebook/react/commit/5bf84d292)) to stable: +```sh +scripts/release/prepare-stable.js --version=0.0.0-5bf84d292 +``` + +## `publish` +Publishes the current contents of `build/node_modules` to NPM. + +This script publishes each public package to NPM and updates the specified tag(s) to match. **It does not test or verify the local package contents before publishing**. This should be done by the release engineer prior to running the script. + +Upon completion, this script provides instructions for tagging the Git commit that the package was created from and updating the release CHANGELOG. + +#### Example usage +To publish a release to NPM as both `next` and `latest`: +```sh +scripts/release/publish.js --tags=next,latest --otp= +``` \ No newline at end of file From 341ebb3978fa47149f89c1e22c4666d2277fcdeb Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 16 Nov 2018 13:05:47 -0800 Subject: [PATCH 05/22] Shuffled around and tidied up --- scripts/release/README.md | 2 +- scripts/release/build-commands/add-git-tag.js | 23 --- .../build-commands/check-circle-ci-status.js | 70 -------- .../build-commands/check-npm-permissions.js | 40 ----- .../check-package-dependencies.js | 60 ------- .../check-uncommitted-changes.js | 20 --- .../install-yarn-dependencies.js | 15 -- .../build-commands/parse-build-parameters.js | 57 ------- .../print-post-build-summary.js | 55 ------- .../run-automated-bundle-tests.js | 30 ---- .../build-commands/run-automated-tests.js | 23 --- scripts/release/build-commands/update-git.js | 26 --- .../update-noop-renderer-dependencies.js | 47 ------ .../build-commands/update-package-versions.js | 153 ------------------ .../update-yarn-dependencies.js | 38 ----- .../build-commands/validate-version.js | 20 --- scripts/release/build.js | 69 -------- scripts/release/config.js | 56 ------- .../add-build-info-json.js | 0 .../build-artifacts.js | 0 .../copy-repo-to-temp-directory.js | 0 .../npm-pack-and-unpack.js | 0 .../update-version-numbers.js | 0 scripts/release/create-canary.js | 13 +- .../check-environment-variables.js | 2 +- .../prepare-canary-commands/parse-params.js | 13 +- scripts/release/prepare-canary.js | 7 +- .../publish-commands/check-build-status.js | 35 ---- .../publish-commands/commit-changelog.js | 24 --- .../get-npm-two-factor-auth.js | 15 -- ...arse-publish-params.js => parse-params.js} | 34 ++-- .../print-post-publish-summary.js | 78 --------- .../publish-commands/publish-to-npm.js | 95 ----------- .../publish-commands/push-git-remote.js | 17 -- scripts/release/publish.js | 34 ++-- 35 files changed, 47 insertions(+), 1124 deletions(-) delete mode 100644 scripts/release/build-commands/add-git-tag.js delete mode 100644 scripts/release/build-commands/check-circle-ci-status.js delete mode 100644 scripts/release/build-commands/check-npm-permissions.js delete mode 100644 scripts/release/build-commands/check-package-dependencies.js delete mode 100644 scripts/release/build-commands/check-uncommitted-changes.js delete mode 100644 scripts/release/build-commands/install-yarn-dependencies.js delete mode 100644 scripts/release/build-commands/parse-build-parameters.js delete mode 100644 scripts/release/build-commands/print-post-build-summary.js delete mode 100644 scripts/release/build-commands/run-automated-bundle-tests.js delete mode 100644 scripts/release/build-commands/run-automated-tests.js delete mode 100644 scripts/release/build-commands/update-git.js delete mode 100644 scripts/release/build-commands/update-noop-renderer-dependencies.js delete mode 100644 scripts/release/build-commands/update-package-versions.js delete mode 100644 scripts/release/build-commands/update-yarn-dependencies.js delete mode 100644 scripts/release/build-commands/validate-version.js delete mode 100755 scripts/release/build.js delete mode 100644 scripts/release/config.js rename scripts/release/{create-build-commands => create-canary-commands}/add-build-info-json.js (100%) rename scripts/release/{create-build-commands => create-canary-commands}/build-artifacts.js (100%) rename scripts/release/{create-build-commands => create-canary-commands}/copy-repo-to-temp-directory.js (100%) rename scripts/release/{create-build-commands => create-canary-commands}/npm-pack-and-unpack.js (100%) rename scripts/release/{create-build-commands => create-canary-commands}/update-version-numbers.js (100%) rename scripts/release/{shared-commands => prepare-canary-commands}/check-environment-variables.js (90%) delete mode 100644 scripts/release/publish-commands/check-build-status.js delete mode 100644 scripts/release/publish-commands/commit-changelog.js delete mode 100644 scripts/release/publish-commands/get-npm-two-factor-auth.js rename scripts/release/publish-commands/{parse-publish-params.js => parse-params.js} (50%) delete mode 100644 scripts/release/publish-commands/print-post-publish-summary.js delete mode 100644 scripts/release/publish-commands/publish-to-npm.js delete mode 100644 scripts/release/publish-commands/push-git-remote.js diff --git a/scripts/release/README.md b/scripts/release/README.md index 6cc5a666f885b..78b431981a3c4 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -58,5 +58,5 @@ Upon completion, this script provides instructions for tagging the Git commit th #### Example usage To publish a release to NPM as both `next` and `latest`: ```sh -scripts/release/publish.js --tags=next,latest --otp= +scripts/release/publish.js --tags next latest ``` \ No newline at end of file diff --git a/scripts/release/build-commands/add-git-tag.js b/scripts/release/build-commands/add-git-tag.js deleted file mode 100644 index 1606b74c333eb..0000000000000 --- a/scripts/release/build-commands/add-git-tag.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {execUnlessDry, logPromise} = require('../utils'); - -const run = async ({cwd, dry, version}) => { - await execUnlessDry( - `git tag -a v${version} -m "Tagging ${version} release"`, - { - cwd, - dry, - } - ); -}; - -module.exports = async ({cwd, dry, version}) => { - return logPromise( - run({cwd, dry, version}), - `Creating git tag ${chalk.yellow.bold(`v${version}`)}` - ); -}; diff --git a/scripts/release/build-commands/check-circle-ci-status.js b/scripts/release/build-commands/check-circle-ci-status.js deleted file mode 100644 index 5c5348906ab0a..0000000000000 --- a/scripts/release/build-commands/check-circle-ci-status.js +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const http = require('request-promise-json'); -const logUpdate = require('log-update'); -const prompt = require('prompt-promise'); -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) { - 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.} - ` - ); - } else if (outcome !== 'success') { - 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.} - ` - ); - } -}; - -module.exports = async params => { - if (params.local) { - return; - } - - if (params.skipCI) { - logUpdate( - chalk.red`Are you sure you want to skip CI? (y for yes, n for no)` - ); - const confirm = await prompt(''); - logUpdate.done(); - if (confirm === 'y') { - return; - } else { - throw Error( - chalk` - Cancelling release. - ` - ); - } - } - - return logPromise(check(params), 'Checking CircleCI status'); -}; diff --git a/scripts/release/build-commands/check-npm-permissions.js b/scripts/release/build-commands/check-npm-permissions.js deleted file mode 100644 index ceab9c7ffc47f..0000000000000 --- a/scripts/release/build-commands/check-npm-permissions.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {execRead, logPromise} = require('../utils'); - -module.exports = async ({packages}) => { - 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(packages.map(checkProject)), - `Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions` - ); - - if (failedProjects.length) { - 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).} - ` - ); - } -}; diff --git a/scripts/release/build-commands/check-package-dependencies.js b/scripts/release/build-commands/check-package-dependencies.js deleted file mode 100644 index fe9a5329962c1..0000000000000 --- a/scripts/release/build-commands/check-package-dependencies.js +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {readJson} = require('fs-extra'); -const {join} = require('path'); -const {dependencies} = require('../config'); -const {logPromise} = require('../utils'); - -const check = async ({cwd, packages}) => { - const rootPackage = await readJson(join(cwd, 'package.json')); - - const projectPackages = []; - for (let i = 0; i < packages.length; i++) { - const project = packages[i]; - projectPackages.push( - await readJson(join(cwd, join('packages', project), 'package.json')) - ); - } - - const invalidDependencies = []; - - const checkModule = module => { - const rootVersion = rootPackage.devDependencies[module]; - - projectPackages.forEach(projectPackage => { - // Not all packages have dependencies (eg react-is) - const projectVersion = projectPackage.dependencies - ? projectPackage.dependencies[module] - : undefined; - - if (rootVersion !== projectVersion && projectVersion !== undefined) { - invalidDependencies.push( - `${module} is ${chalk.red.bold(rootVersion)} in root but ` + - `${chalk.red.bold(projectVersion)} in ${projectPackage.name}` - ); - } - }); - }; - - dependencies.forEach(checkModule); - - if (invalidDependencies.length) { - 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')} - ` - ); - } -}; - -module.exports = async params => { - return logPromise(check(params), 'Checking runtime dependencies'); -}; diff --git a/scripts/release/build-commands/check-uncommitted-changes.js b/scripts/release/build-commands/check-uncommitted-changes.js deleted file mode 100644 index 4d18e889b5f9e..0000000000000 --- a/scripts/release/build-commands/check-uncommitted-changes.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/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) { - throw Error( - chalk` - Uncommitted local changes - - {white Please revert or commit all local changes before making a release.} - ` - ); - } -}; diff --git a/scripts/release/build-commands/install-yarn-dependencies.js b/scripts/release/build-commands/install-yarn-dependencies.js deleted file mode 100644 index f0771552cf202..0000000000000 --- a/scripts/release/build-commands/install-yarn-dependencies.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {exec} = require('child-process-promise'); -const {logPromise} = require('../utils'); - -const install = async ({cwd}) => { - await exec('rm -rf node_modules', {cwd}); - await exec('yarn', {cwd}); -}; - -module.exports = async ({cwd}) => { - return logPromise(install({cwd}), 'Installing NPM dependencies'); -}; diff --git a/scripts/release/build-commands/parse-build-parameters.js b/scripts/release/build-commands/parse-build-parameters.js deleted file mode 100644 index 4ff12e1487380..0000000000000 --- a/scripts/release/build-commands/parse-build-parameters.js +++ /dev/null @@ -1,57 +0,0 @@ -#!/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 pre-release build script.', - }, - { - header: 'Options', - optionList: paramDefinitions, - }, - { - header: 'Examples', - content: [ - { - desc: '1. A concise example.', - example: '$ ./build.js [bold]{-v} [underline]{16.0.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: - '$ ./build.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/build-commands/print-post-build-summary.js b/scripts/release/build-commands/print-post-build-summary.js deleted file mode 100644 index 688159c19b622..0000000000000 --- a/scripts/release/build-commands/print-post-build-summary.js +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {join, relative} = require('path'); -const {getUnexecutedCommands} = require('../utils'); - -const CHANGELOG_PATH = - 'https://github.com/facebook/react/edit/master/CHANGELOG.md'; - -module.exports = ({cwd, dry, path, version}) => { - const publishPath = relative( - process.env.PWD, - join(__dirname, '../publish.js') - ); - const command = - `${publishPath} -v ${version}` + - (path ? ` -p ${path}` : '') + - (dry ? ' --dry' : ''); - - const packagingFixturesPath = join(cwd, 'fixtures/packaging'); - const standaloneFixturePath = join( - cwd, - 'fixtures/packaging/babel-standalone/dev.html' - ); - - console.log( - chalk` - {green.bold Build successful!} - ${getUnexecutedCommands()} - 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 ${standaloneFixturePath}} in the browser. - 2. It should say {italic "Hello world!"} - 3. Next go to {yellow.bold ${packagingFixturesPath}} and run {bold node build-all.js} - 4. Install the "pushstate-server" module ({bold npm install -g pushstate-server}) - 5. Go to the repo root and {bold pushstate-server -s .} - 6. Open {blue.bold http://localhost:9000/fixtures/packaging} - 7. Verify every iframe shows {italic "Hello world!"} - - After completing the above steps, resume the release process by running: - {yellow.bold ${command}} - `.replace(/\n +/g, '\n') - ); -}; diff --git a/scripts/release/build-commands/run-automated-bundle-tests.js b/scripts/release/build-commands/run-automated-bundle-tests.js deleted file mode 100644 index 3fed6b30026af..0000000000000 --- a/scripts/release/build-commands/run-automated-bundle-tests.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {logPromise, runYarnTask} = require('../utils'); - -module.exports = async ({cwd}) => { - await logPromise( - runYarnTask(cwd, 'lint-build', 'Lint bundle failed'), - 'Running ESLint on bundle' - ); - await logPromise( - runYarnTask( - cwd, - 'test-build', - 'Jest tests on the bundle failed in development' - ), - 'Running Jest tests on the bundle in the development environment', - true - ); - await logPromise( - runYarnTask( - cwd, - 'test-build-prod', - 'Jest tests on the bundle failed in production' - ), - 'Running Jest tests on the bundle in the production environment', - true - ); -}; diff --git a/scripts/release/build-commands/run-automated-tests.js b/scripts/release/build-commands/run-automated-tests.js deleted file mode 100644 index da1f6ad08b212..0000000000000 --- a/scripts/release/build-commands/run-automated-tests.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {logPromise, runYarnTask} = require('../utils'); - -module.exports = async ({cwd}) => { - await logPromise(runYarnTask(cwd, 'lint', 'Lint failed'), 'Running ESLint'); - await logPromise( - runYarnTask(cwd, 'flow-ci', 'Flow failed'), - 'Running Flow checks' - ); - await logPromise( - runYarnTask(cwd, 'test', 'Jest tests failed in development'), - 'Running Jest tests in the development environment', - true - ); - await logPromise( - runYarnTask(cwd, 'test-prod', 'Jest tests failed in production'), - 'Running Jest tests in the production environment', - true - ); -}; diff --git a/scripts/release/build-commands/update-git.js b/scripts/release/build-commands/update-git.js deleted file mode 100644 index 163111f09abcf..0000000000000 --- a/scripts/release/build-commands/update-git.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {exec} = require('child-process-promise'); -const {logPromise} = require('../utils'); - -const update = async ({cwd, branch, local}) => { - if (!local) { - await exec('git fetch', {cwd}); - } - await exec(`git checkout ${branch}`, {cwd}); - if (!local) { - await exec('git pull', {cwd}); - } -}; - -module.exports = async params => { - return logPromise( - update(params), - `Updating checkout ${chalk.yellow.bold( - params.cwd - )} on branch ${chalk.yellow.bold(params.branch)}` - ); -}; diff --git a/scripts/release/build-commands/update-noop-renderer-dependencies.js b/scripts/release/build-commands/update-noop-renderer-dependencies.js deleted file mode 100644 index e0bee71961f5c..0000000000000 --- a/scripts/release/build-commands/update-noop-renderer-dependencies.js +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {readJson, writeJson} = require('fs-extra'); -const {join} = require('path'); -const semver = require('semver'); -const {execRead, execUnlessDry, logPromise} = require('../utils'); - -const getReactReconcilerVersion = async cwd => { - const path = join(cwd, 'packages', 'react-reconciler', 'package.json'); - const json = await readJson(path); - return json.version; -}; - -const update = async ({cwd, dry}) => { - const path = join(cwd, 'packages', 'react-noop-renderer', 'package.json'); - const json = await readJson(path); - - // IMPORTANT: This script must be run after update-package-versions, - // Since it depends up the updated react-reconciler version. - const reconcilerVersion = await getReactReconcilerVersion(cwd); - - // There is no wildcard for semver that includes prerelease ranges as well. - // This causes problems for our Yarn workspaces setup, - // Since the noop-renderer depends on react-reconciler. - // So we have a special case check for this that ensures semver compatibility. - if (semver.prerelease(reconcilerVersion)) { - json.dependencies['react-reconciler'] = `* || ${reconcilerVersion}`; - } else { - json.dependencies['react-reconciler'] = '*'; - } - - await writeJson(path, json, {spaces: 2}); - - const status = await execRead('git status -s', {cwd}); - if (status) { - await execUnlessDry( - `git commit -am "Updating dependencies for react-noop-renderer"`, - {cwd, dry} - ); - } -}; - -module.exports = async params => { - return logPromise(update(params), 'Updating noop renderer dependencies'); -}; diff --git a/scripts/release/build-commands/update-package-versions.js b/scripts/release/build-commands/update-package-versions.js deleted file mode 100644 index 467c20791b914..0000000000000 --- a/scripts/release/build-commands/update-package-versions.js +++ /dev/null @@ -1,153 +0,0 @@ -#!/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 semver = require('semver'); -const {execUnlessDry, logPromise} = require('../utils'); - -const getNextVersion = (prevVersion, releaseVersion) => { - const prerelease = semver.prerelease(releaseVersion); - - // Unstable packages (eg version < 1.0) are treated specially: - // Rather than use the release version (eg 16.1.0)- - // We just auto-increment the minor version (eg 0.1.0 -> 0.2.0). - // If we're doing a prerelease, we also append the suffix (eg 0.2.0-beta). - if (semver.lt(prevVersion, '1.0.0')) { - let suffix = ''; - if (prerelease) { - suffix = `-${prerelease.join('.')}`; - } - - // If this is a new pre-release, increment the minor. - // Else just increment (or remove) the pre-release suffix. - // This way our minor version isn't incremented unnecessarily with each prerelease. - const minor = semver.prerelease(prevVersion) - ? semver.minor(prevVersion) - : semver.minor(prevVersion) + 1; - - return `0.${minor}.0${suffix}`; - } else { - return releaseVersion; - } -}; - -const update = async ({cwd, dry, packages, 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, 'packages/shared/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); - const prerelease = semver.prerelease(version); - - // If this is a package we publish directly to NPM, update its version. - // Skip ones that we don't directly publish though (e.g. react-native-renderer). - if (json.private !== true) { - json.version = getNextVersion(json.version, version); - } - - if (project === 'react') { - // Update inter-package dependencies as well. - // e.g. react depends on scheduler - if (json.dependencies) { - Object.keys(json.dependencies).forEach(dependency => { - if (packages.indexOf(dependency) >= 0) { - const prevVersion = json.dependencies[dependency]; - const nextVersion = getNextVersion( - prevVersion.replace('^', ''), - version - ); - json.dependencies[dependency] = `^${nextVersion}`; - } - }); - } - } else if (json.peerDependencies) { - let peerVersion = json.peerDependencies.react.replace('^', ''); - - // If the previous release was a pre-release version, - // The peer dependency will contain multiple versions, eg "^16.0.0 || 16.3.0-alpha.0" - // In this case, assume the first one will be the major and extract it. - if (peerVersion.includes(' || ')) { - peerVersion = peerVersion.split(' || ')[0]; - } - - // Release engineers can manually update minor and bugfix versions, - // But we should ensure that major versions always match. - if (semver.major(version) !== semver.major(peerVersion)) { - json.peerDependencies.react = `^${semver.major(version)}.0.0`; - } else { - json.peerDependencies.react = `^${peerVersion}`; - } - - // If this is a prerelease, update the react dependency as well. - // A dependency on a major version won't satisfy a prerelease version. - // So rather than eg "^16.0.0" we need "^16.0.0 || 16.3.0-alpha.0" - if (prerelease) { - json.peerDependencies.react += ` || ${version}`; - } - - // Update inter-package dependencies as well. - // e.g. react-test-renderer depends on react-is - if (json.dependencies) { - Object.keys(json.dependencies).forEach(dependency => { - if (packages.indexOf(dependency) >= 0) { - const prevVersion = json.dependencies[dependency]; - - // Special case to handle e.g. react-noop-renderer - if (prevVersion === '*') { - return; - } - - const nextVersion = getNextVersion( - prevVersion.replace('^', ''), - version - ); - json.dependencies[dependency] = `^${nextVersion}`; - } - }); - } - } - - await writeJson(path, json, {spaces: 2}); - }; - await Promise.all(packages.map(updateProjectPackage)); - - // Version sanity check - await exec('yarn version-check', {cwd}); - - await execUnlessDry( - `git commit -am "Updating package versions for release ${version}"`, - {cwd, dry} - ); - } catch (error) { - throw Error( - chalk` - Failed while updating package versions - - {white ${error.message}} - ` - ); - } -}; - -module.exports = async params => { - return logPromise(update(params), 'Updating package versions'); -}; diff --git a/scripts/release/build-commands/update-yarn-dependencies.js b/scripts/release/build-commands/update-yarn-dependencies.js deleted file mode 100644 index b8defaf5eaa15..0000000000000 --- a/scripts/release/build-commands/update-yarn-dependencies.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {exec} = require('child-process-promise'); -const {dependencies} = require('../config'); -const {execRead, execUnlessDry, 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') { - throw Error( - chalk` - Unexpected modifications - - {white The following files have been modified unexpectedly:} - {gray ${modifiedFiles}} - ` - ); - } - - await execUnlessDry( - `git commit -am "Updating yarn.lock file for ${version} release"`, - {cwd, dry} - ); - } -}; - -module.exports = async params => { - return logPromise(update(params), 'Upgrading NPM dependencies'); -}; diff --git a/scripts/release/build-commands/validate-version.js b/scripts/release/build-commands/validate-version.js deleted file mode 100644 index 8329d3c38dbb1..0000000000000 --- a/scripts/release/build-commands/validate-version.js +++ /dev/null @@ -1,20 +0,0 @@ -'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` - ); - } -}; diff --git a/scripts/release/build.js b/scripts/release/build.js deleted file mode 100755 index 721770778a218..0000000000000 --- a/scripts/release/build.js +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const {exec} = require('child_process'); - -// Follows the steps outlined in github.com/facebook/react/issues/10620 -const run = async () => { - const {getPublicPackages, getPackages, handleError} = require('./utils'); - - const addGitTag = require('./build-commands/add-git-tag'); - const buildArtifacts = require('./create-release-commands/build-artifacts'); - const checkCircleCiStatus = require('./build-commands/check-circle-ci-status'); - const checkEnvironmentVariables = require('./shared-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 runAutomatedBundleTests = require('./build-commands/run-automated-bundle-tests'); - const updateGit = require('./build-commands/update-git'); - const updateNoopRendererDependencies = require('./build-commands/update-noop-renderer-dependencies'); - const updatePackageVersions = require('./build-commands/update-package-versions'); - const updateYarnDependencies = require('./build-commands/update-yarn-dependencies'); - const validateVersion = require('./build-commands/validate-version'); - - try { - const params = parseBuildParameters(); - params.packages = getPublicPackages(); - - await checkEnvironmentVariables(params); - await validateVersion(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); - // Also update NPM dependencies for private packages (e.g. react-native-renderer) - // Even though we don't publish these to NPM, - // mismatching dependencies can cause `yarn install` to install duplicate packages. - await updatePackageVersions({ - ...params, - packages: getPackages(), - }); - await updateNoopRendererDependencies(params); - await buildArtifacts(params); - await runAutomatedBundleTests(params); - await addGitTag(params); - await printPostBuildSummary(params); - } catch (error) { - handleError(error); - } -}; - -// Install (or update) release script dependencies before proceeding. -// This needs to be done before we require() the first NPM module. -exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => { - if (error) { - console.error(error); - process.exit(1); - } else { - run(); - } -}); diff --git a/scripts/release/config.js b/scripts/release/config.js deleted file mode 100644 index ec88d0074b583..0000000000000 --- a/scripts/release/config.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const dependencies = ['object-assign', 'prop-types']; - -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', - }, - { - name: 'branch', - type: String, - alias: 'b', - description: 'Branch to build from; defaults to [bold]{master}', - defaultValue: 'master', - }, - { - name: 'local', - type: Boolean, - description: - "Don't push or pull changes from remote repo. Don't check CI status.", - }, - { - name: 'tag', - type: String, - description: - 'The npm dist tag; defaults to [bold]{latest} for a stable' + - 'release, [bold]{next} for unstable', - }, - { - name: 'skipCI', - type: Boolean, - description: 'Skip Circle CI status check (requires confirmation)', - }, -]; - -module.exports = { - dependencies, - paramDefinitions, -}; diff --git a/scripts/release/create-build-commands/add-build-info-json.js b/scripts/release/create-canary-commands/add-build-info-json.js similarity index 100% rename from scripts/release/create-build-commands/add-build-info-json.js rename to scripts/release/create-canary-commands/add-build-info-json.js diff --git a/scripts/release/create-build-commands/build-artifacts.js b/scripts/release/create-canary-commands/build-artifacts.js similarity index 100% rename from scripts/release/create-build-commands/build-artifacts.js rename to scripts/release/create-canary-commands/build-artifacts.js diff --git a/scripts/release/create-build-commands/copy-repo-to-temp-directory.js b/scripts/release/create-canary-commands/copy-repo-to-temp-directory.js similarity index 100% rename from scripts/release/create-build-commands/copy-repo-to-temp-directory.js rename to scripts/release/create-canary-commands/copy-repo-to-temp-directory.js diff --git a/scripts/release/create-build-commands/npm-pack-and-unpack.js b/scripts/release/create-canary-commands/npm-pack-and-unpack.js similarity index 100% rename from scripts/release/create-build-commands/npm-pack-and-unpack.js rename to scripts/release/create-canary-commands/npm-pack-and-unpack.js diff --git a/scripts/release/create-build-commands/update-version-numbers.js b/scripts/release/create-canary-commands/update-version-numbers.js similarity index 100% rename from scripts/release/create-build-commands/update-version-numbers.js rename to scripts/release/create-canary-commands/update-version-numbers.js diff --git a/scripts/release/create-canary.js b/scripts/release/create-canary.js index 3a3584276e853..553f77c6f65f1 100755 --- a/scripts/release/create-canary.js +++ b/scripts/release/create-canary.js @@ -6,18 +6,19 @@ const {tmpdir} = require('os'); const {join} = require('path'); const {getBuildInfo, handleError} = require('./utils'); -// This local build script exists for special case, manual builds. +// This script is an escape hatch! +// It exists for special case manual builds. // The typical suggesgted release process is to create a canary from a CI artifact. // This build script is optimized for speed and simplicity. // It doesn't run all of the tests that the CI environment runs. // You're expected to run those manually before publishing a release. -const addBuildInfoJSON = require('./create-build-commands/add-build-info-json'); -const buildArtifacts = require('./create-build-commands/build-artifacts'); -const copyRepoToTempDirectory = require('./create-build-commands/copy-repo-to-temp-directory'); -const npmPackAndUnpack = require('./create-build-commands/npm-pack-and-unpack'); +const addBuildInfoJSON = require('./create-canary-commands/add-build-info-json'); +const buildArtifacts = require('./create-canary-commands/build-artifacts'); +const copyRepoToTempDirectory = require('./create-canary-commands/copy-repo-to-temp-directory'); +const npmPackAndUnpack = require('./create-canary-commands/npm-pack-and-unpack'); const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); -const updateVersionNumbers = require('./create-build-commands/update-version-numbers'); +const updateVersionNumbers = require('./create-canary-commands/update-version-numbers'); const run = async () => { try { diff --git a/scripts/release/shared-commands/check-environment-variables.js b/scripts/release/prepare-canary-commands/check-environment-variables.js similarity index 90% rename from scripts/release/shared-commands/check-environment-variables.js rename to scripts/release/prepare-canary-commands/check-environment-variables.js index a4318a5fa3d20..c284ab992ad7d 100644 --- a/scripts/release/shared-commands/check-environment-variables.js +++ b/scripts/release/prepare-canary-commands/check-environment-variables.js @@ -10,7 +10,7 @@ module.exports = () => { chalk` {red Missing CircleCI API token} - {white The CircleCI API is used to check the status of the latest commit.} + {white The CircleCI API is used to download build artifacts.} {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:} diff --git a/scripts/release/prepare-canary-commands/parse-params.js b/scripts/release/prepare-canary-commands/parse-params.js index 1e27fe95dd372..f2f4db4034082 100644 --- a/scripts/release/prepare-canary-commands/parse-params.js +++ b/scripts/release/prepare-canary-commands/parse-params.js @@ -15,14 +15,6 @@ const paramDefinitions = [ 'Circle CI build identifier (e.g. https://circleci.com/gh/facebook/react/)', defaultValue: false, }, - { - name: 'path', - type: String, - alias: 'p', - description: - 'Location of React repository to release; defaults to [bold]{cwd}', - defaultValue: '.', - }, ]; module.exports = () => { @@ -58,8 +50,5 @@ module.exports = () => { process.exit(1); } - return { - ...params, - cwd: params.path, // For script convenience - }; + return params; }; diff --git a/scripts/release/prepare-canary.js b/scripts/release/prepare-canary.js index 5302bd95f251f..6de65448f3226 100755 --- a/scripts/release/prepare-canary.js +++ b/scripts/release/prepare-canary.js @@ -2,9 +2,10 @@ 'use strict'; -const {handleError} = require('./utils'); +const {join} = require('path'); +const {getPublicPackages, handleError} = require('./utils'); -const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); +const checkEnvironmentVariables = require('./prepare-canary-commands/check-environment-variables'); const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts'); const parseParams = require('./prepare-canary-commands/parse-params'); const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); @@ -12,6 +13,8 @@ const printPrereleaseSummary = require('./shared-commands/print-prerelease-summa const run = async () => { try { const params = parseParams(); + params.cwd = join(__dirname, '..', '..'); + params.packages = await getPublicPackages(); await checkEnvironmentVariables(params); await downloadBuildArtifacts(params); diff --git a/scripts/release/publish-commands/check-build-status.js b/scripts/release/publish-commands/check-build-status.js deleted file mode 100644 index 02ecf20424c19..0000000000000 --- a/scripts/release/publish-commands/check-build-status.js +++ /dev/null @@ -1,35 +0,0 @@ -#!/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, local}) => { - if (local) { - return; - } - const packagePath = join( - cwd, - 'build', - 'node_modules', - '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-commands/commit-changelog.js b/scripts/release/publish-commands/commit-changelog.js deleted file mode 100644 index 448dda5a30903..0000000000000 --- a/scripts/release/publish-commands/commit-changelog.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/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-commands/get-npm-two-factor-auth.js b/scripts/release/publish-commands/get-npm-two-factor-auth.js deleted file mode 100644 index d32bb8c429374..0000000000000 --- a/scripts/release/publish-commands/get-npm-two-factor-auth.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const logUpdate = require('log-update'); -const prompt = require('prompt-promise'); - -module.exports = async params => { - logUpdate(chalk`{green ✓} Npm two-factor auth code {gray (or blank)}: `); - const otp = await prompt(''); - prompt.done(); - logUpdate.clear(); - return otp.trim() || null; -}; diff --git a/scripts/release/publish-commands/parse-publish-params.js b/scripts/release/publish-commands/parse-params.js similarity index 50% rename from scripts/release/publish-commands/parse-publish-params.js rename to scripts/release/publish-commands/parse-params.js index c4c7209dce7af..67f22f6b65d43 100644 --- a/scripts/release/publish-commands/parse-publish-params.js +++ b/scripts/release/publish-commands/parse-params.js @@ -6,12 +6,20 @@ const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); const figlet = require('figlet'); -const {paramDefinitions} = require('../config'); + +const paramDefinitions = [ + { + name: 'tags', + type: String, + multiple: true, + description: 'NPM tags to point to the new release.', + }, +]; module.exports = () => { const params = commandLineArgs(paramDefinitions); - if (!params.version) { + if (!params.tags) { const usage = commandLineUsage([ { content: chalk @@ -20,7 +28,8 @@ module.exports = () => { raw: true, }, { - content: 'Automated release publishing script.', + content: + 'Publishes the current contents of "build/node_modules" to NPM.', }, { header: 'Options', @@ -30,18 +39,8 @@ module.exports = () => { 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', + desc: 'Example:', + example: '$ scripts/release/publish.js --tags next latest', }, ], }, @@ -50,8 +49,5 @@ module.exports = () => { process.exit(1); } - return { - ...params, - cwd: params.path, // For script convenience - }; + return params; }; diff --git a/scripts/release/publish-commands/print-post-publish-summary.js b/scripts/release/publish-commands/print-post-publish-summary.js deleted file mode 100644 index fc1dfa25415d9..0000000000000 --- a/scripts/release/publish-commands/print-post-publish-summary.js +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const semver = require('semver'); -const {getUnexecutedCommands} = require('../utils'); - -const printSteps = steps => { - return steps - .filter(Boolean) // Remove no-op steps - .map((step, index) => `${index + 1}. ${step}`) - .join('\n'); -}; - -const printSections = sections => { - return sections - .map((section, index) => { - const [title, ...steps] = section; - - return chalk` - {bold.underline Step ${index + 1}: ${title}} - - ${printSteps(steps)} - `.replace(/\n +/g, '\n'); - }) - .join(''); -}; - -module.exports = ({dry, version}) => { - const isPrerelease = semver.prerelease(version); - - const sections = []; - - // Certain follow-up steps are for stable releases only. - if (!isPrerelease) { - sections.push([ - 'Create GitHub release', - chalk`Open new release page: {blue.bold https://github.com/facebook/react/releases/new}`, - chalk`Choose {bold ${version}} from the dropdown menu`, - chalk`Paste the new release notes from {yellow.bold CHANGELOG.md}`, - chalk`Attach all files in {yellow.bold build/dist/*.js} except {yellow.bold react-art.*} to the release.`, - chalk`Press {bold "Publish release"}!`, - ]); - - sections.push([ - 'Update the version on reactjs.org', - chalk`Git clone (or update) {blue.bold https://github.com/reactjs/reactjs.org}`, - chalk`Open the {bold.yellow src/site-constants.js} file`, - chalk`Update the {bold version} value to {bold ${version}}`, - chalk`Open a Pull Request to {bold master}`, - ]); - } - - sections.push([ - 'Test the new release', - chalk`Install CRA: {bold npm i -g create-react-app}`, - chalk`Create a test application: {bold create-react-app myapp && cd myapp}`, - isPrerelease - ? chalk`Install the pre-release versions: {bold yarn add react@next react-dom@next}` - : null, - chalk`Run the app: {bold yarn start}`, - ]); - - sections.push([ - 'Notify the DOM team', - chalk`Notify DOM team members: {bold @nhunzaker @jquense @aweary}`, - ]); - - console.log( - chalk` - {green.bold Publish successful!} - ${getUnexecutedCommands()} - Next there are a couple of manual steps: - ${printSections(sections)} - `.replace(/\n +/g, '\n') - ); -}; diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js deleted file mode 100644 index f22525bfd633a..0000000000000 --- a/scripts/release/publish-commands/publish-to-npm.js +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const chalk = require('chalk'); -const {readJson} = require('fs-extra'); -const {join} = require('path'); -const semver = require('semver'); -const {execRead, execUnlessDry, logPromise} = require('../utils'); - -const push = async ({cwd, dry, otp, packages, version, tag}) => { - const errors = []; - const isPrerelease = semver.prerelease(version); - - let resolvedTag = tag; - if (tag === undefined) { - // No tag was provided. Default to `latest` for stable releases and `next` - // for prereleases - resolvedTag = isPrerelease ? 'next' : 'latest'; - } else if (tag === 'latest' && isPrerelease) { - throw new Error('The tag `latest` can only be used for stable versions.'); - } - - // Pass two factor auth code if provided: - // https://docs.npmjs.com/getting-started/using-two-factor-authentication - const twoFactorAuth = otp != null ? `--otp ${otp}` : ''; - - const publishProject = async project => { - try { - const path = join(cwd, 'build', 'node_modules', project); - await execUnlessDry(`npm publish --tag ${resolvedTag} ${twoFactorAuth}`, { - cwd: path, - dry, - }); - - const packagePath = join( - cwd, - 'build', - 'node_modules', - project, - 'package.json' - ); - const packageJSON = await readJson(packagePath); - const packageVersion = packageJSON.version; - - if (!dry) { - // Wait a couple of seconds before querying NPM for status; - // Anecdotally, querying too soon can result in a false negative. - await new Promise(resolve => setTimeout(resolve, 5000)); - - const status = JSON.parse( - await execRead(`npm info ${project} dist-tags --json`) - ); - const remoteVersion = status[resolvedTag]; - - // Compare remote version to package.json version, - // To better handle the case of pre-release versions. - if (remoteVersion !== packageVersion) { - throw Error( - chalk`Published version {yellow.bold ${packageVersion}} for ` + - chalk`{bold ${project}} but NPM shows {yellow.bold ${remoteVersion}}` - ); - } - - // If we've just published a stable release, - // Update the @next tag to also point to it (so @next doesn't lag behind). - // Skip this step if we have a manually specified tag. - // This is an escape hatch for us to interleave alpha and stable releases. - if (tag === undefined && !isPrerelease) { - await execUnlessDry( - `npm dist-tag add ${project}@${packageVersion} next ${twoFactorAuth}`, - {cwd: path, dry} - ); - } - } - } catch (error) { - errors.push(error.stack); - } - }; - - await Promise.all(packages.map(publishProject)); - - if (errors.length > 0) { - throw Error( - chalk` - Failure publishing to NPM - - {white ${errors.join('\n\n')}}` - ); - } -}; - -module.exports = async params => { - return logPromise(push(params), 'Publishing packages to NPM'); -}; diff --git a/scripts/release/publish-commands/push-git-remote.js b/scripts/release/publish-commands/push-git-remote.js deleted file mode 100644 index 6d487ac546d2e..0000000000000 --- a/scripts/release/publish-commands/push-git-remote.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/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 => { - if (params.local) { - return; - } - return logPromise(push(params), 'Pushing to git remote'); -}; diff --git a/scripts/release/publish.js b/scripts/release/publish.js index 4f242041488f8..a034d7b904e49 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -2,28 +2,28 @@ 'use strict'; +const {join} = require('path'); const {getPublicPackages, handleError} = require('./utils'); -const checkBuildStatus = require('./publish-commands/check-build-status'); -const commitChangelog = require('./publish-commands/commit-changelog'); -const getNpmTwoFactorAuth = require('./publish-commands/get-npm-two-factor-auth'); -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'); +const parseParams = require('./publish-commands/parse-params'); -// Follows the steps outlined in github.com/facebook/react/issues/10620 const run = async () => { - const params = parsePublishParams(); - params.packages = getPublicPackages(); - try { - await checkBuildStatus(params); - await commitChangelog(params); - await pushGitRemote(params); - params.otp = await getNpmTwoFactorAuth(params); - await publishToNpm(params); - await printPostPublishSummary(params); + const params = parseParams(); + params.cwd = join(__dirname, '..', '..'); + params.packages = await getPublicPackages(); + + console.log(params); + + // TODO Require inputs: a list of NPM tags (e.g. --tags=latest, --tags=latest,next). + // TODO Print versions of the local, prepared packages and confirm the tag release. + // TODO Check NPM permissions to ensure that the current user can publish all public packages. + // TODO Publish each package to NPM with the specified version number and tag. + // J.I.T. prompt (or re-prompt) for OTP token if publishing fails. + // TODO Print command for tagging the Git commit the release was originally created from (using build-info.json). + // TODO Print command for creating a changelog. + // TODO Support basic "resume" by checking each package to see if it has already been published + // before publishing it (and skipping it if so). } catch (error) { handleError(error); } From bbb9d9e37fdabb328ae11853954bb06989700d55 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 17 Nov 2018 11:47:22 -0800 Subject: [PATCH 06/22] Added publish command and slightly refactored some other command messaging --- package.json | 2 +- scripts/release/ci-add-build-info-json.js | 3 +- scripts/release/ci-update-package-versions.js | 4 +- .../add-build-info-json.js | 3 +- .../confirm-automated-testing.js | 24 ++++ .../npm-pack-and-unpack.js | 2 +- .../update-version-numbers.js | 4 +- scripts/release/create-canary.js | 22 +++- scripts/release/package.json | 3 +- .../download-build-artifacts.js | 2 +- .../prepare-canary-commands/parse-params.js | 1 - .../update-stable-version-numbers.js | 55 ++++++--- .../publish-commands/check-npm-permissions.js | 40 +++++++ .../confirm-version-and-tags.js | 48 ++++++++ .../release/publish-commands/parse-params.js | 16 ++- .../print-follow-up-instructions.js | 56 +++++++++ .../publish-commands/prompt-for-otp.js | 22 ++++ .../publish-commands/publish-to-npm.js | 62 ++++++++++ .../release/publish-commands/validate-tags.js | 29 +++++ scripts/release/publish.js | 23 ++-- .../print-prerelease-summary.js | 25 ++-- scripts/release/utils.js | 112 +++++++++--------- scripts/release/yarn.lock | 20 ++-- 23 files changed, 457 insertions(+), 121 deletions(-) create mode 100644 scripts/release/create-canary-commands/confirm-automated-testing.js create mode 100644 scripts/release/publish-commands/check-npm-permissions.js create mode 100644 scripts/release/publish-commands/confirm-version-and-tags.js create mode 100644 scripts/release/publish-commands/print-follow-up-instructions.js create mode 100644 scripts/release/publish-commands/prompt-for-otp.js create mode 100644 scripts/release/publish-commands/publish-to-npm.js create mode 100644 scripts/release/publish-commands/validate-tags.js diff --git a/package.json b/package.json index 9f3d3d0970d80..f6829548c0a66 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "testRegex": "/scripts/jest/dont-run-jest-directly\\.js$" }, "scripts": { - "build": "npm run version-check && node ./scripts/rollup/build.js", + "build": "node ./scripts/rollup/build.js", "linc": "node ./scripts/tasks/linc.js", "lint": "node ./scripts/tasks/eslint.js", "lint-build": "node ./scripts/rollup/validate/index.js", diff --git a/scripts/release/ci-add-build-info-json.js b/scripts/release/ci-add-build-info-json.js index a2f30fc4492f7..540db47b91c45 100755 --- a/scripts/release/ci-add-build-info-json.js +++ b/scripts/release/ci-add-build-info-json.js @@ -16,7 +16,7 @@ const run = async () => { const cwd = join(__dirname, '..', '..'); - const {checksum, commit, branch} = await getBuildInfo(); + const {branch, checksum, commit, reactVersion} = await getBuildInfo(); const packages = getPackages(join(cwd, 'packages')); const packagesDir = join(cwd, 'packages'); @@ -26,6 +26,7 @@ const run = async () => { checksum, commit, environment: 'ci', + reactVersion, }; for (let i = 0; i < packages.length; i++) { diff --git a/scripts/release/ci-update-package-versions.js b/scripts/release/ci-update-package-versions.js index 0d5e65ec04a7e..c0042662d0b5a 100755 --- a/scripts/release/ci-update-package-versions.js +++ b/scripts/release/ci-update-package-versions.js @@ -14,9 +14,9 @@ const run = async () => { const cwd = join(__dirname, '..', '..'); - const {version} = await getBuildInfo(); + const {reactVersion, version} = await getBuildInfo(); - await updateVersionsForCanary(cwd, version); + await updateVersionsForCanary(cwd, reactVersion, version); }; // Install (or update) release script dependencies before proceeding. diff --git a/scripts/release/create-canary-commands/add-build-info-json.js b/scripts/release/create-canary-commands/add-build-info-json.js index 816b2a146eb18..2f60331ac50a9 100644 --- a/scripts/release/create-canary-commands/add-build-info-json.js +++ b/scripts/release/create-canary-commands/add-build-info-json.js @@ -7,7 +7,7 @@ const {writeJson} = require('fs-extra'); const {join} = require('path'); const {getPackages, logPromise} = require('../utils'); -const run = async ({branch, checksum, commit, tempDirectory}) => { +const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { const packages = getPackages(join(tempDirectory, 'packages')); const packagesDir = join(tempDirectory, 'packages'); @@ -16,6 +16,7 @@ const run = async ({branch, checksum, commit, tempDirectory}) => { checksum, commit, environment: 'local', + reactVersion, }; for (let i = 0; i < packages.length; i++) { diff --git a/scripts/release/create-canary-commands/confirm-automated-testing.js b/scripts/release/create-canary-commands/confirm-automated-testing.js new file mode 100644 index 0000000000000..9e706905f32d2 --- /dev/null +++ b/scripts/release/create-canary-commands/confirm-automated-testing.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const clear = require('clear'); +const {confirm} = require('../utils'); + +const run = async () => { + clear(); + + console.log( + chalk.red( + 'This script does not run any automated tests.' + + 'You should run them manually before creating a canary release.' + ) + ); + + await confirm('Do you want to proceed?'); + + clear(); +}; + +module.exports = run; diff --git a/scripts/release/create-canary-commands/npm-pack-and-unpack.js b/scripts/release/create-canary-commands/npm-pack-and-unpack.js index 76f3cf85cf882..3a0d909131d02 100644 --- a/scripts/release/create-canary-commands/npm-pack-and-unpack.js +++ b/scripts/release/create-canary-commands/npm-pack-and-unpack.js @@ -27,7 +27,7 @@ const run = async ({cwd, dry, tempDirectory}) => { `cp -r ${tempDirectory}/build/node_modules/*.tgz ${cwd}/build/node_modules/` ); - // Unpack packages and parepare to publish. + // Unpack packages and prepare to publish. const compressedPackages = readdirSync('build/node_modules/'); for (let i = 0; i < compressedPackages.length; i++) { await exec( diff --git a/scripts/release/create-canary-commands/update-version-numbers.js b/scripts/release/create-canary-commands/update-version-numbers.js index f0adc9ae77237..fe4da92546bce 100644 --- a/scripts/release/create-canary-commands/update-version-numbers.js +++ b/scripts/release/create-canary-commands/update-version-numbers.js @@ -5,9 +5,9 @@ const chalk = require('chalk'); const {logPromise, updateVersionsForCanary} = require('../utils'); -module.exports = async ({tempDirectory, version}) => { +module.exports = async ({reactVersion, tempDirectory, version}) => { return logPromise( - updateVersionsForCanary(tempDirectory, version), + updateVersionsForCanary(tempDirectory, reactVersion, version), `Updating version numbers (${chalk.yellow.bold(version)})` ); }; diff --git a/scripts/release/create-canary.js b/scripts/release/create-canary.js index 553f77c6f65f1..dc00b3a7b3289 100755 --- a/scripts/release/create-canary.js +++ b/scripts/release/create-canary.js @@ -8,13 +8,14 @@ const {getBuildInfo, handleError} = require('./utils'); // This script is an escape hatch! // It exists for special case manual builds. -// The typical suggesgted release process is to create a canary from a CI artifact. +// The typical suggested release process is to create a canary from a CI artifact. // This build script is optimized for speed and simplicity. // It doesn't run all of the tests that the CI environment runs. // You're expected to run those manually before publishing a release. const addBuildInfoJSON = require('./create-canary-commands/add-build-info-json'); const buildArtifacts = require('./create-canary-commands/build-artifacts'); +const confirmAutomatedTesting = require('./create-canary-commands/confirm-automated-testing'); const copyRepoToTempDirectory = require('./create-canary-commands/copy-repo-to-temp-directory'); const npmPackAndUnpack = require('./create-canary-commands/npm-pack-and-unpack'); const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); @@ -23,10 +24,25 @@ const updateVersionNumbers = require('./create-canary-commands/update-version-nu const run = async () => { try { const cwd = join(__dirname, '..', '..'); - const {branch, checksum, commit, version} = await getBuildInfo(); + const { + branch, + checksum, + commit, + reactVersion, + version, + } = await getBuildInfo(); const tempDirectory = join(tmpdir(), `react-${commit}`); - const params = {branch, checksum, commit, cwd, tempDirectory, version}; + const params = { + branch, + checksum, + commit, + cwd, + reactVersion, + tempDirectory, + version, + }; + await confirmAutomatedTesting(params); await copyRepoToTempDirectory(params); await updateVersionNumbers(params); await addBuildInfoJSON(params); diff --git a/scripts/release/package.json b/scripts/release/package.json index dd40d3cfdbedc..cce43a70ad431 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -7,14 +7,15 @@ "dependencies": { "chalk": "^2.1.0", "child-process-promise": "^2.2.1", + "clear": "^0.1.0", "cli-spinners": "^1.1.0", "command-line-args": "^4.0.7", "command-line-usage": "^4.0.1", + "diff": "^3.5.0", "figlet": "^1.2.0", "folder-hash": "^2.1.2", "fs-extra": "^4.0.2", "log-update": "^2.1.0", - "print-diff": "^0.1.1", "prompt-promise": "^1.0.3", "request-promise-json": "^1.0.4", "semver": "^5.4.1" diff --git a/scripts/release/prepare-canary-commands/download-build-artifacts.js b/scripts/release/prepare-canary-commands/download-build-artifacts.js index e047b59732573..ec6bf1dfbf3e5 100644 --- a/scripts/release/prepare-canary-commands/download-build-artifacts.js +++ b/scripts/release/prepare-canary-commands/download-build-artifacts.js @@ -27,7 +27,7 @@ const run = async ({build, cwd}) => { `tar zxvf ${cwd}/build/node_modules.tgz -C ${cwd}/build/node_modules/` ); - // Unpack packages and parepare to publish + // Unpack packages and prepare to publish const compressedPackages = readdirSync('build/node_modules/'); for (let i = 0; i < compressedPackages.length; i++) { await exec( diff --git a/scripts/release/prepare-canary-commands/parse-params.js b/scripts/release/prepare-canary-commands/parse-params.js index f2f4db4034082..2212296505f79 100644 --- a/scripts/release/prepare-canary-commands/parse-params.js +++ b/scripts/release/prepare-canary-commands/parse-params.js @@ -13,7 +13,6 @@ const paramDefinitions = [ type: Number, description: 'Circle CI build identifier (e.g. https://circleci.com/gh/facebook/react/)', - defaultValue: false, }, ]; diff --git a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js index b088b504786ee..ed1893e348c26 100644 --- a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js +++ b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js @@ -3,28 +3,23 @@ 'use strict'; const chalk = require('chalk'); +const clear = require('clear'); const {readFileSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); -const {join} = require('path'); -const printDiff = require('print-diff'); -const {confirm, execRead} = require('../utils'); +const {join, relative} = require('path'); +const {confirm, execRead, printDiff} = require('../utils'); const run = async ({cwd, packages, version}, versionsMap) => { const nodeModulesPath = join(cwd, 'build/node_modules'); // Cache all package JSONs for easy lookup below. const sourcePackageJSONs = new Map(); - const targetPackageJSONs = new Map(); for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; const sourcePackageJSON = await readJson( join(cwd, 'packages', packageName, 'package.json') ); sourcePackageJSONs.set(packageName, sourcePackageJSON); - const targetPackageJSON = await readJson( - join(nodeModulesPath, packageName, 'package.json') - ); - targetPackageJSONs.set(packageName, targetPackageJSON); } const updateDependencies = async (targetPackageJSON, key) => { @@ -50,7 +45,7 @@ const run = async ({cwd, packages, version}, versionsMap) => { // If the source dependency's version and the constraint match, // we will need to update the constraint to point at the dependency's new release version, // (e.g. scheduler@^0.11.0 becomes scheduler@^0.12.0 when we release scheduler 0.12.0). - // Othewise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases). + // Otherwise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases). // Note that in both cases, we must update the target package JSON, // since canary releases are all locked to the canary version (e.g. 0.0.0-ddaf2b07c). if ( @@ -74,7 +69,7 @@ const run = async ({cwd, packages, version}, versionsMap) => { // Update all package JSON versions and their dependencies/peerDependencies. // This must be done in a way that respects semver constraints (e.g. 16.7.0, ^16.7.0, ^16.0.0). // To do this, we use the dependencies defined in the source package JSONs, - // because the canary dependencies have already been falttened to an exact match (e.g. 0.0.0-ddaf2b07c). + // because the canary dependencies have already been flattened to an exact match (e.g. 0.0.0-ddaf2b07c). for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; const packageJSONPath = join(nodeModulesPath, packageName, 'package.json'); @@ -87,6 +82,8 @@ const run = async ({cwd, packages, version}, versionsMap) => { await writeJson(packageJSONPath, packageJSON, {spaces: 2}); } + clear(); + // Print the map of versions and their dependencies for confirmation. const printDependencies = (maybeDependency, label) => { if (maybeDependency) { @@ -115,6 +112,20 @@ const run = async ({cwd, packages, version}, versionsMap) => { } await confirm('Do the versions above look correct?'); + clear(); + + // A separate "React version" is used for the embedded renderer version to support DevTools, + // since it needs to distinguish between different version ranges of React. + // We need to replace it as well as the canary version number. + const buildInfoPath = join(nodeModulesPath, 'react', 'build-info.json'); + const {reactVersion} = await readJson(buildInfoPath); + + // We print the diff to the console for review, + // but it can be large so let's also write it to disk. + const diffPath = join(cwd, 'build', 'temp.diff'); + let diff = ''; + let numFilesModified = 0; + // Find-and-replace hard coded version (in built JS) for renderers. for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; @@ -126,18 +137,30 @@ const run = async ({cwd, packages, version}, versionsMap) => { ); files = files.split('\n'); files.forEach(path => { + const newStableVersion = versionsMap.get(packageName); const beforeContents = readFileSync(path, 'utf8', {cwd}); - const afterContents = beforeContents.replace( - new RegExp(version, 'g'), - versionsMap.get(packageName) - ); + let afterContents = beforeContents; + // Replace all canary version numbers (e.g. header @license). + while (afterContents.indexOf(version) >= 0) { + afterContents = afterContents.replace(version, newStableVersion); + } + // Replace inline renderer version numbers (e.g. shared/ReactVersion). + while (afterContents.indexOf(reactVersion) >= 0) { + afterContents = afterContents.replace(reactVersion, newStableVersion); + } if (beforeContents !== afterContents) { - printDiff(beforeContents, afterContents); + numFilesModified++; + diff += printDiff(path, beforeContents, afterContents); writeFileSync(path, afterContents, {cwd}); } }); } - await confirm('Do the replacements above look correct?'); + writeFileSync(diffPath, diff, {cwd}); + console.log(chalk.green(`\n${numFilesModified} files have been updated.`)); + console.log( + chalk`A full diff is availbale at {yellow ${relative(cwd, diffPath)}}.` + ); + await confirm('Do changes changes look correct?'); }; // Run this directly because logPromise would interfere with printing package dependencies. diff --git a/scripts/release/publish-commands/check-npm-permissions.js b/scripts/release/publish-commands/check-npm-permissions.js new file mode 100644 index 0000000000000..8773cf4703dbe --- /dev/null +++ b/scripts/release/publish-commands/check-npm-permissions.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {execRead, logPromise} = require('../utils'); + +const run = async ({cwd, packages, version}) => { + 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(packages.map(checkProject)), + `Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions` + ); + + if (failedProjects.length) { + 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).} + ` + ); + } +}; + +module.exports = run; diff --git a/scripts/release/publish-commands/confirm-version-and-tags.js b/scripts/release/publish-commands/confirm-version-and-tags.js new file mode 100644 index 0000000000000..01948917c4c7a --- /dev/null +++ b/scripts/release/publish-commands/confirm-version-and-tags.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const clear = require('clear'); +const {readJson} = require('fs-extra'); +const {join} = require('path'); +const {confirm} = require('../utils'); + +const run = async ({cwd, packages, tags}) => { + clear(); + + if (tags.length === 1) { + console.log( + chalk`{green ✓} You are about the publish the following packages under the tag {yellow ${tags}}` + ); + } else { + console.log( + chalk`{green ✓} You are about the publish the following packages under the tags {yellow ${tags.join( + ', ' + )}}` + ); + } + + // Cache all package JSONs for easy lookup below. + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packageJSONPath = join( + cwd, + 'build/node_modules', + packageName, + 'package.json' + ); + const packageJSON = await readJson(packageJSONPath); + console.log( + chalk`• {green ${packageName}} @ {yellow ${chalk.yellow( + packageJSON.version + )}}` + ); + } + + await confirm('Do you want to proceed?'); +}; + +// Run this directly because it's fast, +// and logPromise would interfere with console prompting. +module.exports = run; diff --git a/scripts/release/publish-commands/parse-params.js b/scripts/release/publish-commands/parse-params.js index 67f22f6b65d43..443155441e3d2 100644 --- a/scripts/release/publish-commands/parse-params.js +++ b/scripts/release/publish-commands/parse-params.js @@ -8,6 +8,12 @@ const commandLineUsage = require('command-line-usage'); const figlet = require('figlet'); const paramDefinitions = [ + { + name: 'dry', + type: Boolean, + description: 'Dry run command without actually publishing to NPM.', + defaultValue: false, + }, { name: 'tags', type: String, @@ -19,7 +25,7 @@ const paramDefinitions = [ module.exports = () => { const params = commandLineArgs(paramDefinitions); - if (!params.tags) { + if (!params.tags || params.tags.length === 0) { const usage = commandLineUsage([ { content: chalk @@ -29,7 +35,7 @@ module.exports = () => { }, { content: - 'Publishes the current contents of "build/node_modules" to NPM.', + 'Publishes the current contents of "build/node_modules" to NPM.}', }, { header: 'Options', @@ -39,7 +45,11 @@ module.exports = () => { header: 'Examples', content: [ { - desc: 'Example:', + desc: 'Dry run test:', + example: '$ scripts/release/publish.js --dry --tags next', + }, + { + desc: 'Publish a new stable:', example: '$ scripts/release/publish.js --tags next latest', }, ], diff --git a/scripts/release/publish-commands/print-follow-up-instructions.js b/scripts/release/publish-commands/print-follow-up-instructions.js new file mode 100644 index 0000000000000..9aa43a5d5185f --- /dev/null +++ b/scripts/release/publish-commands/print-follow-up-instructions.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const clear = require('clear'); +const {readJsonSync} = require('fs-extra'); + +const run = async ({cwd, packages, tags}) => { + // All packages are built from a single source revision, + // so it is safe to read the commit number from any one of them. + const {commit} = readJsonSync( + `${cwd}/build/node_modules/react/build-info.json` + ); + + // Tags are named after the react version. + const {version} = readJsonSync( + `${cwd}/build/node_modules/react/package.json` + ); + + clear(); + + console.log( + chalk`{red.bold The release has been published but you're not done yet!}` + ); + + if (tags.includes('latest')) { + console.log( + chalk`\n{green Local versions may require updating after a stable release. Please verify the following files:}` + ); + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + console.log(chalk`• packages/{green ${packageName}}/package.json`); + } + console.log(chalk`• packages/{green shared/ReactVersion.js}`); + } + + // Prompt the release engineer to tag the commit and update the CHANGELOG. + // (The script could automatically do this, but this seems safer.) + console.log( + chalk`\n{green Tag the source for this release in Git with the following command:}` + ); + console.log( + chalk` git tag -a {yellow v${version}} -m "v${version}" {yellow ${commit}}` + ); + console.log(chalk` git push origin --tags`); + console.log( + chalk`\n{green Don't forget to update and commit the {white CHANGELOG}.}` + ); + console.log(chalk`\n{green Then fill in the release on GitHub:}`); + console.log( + chalk`{cyan.underline https://github.com/facebook/react/releases/tag/v${version}}\n` + ); +}; + +module.exports = run; diff --git a/scripts/release/publish-commands/prompt-for-otp.js b/scripts/release/publish-commands/prompt-for-otp.js new file mode 100644 index 0000000000000..1fa66c9717484 --- /dev/null +++ b/scripts/release/publish-commands/prompt-for-otp.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const prompt = require('prompt-promise'); + +const run = async () => { + while (true) { + const otp = await prompt('NPM 2-factor auth code: '); + prompt.done(); + + if (otp) { + return otp; + } else { + console.log(chalk`\n{red.bold Two-factor auth is required to publish.}`); + // (Ask again.) + } + } +}; + +module.exports = run; diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js new file mode 100644 index 0000000000000..7cd0e44d8ebd5 --- /dev/null +++ b/scripts/release/publish-commands/publish-to-npm.js @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {exec} = require('child-process-promise'); +const {readJsonSync} = require('fs-extra'); +const {join} = require('path'); +const {confirm, execRead} = require('../utils'); + +const run = async ({cwd, dry, packages, tags}, otp) => { + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const packagePath = join(cwd, 'build/node_modules', packageName); + const {version} = readJsonSync(join(packagePath, 'package.json')); + + // Check if this package version has already been published. + // If so we might be resuming from a previous run. + // We could infer this by comparing the build-info.json, + // But for now the easiest way is just to ask if this is expected. + const info = await execRead(`npm view react@${version}`); + if (info) { + console.log( + chalk`{green react}@{yellow ${version}} has already been published.` + ); + await confirm(chalk`Is this expected?`); + } else { + console.log(chalk`{green ✓} Publishing {green ${packageName}}`); + + // Publish the package and tag it. + if (!dry) { + await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, { + cwd: packagePath, + }); + } else { + console.log(chalk.gray(` cd ${packagePath}`)); + console.log(chalk.gray(` npm publish --tag=${tags[0]} --otp=${otp}`)); + } + + for (let j = 1; j < tags.length; j++) { + if (!dry) { + await exec( + `npm dist-tag add ${packageName}@${version} ${ + tags[j] + } --otp=${otp}`, + {cwd: packagePath} + ); + } else { + console.log( + chalk.gray( + ` npm dist-tag add ${packageName}@${version} ${ + tags[j] + } --otp=${otp}` + ) + ); + } + } + } + } +}; + +module.exports = run; diff --git a/scripts/release/publish-commands/validate-tags.js b/scripts/release/publish-commands/validate-tags.js new file mode 100644 index 0000000000000..8f92412faff64 --- /dev/null +++ b/scripts/release/publish-commands/validate-tags.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const {readJson} = require('fs-extra'); +const {join} = require('path'); + +const run = async ({cwd, packages, tags}) => { + // Prevent a canary release from ever being published as @latest + const packageJSONPath = join( + cwd, + 'build', + 'node_modules', + 'react', + 'package.json' + ); + const {version} = await readJson(packageJSONPath); + if (version.indexOf('0.0.0') === 0) { + if (tags.includes('latest')) { + console.log( + chalk`{red.bold Canary release {white (${version})} cannot be tagged as {yellow latest}.}` + ); + process.exit(1); + } + } +}; + +module.exports = run; diff --git a/scripts/release/publish.js b/scripts/release/publish.js index a034d7b904e49..69299947732e5 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -5,7 +5,13 @@ const {join} = require('path'); const {getPublicPackages, handleError} = require('./utils'); +const checkNPMPermissions = require('./publish-commands/check-npm-permissions'); +const confirmVersionAndTags = require('./publish-commands/confirm-version-and-tags'); const parseParams = require('./publish-commands/parse-params'); +const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions'); +const promptForOTP = require('./publish-commands/prompt-for-otp'); +const publishToNPM = require('./publish-commands/publish-to-npm'); +const validateTags = require('./publish-commands/validate-tags'); const run = async () => { try { @@ -13,17 +19,12 @@ const run = async () => { params.cwd = join(__dirname, '..', '..'); params.packages = await getPublicPackages(); - console.log(params); - - // TODO Require inputs: a list of NPM tags (e.g. --tags=latest, --tags=latest,next). - // TODO Print versions of the local, prepared packages and confirm the tag release. - // TODO Check NPM permissions to ensure that the current user can publish all public packages. - // TODO Publish each package to NPM with the specified version number and tag. - // J.I.T. prompt (or re-prompt) for OTP token if publishing fails. - // TODO Print command for tagging the Git commit the release was originally created from (using build-info.json). - // TODO Print command for creating a changelog. - // TODO Support basic "resume" by checking each package to see if it has already been published - // before publishing it (and skipping it if so). + await validateTags(params); + await confirmVersionAndTags(params); + await checkNPMPermissions(params); + const otp = await promptForOTP(params); + await publishToNPM(params, otp); + await printFollowUpInstructions(params); } catch (error) { handleError(error); } diff --git a/scripts/release/shared-commands/print-prerelease-summary.js b/scripts/release/shared-commands/print-prerelease-summary.js index b7a1691c5efb3..8f1e80bc3628b 100644 --- a/scripts/release/shared-commands/print-prerelease-summary.js +++ b/scripts/release/shared-commands/print-prerelease-summary.js @@ -3,6 +3,7 @@ 'use strict'; const chalk = require('chalk'); +const clear = require('clear'); const {join, relative} = require('path'); module.exports = ({cwd}) => { @@ -11,6 +12,8 @@ module.exports = ({cwd}) => { join(__dirname, '../publish.js') ); + clear(); + const packagingFixturesPath = join(cwd, 'fixtures/packaging'); const standaloneFixturePath = join( cwd, @@ -19,22 +22,24 @@ module.exports = ({cwd}) => { console.log( chalk` - {green.bold A potential release has been prepared!} + {red.bold A release candidate has been prepared but you're not done yet!} - You can review the contents of this release in {yellow.bold ./build/node_modules/} + You can review the contents of this release in {yellow ./build/node_modules/} - {bold.underline Before publishing, please smoke test the packages} + {green.bold Before publishing, please smoke test the packages!} - 1. Open {yellow.bold ${standaloneFixturePath}} in the browser. + 1. Open {yellow ${standaloneFixturePath}} in the browser. 2. It should say {italic "Hello world!"} - 3. Next go to {yellow.bold ${packagingFixturesPath}} and run {bold node build-all.js} - 4. Install the "pushstate-server" module ({bold npm install -g pushstate-server}) - 5. Go to the repo root and {bold pushstate-server -s .} - 6. Open {blue.bold http://localhost:9000/fixtures/packaging} + 3. Next go to {yellow ${packagingFixturesPath}} and run {green node build-all.js} + 4. Install the "pushstate-server" module ({green npm install -g pushstate-server}) + 5. Go to the repo root and {green pushstate-server -s .} + 6. Open {cyan.underline http://localhost:9000/fixtures/packaging} 7. Verify every iframe shows {italic "Hello world!"} After completing the above steps, you can publish this release by running: - {yellow.bold ${publishPath}} - `.replace(/\n +/g, '\n') + {yellow ${publishPath}} + ` + .replace(/\n +/g, '\n') + .trim() ); }; diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 261df04764bb1..cb53b497adbba 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -2,7 +2,8 @@ const chalk = require('chalk'); const {dots} = require('cli-spinners'); -const {exec, spawn} = require('child-process-promise'); +const {exec} = require('child-process-promise'); +const {createPatch} = require('diff'); const {hashElement} = require('folder-hash'); const {readdirSync, readFileSync, statSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); @@ -11,10 +12,12 @@ const {join} = require('path'); const prompt = require('prompt-promise'); const confirm = async message => { - const confirmation = await prompt(chalk`\n${message} {yellow (y/N)} `); + const confirmation = await prompt( + chalk`\n{red.bold ${message}} (y/{underline N}) ` + ); prompt.done(); if (confirmation !== 'y' && confirmation !== 'Y') { - console.log(chalk.red('Release cancelled.')); + console.log(chalk.red('\nRelease cancelled.')); process.exit(0); } }; @@ -25,16 +28,6 @@ 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 getBuildInfo = async () => { const cwd = join(__dirname, '..', '..'); @@ -45,7 +38,14 @@ const getBuildInfo = async () => { const checksum = await getChecksumForCurrentRevision(cwd); const version = `0.0.0-${commit}`; - return {branch, checksum, commit, version}; + // React version is stored explicitly, separately for DevTools support. + // See updateVersionsForCanary() below for more info. + const packageJSON = await readJson( + join(cwd, 'packages', 'react', 'package.json') + ); + const reactVersion = `${packageJSON.version}-canary-${commit}`; + + return {branch, checksum, commit, reactVersion, version}; }; const getChecksumForCurrentRevision = async cwd => { @@ -90,17 +90,6 @@ const getPublicPackages = () => { }); }; -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 handleError = error => { logUpdate.clear(); @@ -148,43 +137,57 @@ const logPromise = async (promise, text, isLongRunningTask = false) => { } }; -const runYarnTask = async (cwd, task, errorMessage) => { - try { - await exec(`yarn ${task}`, {cwd}); - } catch (error) { - throw Error( - chalk` - ${errorMessage} - - {white ${error.stdout}} - ` - ); - } +const printDiff = (path, beforeContents, afterContents) => { + const patch = createPatch(path, beforeContents, afterContents); + const coloredLines = patch + .split('\n') + .slice(2) // Trim index file + .map((line, index) => { + if (index <= 1) { + return chalk.gray(line); + } + switch (line[0]) { + case '+': + return chalk.green(line); + case '-': + return chalk.red(line); + case ' ': + return line; + case '@': + return null; + case '\\': + return null; + } + }) + .filter(line => line); + console.log(coloredLines.join('\n')); + return patch; }; -const spawnCommand = (command, options) => - spawn(command, { - cwd: join(__dirname, '..', '..'), - encoding: 'utf-8', - env: process.env, - shell: true, - stdio: [process.stdin, process.stdout, process.stderr], - ...options, - }); - -const updateVersionsForCanary = async (cwd, version) => { +// This method is used by both local Node release scripts and Circle CI bash scripts. +// It updates version numbers in package JSONs (both the version field and dependencies), +// As well as the embedded renderer version in "packages/shared/ReactVersion". +// Canaries version numbers use the format of 0.0.0- to be easily recognized (e.g. 0.0.0-57239eac8). +// A separate "React version" is used for the embedded renderer version to support DevTools, +// since it needs to distinguish between different version ranges of React. +// It is based on the version of React in the local package.json (e.g. 16.6.1-canary-57239eac8). +// Both numbers will be replaced if the canary is promoted to a stable release. +const updateVersionsForCanary = async (cwd, reactVersion, version) => { const packages = getPackages(join(cwd, 'packages')); const packagesDir = join(cwd, 'packages'); // Update the shared React version source file. // This is bundled into built renderers. // The promote script will replace this with a final version later. - const reactVersionPath = join(cwd, 'packages/shared/ReactVersion.js'); - const reactVersion = readFileSync(reactVersionPath, 'utf8').replace( + const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js'); + const sourceReactVersion = readFileSync( + sourceReactVersionPath, + 'utf8' + ).replace( /module\.exports = '[^']+';/, - `module.exports = '${version}';` + `module.exports = '${reactVersion}';` ); - writeFileSync(reactVersionPath, reactVersion); + writeFileSync(sourceReactVersionPath, sourceReactVersion); // Update the root package.json. // This is required to pass a later version check script. @@ -225,15 +228,12 @@ const updateVersionsForCanary = async (cwd, version) => { module.exports = { confirm, execRead, - execUnlessDry, getBuildInfo, getChecksumForCurrentRevision, getPackages, getPublicPackages, - getUnexecutedCommands, handleError, logPromise, - runYarnTask, - spawnCommand, + printDiff, updateVersionsForCanary, }; diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index f3eecb747cc1e..c2bfdab10019e 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -114,6 +114,11 @@ child-process-promise@^2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" +clear@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a" + integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw== + cli-cursor@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -204,10 +209,10 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" -diff@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" - integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= +diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== ecc-jsbn@~0.1.1: version "0.1.1" @@ -456,13 +461,6 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -print-diff@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/print-diff/-/print-diff-0.1.1.tgz#e32e59d89f753208629ff74d9a7430df4f3cc899" - integrity sha512-dp36GezMEivgKH/zcLB4eBhJmQM3ewAa1UAqEPXMzU69NQ5wCP8puVBZlDFyt1WEtR5k2UC+3ahg+T5BfmRVGw== - dependencies: - diff "^1.2.1" - promise-polyfill@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.0.2.tgz#d9c86d3dc4dc2df9016e88946defd69b49b41162" From 4d530879c19950fa4fc8b3668cc8f229b187f9c7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 17 Nov 2018 11:50:46 -0800 Subject: [PATCH 07/22] Always log NPM commands (even if not dry run) --- .../publish-commands/publish-to-npm.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js index 7cd0e44d8ebd5..e768c09bec51d 100644 --- a/scripts/release/publish-commands/publish-to-npm.js +++ b/scripts/release/publish-commands/publish-to-npm.js @@ -32,10 +32,9 @@ const run = async ({cwd, dry, packages, tags}, otp) => { await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, { cwd: packagePath, }); - } else { - console.log(chalk.gray(` cd ${packagePath}`)); - console.log(chalk.gray(` npm publish --tag=${tags[0]} --otp=${otp}`)); } + console.log(chalk.gray(` cd ${packagePath}`)); + console.log(chalk.gray(` npm publish --tag=${tags[0]} --otp=${otp}`)); for (let j = 1; j < tags.length; j++) { if (!dry) { @@ -45,15 +44,14 @@ const run = async ({cwd, dry, packages, tags}, otp) => { } --otp=${otp}`, {cwd: packagePath} ); - } else { - console.log( - chalk.gray( - ` npm dist-tag add ${packageName}@${version} ${ - tags[j] - } --otp=${otp}` - ) - ); } + console.log( + chalk.gray( + ` npm dist-tag add ${packageName}@${version} ${ + tags[j] + } --otp=${otp}` + ) + ); } } } From 86d30368a6c4058080373c0fb4c8cdfbcec2a1af Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 17 Nov 2018 12:00:18 -0800 Subject: [PATCH 08/22] Fixed resume check typo/bug --- scripts/release/publish-commands/publish-to-npm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js index e768c09bec51d..e939c5174b274 100644 --- a/scripts/release/publish-commands/publish-to-npm.js +++ b/scripts/release/publish-commands/publish-to-npm.js @@ -18,10 +18,10 @@ const run = async ({cwd, dry, packages, tags}, otp) => { // If so we might be resuming from a previous run. // We could infer this by comparing the build-info.json, // But for now the easiest way is just to ask if this is expected. - const info = await execRead(`npm view react@${version}`); + const info = await execRead(`npm view ${packageName}@${version}`); if (info) { console.log( - chalk`{green react}@{yellow ${version}} has already been published.` + chalk`{green ${packageName}}@{yellow ${version}} has already been published.` ); await confirm(chalk`Is this expected?`); } else { From 43ac8996345404d9f5371c76bec3f706e029dff2 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 18 Nov 2018 08:37:57 -0800 Subject: [PATCH 09/22] Added check for build-info.json in files array --- scripts/release/ci-add-build-info-json.js | 20 ++++++++++++++---- .../add-build-info-json.js | 21 +++++++++++++++---- scripts/release/utils.js | 20 +----------------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/scripts/release/ci-add-build-info-json.js b/scripts/release/ci-add-build-info-json.js index 540db47b91c45..78924c8433da4 100755 --- a/scripts/release/ci-add-build-info-json.js +++ b/scripts/release/ci-add-build-info-json.js @@ -6,19 +6,22 @@ // It is not meant to be run as part of the local build or publish process. // It exists to share code between the Node release scripts and CI bash scripts. +// IMPORTANT: +// Changes below should be mirrored in ./create-canary-commands/add-build-info-json.js + const {exec} = require('child_process'); const {existsSync} = require('fs'); const {join} = require('path'); const run = async () => { - const {writeJson} = require('fs-extra'); - const {getBuildInfo, getPackages} = require('./utils'); + const {writeJson, readJson} = require('fs-extra'); + const {getBuildInfo, getPublicPackages} = require('./utils'); const cwd = join(__dirname, '..', '..'); const {branch, checksum, commit, reactVersion} = await getBuildInfo(); - const packages = getPackages(join(cwd, 'packages')); + const packages = getPublicPackages(join(cwd, 'packages')); const packagesDir = join(cwd, 'packages'); const buildInfoJSON = { @@ -32,8 +35,17 @@ const run = async () => { for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; const packagePath = join(packagesDir, packageName); + const packageJSON = await readJson(join(packagePath, 'package.json')); + + // Verify all public packages include "build-info.json" in the files array. + if (!packageJSON.files.includes('build-info.json')) { + console.error( + `${packageName} must include "build-info.json" in files array.` + ); + process.exit(1); + } - // Add build info JSON to package + // Add build info JSON to package. if (existsSync(join(packagePath, 'npm'))) { const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json'); await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2}); diff --git a/scripts/release/create-canary-commands/add-build-info-json.js b/scripts/release/create-canary-commands/add-build-info-json.js index 2f60331ac50a9..d71ff1dd0b8c1 100644 --- a/scripts/release/create-canary-commands/add-build-info-json.js +++ b/scripts/release/create-canary-commands/add-build-info-json.js @@ -2,13 +2,17 @@ 'use strict'; +// IMPORTANT: +// Changes below should be mirrored in ../ci-add-build-info-json.js + +const chalk = require('chalk'); const {existsSync} = require('fs'); -const {writeJson} = require('fs-extra'); +const {writeJson, readJson} = require('fs-extra'); const {join} = require('path'); -const {getPackages, logPromise} = require('../utils'); +const {getPublicPackages, logPromise} = require('../utils'); const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { - const packages = getPackages(join(tempDirectory, 'packages')); + const packages = getPublicPackages(join(tempDirectory, 'packages')); const packagesDir = join(tempDirectory, 'packages'); const buildInfoJSON = { @@ -22,8 +26,17 @@ const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; const packagePath = join(packagesDir, packageName); + const packageJSON = await readJson(join(packagePath, 'package.json')); + + // Verify all public packages include "build-info.json" in the files array. + if (!packageJSON.files.includes('build-info.json')) { + console.log( + chalk`{red.bold ${packageName} must include "build-info.json" in files array.}` + ); + process.exit(1); + } - // Add build info JSON to package + // Add build info JSON to package. if (existsSync(join(packagePath, 'npm'))) { const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json'); await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2}); diff --git a/scripts/release/utils.js b/scripts/release/utils.js index cb53b497adbba..463c758662076 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -57,23 +57,6 @@ const getChecksumForCurrentRevision = async cwd => { return hashedPackages.hash.slice(0, 7); }; -const getPackages = ( - packagesRoot = join(__dirname, '..', '..', 'packages') -) => { - return readdirSync(packagesRoot).filter(dir => { - const packagePath = join(packagesRoot, dir, 'package.json'); - - if (dir.charAt(0) !== '.' && statSync(packagePath).isFile()) { - const packageJSON = JSON.parse(readFileSync(packagePath)); - - // Skip packages like "shared" and "events" that shouldn't be updated. - return packageJSON.version !== '0.0.0'; - } - - return false; - }); -}; - const getPublicPackages = () => { const packagesRoot = join(__dirname, '..', '..', 'packages'); @@ -173,7 +156,7 @@ const printDiff = (path, beforeContents, afterContents) => { // It is based on the version of React in the local package.json (e.g. 16.6.1-canary-57239eac8). // Both numbers will be replaced if the canary is promoted to a stable release. const updateVersionsForCanary = async (cwd, reactVersion, version) => { - const packages = getPackages(join(cwd, 'packages')); + const packages = getPublicPackages(join(cwd, 'packages')); const packagesDir = join(cwd, 'packages'); // Update the shared React version source file. @@ -230,7 +213,6 @@ module.exports = { execRead, getBuildInfo, getChecksumForCurrentRevision, - getPackages, getPublicPackages, handleError, logPromise, From 1018c3065371294408352130486fc29df14a9293 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 18 Nov 2018 10:55:28 -0800 Subject: [PATCH 10/22] Replaced chalk usage with shared theme for consistancy --- .../add-build-info-json.js | 6 ++-- .../confirm-automated-testing.js | 4 +-- .../copy-repo-to-temp-directory.js | 6 ++-- .../update-version-numbers.js | 4 +-- scripts/release/package.json | 1 - .../check-environment-variables.js | 27 +++++++------- .../download-build-artifacts.js | 6 ++-- .../prepare-canary-commands/parse-params.js | 8 ----- .../check-out-packages.js | 4 +-- .../confirm-stable-version-numbers.js | 12 ++++--- .../prepare-stable-commands/parse-params.js | 8 ----- .../update-stable-version-numbers.js | 14 ++++---- .../publish-commands/check-npm-permissions.js | 20 ++++++----- .../confirm-version-and-tags.js | 10 +++--- .../release/publish-commands/parse-params.js | 8 ----- .../print-follow-up-instructions.js | 30 ++++++++++------ .../publish-commands/prompt-for-otp.js | 5 +-- .../publish-commands/publish-to-npm.js | 16 +++++---- .../release/publish-commands/validate-tags.js | 4 +-- .../print-prerelease-summary.js | 32 +++++++---------- scripts/release/theme.js | 35 +++++++++++++++++++ scripts/release/utils.js | 27 +++++++------- scripts/release/yarn.lock | 4 --- 23 files changed, 152 insertions(+), 139 deletions(-) create mode 100644 scripts/release/theme.js diff --git a/scripts/release/create-canary-commands/add-build-info-json.js b/scripts/release/create-canary-commands/add-build-info-json.js index d71ff1dd0b8c1..a62c8b837bd7e 100644 --- a/scripts/release/create-canary-commands/add-build-info-json.js +++ b/scripts/release/create-canary-commands/add-build-info-json.js @@ -5,11 +5,11 @@ // IMPORTANT: // Changes below should be mirrored in ../ci-add-build-info-json.js -const chalk = require('chalk'); const {existsSync} = require('fs'); const {writeJson, readJson} = require('fs-extra'); const {join} = require('path'); const {getPublicPackages, logPromise} = require('../utils'); +const theme = require('../theme'); const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { const packages = getPublicPackages(join(tempDirectory, 'packages')); @@ -30,8 +30,8 @@ const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { // Verify all public packages include "build-info.json" in the files array. if (!packageJSON.files.includes('build-info.json')) { - console.log( - chalk`{red.bold ${packageName} must include "build-info.json" in files array.}` + console.error( + theme`{error ${packageName} must include "build-info.json" in files array.}` ); process.exit(1); } diff --git a/scripts/release/create-canary-commands/confirm-automated-testing.js b/scripts/release/create-canary-commands/confirm-automated-testing.js index 9e706905f32d2..8e28e92b561b1 100644 --- a/scripts/release/create-canary-commands/confirm-automated-testing.js +++ b/scripts/release/create-canary-commands/confirm-automated-testing.js @@ -2,15 +2,15 @@ 'use strict'; -const chalk = require('chalk'); const clear = require('clear'); const {confirm} = require('../utils'); +const theme = require('../theme'); const run = async () => { clear(); console.log( - chalk.red( + theme.caution( 'This script does not run any automated tests.' + 'You should run them manually before creating a canary release.' ) diff --git a/scripts/release/create-canary-commands/copy-repo-to-temp-directory.js b/scripts/release/create-canary-commands/copy-repo-to-temp-directory.js index 0490efbe67d17..3d859b7e23c6c 100644 --- a/scripts/release/create-canary-commands/copy-repo-to-temp-directory.js +++ b/scripts/release/create-canary-commands/copy-repo-to-temp-directory.js @@ -2,11 +2,11 @@ 'use strict'; -const chalk = require('chalk'); const {exec} = require('child-process-promise'); const {join} = require('path'); const {tmpdir} = require('os'); const {logPromise} = require('../utils'); +const theme = require('../theme'); const run = async ({commit, cwd, tempDirectory}) => { const directory = `react-${commit}`; @@ -27,8 +27,8 @@ const run = async ({commit, cwd, tempDirectory}) => { module.exports = async params => { return logPromise( run(params), - `Copying React repo to temporary directory (${chalk.gray( + theme`Copying React repo to temporary directory ({path ${ params.tempDirectory - )})` + }})` ); }; diff --git a/scripts/release/create-canary-commands/update-version-numbers.js b/scripts/release/create-canary-commands/update-version-numbers.js index fe4da92546bce..1c178c83c08c9 100644 --- a/scripts/release/create-canary-commands/update-version-numbers.js +++ b/scripts/release/create-canary-commands/update-version-numbers.js @@ -2,12 +2,12 @@ 'use strict'; -const chalk = require('chalk'); const {logPromise, updateVersionsForCanary} = require('../utils'); +const theme = require('../theme'); module.exports = async ({reactVersion, tempDirectory, version}) => { return logPromise( updateVersionsForCanary(tempDirectory, reactVersion, version), - `Updating version numbers (${chalk.yellow.bold(version)})` + theme`Updating version numbers ({version ${version}})` ); }; diff --git a/scripts/release/package.json b/scripts/release/package.json index cce43a70ad431..f0f1fe033d1ac 100644 --- a/scripts/release/package.json +++ b/scripts/release/package.json @@ -12,7 +12,6 @@ "command-line-args": "^4.0.7", "command-line-usage": "^4.0.1", "diff": "^3.5.0", - "figlet": "^1.2.0", "folder-hash": "^2.1.2", "fs-extra": "^4.0.2", "log-update": "^2.1.0", diff --git a/scripts/release/prepare-canary-commands/check-environment-variables.js b/scripts/release/prepare-canary-commands/check-environment-variables.js index c284ab992ad7d..dcb7785d99314 100644 --- a/scripts/release/prepare-canary-commands/check-environment-variables.js +++ b/scripts/release/prepare-canary-commands/check-environment-variables.js @@ -2,26 +2,29 @@ 'use strict'; -const chalk = require('chalk'); +const theme = require('../theme'); module.exports = () => { if (!process.env.CIRCLE_CI_API_TOKEN) { - throw Error( - chalk` - {red Missing CircleCI API token} + console.error( + theme` + {error Missing CircleCI API token} - {white The CircleCI API is used to download build artifacts.} - {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:} + The CircleCI API is used to download build artifacts. + This API requires a token which must be exposed via a {underline CIRCLE_CI_API_TOKEN} environment var. + In order to run this script you will need to create your own API token. + Instructions can be found at: - {blue.bold https://circleci.com/docs/api/v1-reference/#getting-started} + {link 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:} + To make this token available to the release script, add it to your {path .bash_profile} like so: - {gray # React release script} - {white export CIRCLE_CI_API_TOKEN=} + {dimmed # React release script} + export CIRCLE_CI_API_TOKEN= ` + .replace(/\n +/g, '\n') + .trim() ); + process.exit(1); } }; diff --git a/scripts/release/prepare-canary-commands/download-build-artifacts.js b/scripts/release/prepare-canary-commands/download-build-artifacts.js index ec6bf1dfbf3e5..f7c3ab2a0c453 100644 --- a/scripts/release/prepare-canary-commands/download-build-artifacts.js +++ b/scripts/release/prepare-canary-commands/download-build-artifacts.js @@ -2,12 +2,12 @@ 'use strict'; -const chalk = require('chalk'); const http = require('request-promise-json'); const {exec} = require('child-process-promise'); const {readdirSync} = require('fs'); const {readJsonSync} = require('fs-extra'); const {logPromise} = require('../utils'); +const theme = require('../theme'); const run = async ({build, cwd}) => { // https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci @@ -53,8 +53,6 @@ const run = async ({build, cwd}) => { module.exports = async ({build, cwd}) => { return logPromise( run({build, cwd}), - `Downloading artifacts from Circle CI for build ${chalk.yellow.bold( - `${build}` - )}` + theme`Downloading artifacts from Circle CI for build {build ${build}}` ); }; diff --git a/scripts/release/prepare-canary-commands/parse-params.js b/scripts/release/prepare-canary-commands/parse-params.js index 2212296505f79..cb32adfa61307 100644 --- a/scripts/release/prepare-canary-commands/parse-params.js +++ b/scripts/release/prepare-canary-commands/parse-params.js @@ -2,10 +2,8 @@ 'use strict'; -const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); -const figlet = require('figlet'); const paramDefinitions = [ { @@ -21,12 +19,6 @@ module.exports = () => { if (!params.build) { const usage = commandLineUsage([ - { - content: chalk - .hex('#61dafb') - .bold(figlet.textSync('react', {font: 'Graffiti'})), - raw: true, - }, { content: 'Prepare a Circle CI build to be published to NPM as a canary.', diff --git a/scripts/release/prepare-stable-commands/check-out-packages.js b/scripts/release/prepare-stable-commands/check-out-packages.js index a49a37377fbcd..d29dec97196ee 100644 --- a/scripts/release/prepare-stable-commands/check-out-packages.js +++ b/scripts/release/prepare-stable-commands/check-out-packages.js @@ -2,10 +2,10 @@ 'use strict'; -const chalk = require('chalk'); const {exec} = require('child-process-promise'); const {join} = require('path'); const {logPromise} = require('../utils'); +const theme = require('../theme'); const run = async ({cwd, packages, version}) => { // Cleanup from previous builds @@ -24,6 +24,6 @@ const run = async ({cwd, packages, version}) => { module.exports = async params => { return logPromise( run(params), - `Checking out canary from NPM ${chalk.yellow(params.version)}` + theme`Checking out canary from NPM {version ${params.version}}` ); }; diff --git a/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js b/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js index a5b2e67c7684e..d00bb38576454 100644 --- a/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js +++ b/scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js @@ -2,9 +2,9 @@ 'use strict'; -const chalk = require('chalk'); const prompt = require('prompt-promise'); const semver = require('semver'); +const theme = require('../theme'); const run = async (params, versionsMap) => { const groupedVersionsMap = new Map(); @@ -25,13 +25,13 @@ const run = async (params, versionsMap) => { const entries = [...groupedVersionsMap.entries()]; for (let i = 0; i < entries.length; i++) { const [bestGuessVersion, packages] = entries[i]; - const packageNames = chalk.green(packages.join(', ')); + const packageNames = packages.map(name => theme.package(name)).join(', '); const defaultVersion = bestGuessVersion - ? chalk.yellow(` (default ${bestGuessVersion})`) + ? theme.version(` (default ${bestGuessVersion})`) : ''; const version = (await prompt( - chalk`{green ✓} Version for ${packageNames}${defaultVersion}: ` + theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: ` )) || bestGuessVersion; prompt.done(); @@ -43,7 +43,9 @@ const run = async (params, versionsMap) => { versionsMap.set(packageName, version); }); } catch (error) { - console.log(chalk`{red ✘ Version {white ${version}} is invalid.}`); + console.log( + theme`{spinnerError ✘} Version {version ${version}} is invalid.` + ); // Prompt again i--; diff --git a/scripts/release/prepare-stable-commands/parse-params.js b/scripts/release/prepare-stable-commands/parse-params.js index 462b69bbae209..2d553a9047e4a 100644 --- a/scripts/release/prepare-stable-commands/parse-params.js +++ b/scripts/release/prepare-stable-commands/parse-params.js @@ -2,10 +2,8 @@ 'use strict'; -const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); -const figlet = require('figlet'); const paramDefinitions = [ { @@ -20,12 +18,6 @@ module.exports = () => { if (!params.version) { const usage = commandLineUsage([ - { - content: chalk - .hex('#61dafb') - .bold(figlet.textSync('react', {font: 'Graffiti'})), - raw: true, - }, { content: 'Prepare a published canary release to be promoted to stable.', }, diff --git a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js index ed1893e348c26..9fab86c02eb81 100644 --- a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js +++ b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js @@ -2,12 +2,12 @@ 'use strict'; -const chalk = require('chalk'); const clear = require('clear'); const {readFileSync, writeFileSync} = require('fs'); const {readJson, writeJson} = require('fs-extra'); const {join, relative} = require('path'); const {confirm, execRead, printDiff} = require('../utils'); +const theme = require('../theme'); const run = async ({cwd, packages, version}, versionsMap) => { const nodeModulesPath = join(cwd, 'build/node_modules'); @@ -90,9 +90,9 @@ const run = async ({cwd, packages, version}, versionsMap) => { for (let dependencyName in maybeDependency) { if (packages.includes(dependencyName)) { console.log( - chalk`• {green ${dependencyName}} @ {yellow ${ + theme`• {package ${dependencyName}} {version ${ maybeDependency[dependencyName] - }} (${label})` + }} {dimmed ${label}}` ); } } @@ -103,8 +103,8 @@ const run = async ({cwd, packages, version}, versionsMap) => { const packageJSONPath = join(nodeModulesPath, packageName, 'package.json'); const packageJSON = await readJson(packageJSONPath); console.log( - chalk`\n{green ${packageName}} @ {yellow ${chalk.yellow( - versionsMap.get(packageName) + theme`\n{package ${packageName}} {version ${versionsMap.get( + packageName )}}` ); printDependencies(packageJSON.dependencies, 'dependency'); @@ -156,9 +156,9 @@ const run = async ({cwd, packages, version}, versionsMap) => { }); } writeFileSync(diffPath, diff, {cwd}); - console.log(chalk.green(`\n${numFilesModified} files have been updated.`)); + console.log(theme.header(`\n${numFilesModified} files have been updated.`)); console.log( - chalk`A full diff is availbale at {yellow ${relative(cwd, diffPath)}}.` + theme`A full diff is availbale at {path ${relative(cwd, diffPath)}}.` ); await confirm('Do changes changes look correct?'); }; diff --git a/scripts/release/publish-commands/check-npm-permissions.js b/scripts/release/publish-commands/check-npm-permissions.js index 8773cf4703dbe..834053100571b 100644 --- a/scripts/release/publish-commands/check-npm-permissions.js +++ b/scripts/release/publish-commands/check-npm-permissions.js @@ -2,8 +2,8 @@ 'use strict'; -const chalk = require('chalk'); const {execRead, logPromise} = require('../utils'); +const theme = require('../theme'); const run = async ({cwd, packages, version}) => { const currentUser = await execRead('npm whoami'); @@ -22,18 +22,22 @@ const run = async ({cwd, packages, version}) => { await logPromise( Promise.all(packages.map(checkProject)), - `Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions` + theme`Checking NPM permissions for {underline ${currentUser}}.` ); if (failedProjects.length) { - 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).} + console.error( + theme` + {error Insufficient NPM permissions} + \nNPM user {underline ${currentUser}} is not an owner for: ${failedProjects + .map(name => theme.package(name)) + .join(', ')} + \nPlease contact a React team member to be added to the above project(s). ` + .replace(/\n +/g, '\n') + .trim() ); + process.exit(1); } }; diff --git a/scripts/release/publish-commands/confirm-version-and-tags.js b/scripts/release/publish-commands/confirm-version-and-tags.js index 01948917c4c7a..bfc5cbcfcc208 100644 --- a/scripts/release/publish-commands/confirm-version-and-tags.js +++ b/scripts/release/publish-commands/confirm-version-and-tags.js @@ -2,22 +2,22 @@ 'use strict'; -const chalk = require('chalk'); const clear = require('clear'); const {readJson} = require('fs-extra'); const {join} = require('path'); const {confirm} = require('../utils'); +const theme = require('../theme'); const run = async ({cwd, packages, tags}) => { clear(); if (tags.length === 1) { console.log( - chalk`{green ✓} You are about the publish the following packages under the tag {yellow ${tags}}` + theme`{spinnerSuccess ✓} You are about the publish the following packages under the tag {tag ${tags}}` ); } else { console.log( - chalk`{green ✓} You are about the publish the following packages under the tags {yellow ${tags.join( + theme`{spinnerSuccess ✓} You are about the publish the following packages under the tags {tag ${tags.join( ', ' )}}` ); @@ -34,9 +34,7 @@ const run = async ({cwd, packages, tags}) => { ); const packageJSON = await readJson(packageJSONPath); console.log( - chalk`• {green ${packageName}} @ {yellow ${chalk.yellow( - packageJSON.version - )}}` + theme`• {package ${packageName}} {version ${packageJSON.version}}` ); } diff --git a/scripts/release/publish-commands/parse-params.js b/scripts/release/publish-commands/parse-params.js index 443155441e3d2..bacd9eb5e3c1f 100644 --- a/scripts/release/publish-commands/parse-params.js +++ b/scripts/release/publish-commands/parse-params.js @@ -2,10 +2,8 @@ 'use strict'; -const chalk = require('chalk'); const commandLineArgs = require('command-line-args'); const commandLineUsage = require('command-line-usage'); -const figlet = require('figlet'); const paramDefinitions = [ { @@ -27,12 +25,6 @@ module.exports = () => { if (!params.tags || params.tags.length === 0) { const usage = commandLineUsage([ - { - content: chalk - .hex('#61dafb') - .bold(figlet.textSync('react', {font: 'Graffiti'})), - raw: true, - }, { content: 'Publishes the current contents of "build/node_modules" to NPM.}', diff --git a/scripts/release/publish-commands/print-follow-up-instructions.js b/scripts/release/publish-commands/print-follow-up-instructions.js index 9aa43a5d5185f..a81aa30bb3db0 100644 --- a/scripts/release/publish-commands/print-follow-up-instructions.js +++ b/scripts/release/publish-commands/print-follow-up-instructions.js @@ -2,9 +2,9 @@ 'use strict'; -const chalk = require('chalk'); const clear = require('clear'); const {readJsonSync} = require('fs-extra'); +const theme = require('../theme'); const run = async ({cwd, packages, tags}) => { // All packages are built from a single source revision, @@ -21,36 +21,44 @@ const run = async ({cwd, packages, tags}) => { clear(); console.log( - chalk`{red.bold The release has been published but you're not done yet!}` + theme.caution`The release has been published but you're not done yet!` ); if (tags.includes('latest')) { console.log( - chalk`\n{green Local versions may require updating after a stable release. Please verify the following files:}` + theme.header`\nLocal versions may require updating after a stable release. Please verify the following files:` ); for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; - console.log(chalk`• packages/{green ${packageName}}/package.json`); + console.log(theme.path`• packages/%s/package.json`, packageName); } - console.log(chalk`• packages/{green shared/ReactVersion.js}`); + console.log(theme.path`• packages/shared/ReactVersion.js`); } // Prompt the release engineer to tag the commit and update the CHANGELOG. // (The script could automatically do this, but this seems safer.) + console.log(); console.log( - chalk`\n{green Tag the source for this release in Git with the following command:}` + theme.header`Tag the source for this release in Git with the following command:` ); console.log( - chalk` git tag -a {yellow v${version}} -m "v${version}" {yellow ${commit}}` + theme` {command git tag -a v}{version %s} {command -m "v%s"} {version %s}`, + version, + version, + commit ); - console.log(chalk` git push origin --tags`); + console.log(theme.command` git push origin --tags`); + console.log(); console.log( - chalk`\n{green Don't forget to update and commit the {white CHANGELOG}.}` + theme`{header Don't forget to update and commit the }{path CHANGELOG}` ); - console.log(chalk`\n{green Then fill in the release on GitHub:}`); + console.log(); + console.log(theme.header`Then fill in the release on GitHub:`); console.log( - chalk`{cyan.underline https://github.com/facebook/react/releases/tag/v${version}}\n` + theme.link`https://github.com/facebook/react/releases/tag/v%s`, + version ); + console.log(); }; module.exports = run; diff --git a/scripts/release/publish-commands/prompt-for-otp.js b/scripts/release/publish-commands/prompt-for-otp.js index 1fa66c9717484..c89c2884630e4 100644 --- a/scripts/release/publish-commands/prompt-for-otp.js +++ b/scripts/release/publish-commands/prompt-for-otp.js @@ -2,8 +2,8 @@ 'use strict'; -const chalk = require('chalk'); const prompt = require('prompt-promise'); +const theme = require('../theme'); const run = async () => { while (true) { @@ -13,7 +13,8 @@ const run = async () => { if (otp) { return otp; } else { - console.log(chalk`\n{red.bold Two-factor auth is required to publish.}`); + console.log(); + console.log(theme.error`Two-factor auth is required to publish.`); // (Ask again.) } } diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js index e939c5174b274..34422494ad8dc 100644 --- a/scripts/release/publish-commands/publish-to-npm.js +++ b/scripts/release/publish-commands/publish-to-npm.js @@ -2,11 +2,11 @@ 'use strict'; -const chalk = require('chalk'); const {exec} = require('child-process-promise'); const {readJsonSync} = require('fs-extra'); const {join} = require('path'); const {confirm, execRead} = require('../utils'); +const theme = require('../theme'); const run = async ({cwd, dry, packages, tags}, otp) => { for (let i = 0; i < packages.length; i++) { @@ -21,11 +21,13 @@ const run = async ({cwd, dry, packages, tags}, otp) => { const info = await execRead(`npm view ${packageName}@${version}`); if (info) { console.log( - chalk`{green ${packageName}}@{yellow ${version}} has already been published.` + theme`{package ${packageName}} {version ${version}} has already been published.` ); - await confirm(chalk`Is this expected?`); + await confirm('Is this expected?'); } else { - console.log(chalk`{green ✓} Publishing {green ${packageName}}`); + console.log( + theme`{spinnerSuccess ✓} Publishing {package ${packageName}}` + ); // Publish the package and tag it. if (!dry) { @@ -33,8 +35,8 @@ const run = async ({cwd, dry, packages, tags}, otp) => { cwd: packagePath, }); } - console.log(chalk.gray(` cd ${packagePath}`)); - console.log(chalk.gray(` npm publish --tag=${tags[0]} --otp=${otp}`)); + console.log(theme.command(` cd ${packagePath}`)); + console.log(theme.command(` npm publish --tag=${tags[0]} --otp=${otp}`)); for (let j = 1; j < tags.length; j++) { if (!dry) { @@ -46,7 +48,7 @@ const run = async ({cwd, dry, packages, tags}, otp) => { ); } console.log( - chalk.gray( + theme.command( ` npm dist-tag add ${packageName}@${version} ${ tags[j] } --otp=${otp}` diff --git a/scripts/release/publish-commands/validate-tags.js b/scripts/release/publish-commands/validate-tags.js index 8f92412faff64..0021511da3488 100644 --- a/scripts/release/publish-commands/validate-tags.js +++ b/scripts/release/publish-commands/validate-tags.js @@ -2,9 +2,9 @@ 'use strict'; -const chalk = require('chalk'); const {readJson} = require('fs-extra'); const {join} = require('path'); +const theme = require('../theme'); const run = async ({cwd, packages, tags}) => { // Prevent a canary release from ever being published as @latest @@ -19,7 +19,7 @@ const run = async ({cwd, packages, tags}) => { if (version.indexOf('0.0.0') === 0) { if (tags.includes('latest')) { console.log( - chalk`{red.bold Canary release {white (${version})} cannot be tagged as {yellow latest}.}` + theme`{error Canary release} {version ${version}} {error cannot be tagged as} {tag latest}` ); process.exit(1); } diff --git a/scripts/release/shared-commands/print-prerelease-summary.js b/scripts/release/shared-commands/print-prerelease-summary.js index 8f1e80bc3628b..bb90b70e07f58 100644 --- a/scripts/release/shared-commands/print-prerelease-summary.js +++ b/scripts/release/shared-commands/print-prerelease-summary.js @@ -2,9 +2,9 @@ 'use strict'; -const chalk = require('chalk'); const clear = require('clear'); const {join, relative} = require('path'); +const theme = require('../theme'); module.exports = ({cwd}) => { const publishPath = relative( @@ -14,30 +14,24 @@ module.exports = ({cwd}) => { clear(); - const packagingFixturesPath = join(cwd, 'fixtures/packaging'); - const standaloneFixturePath = join( - cwd, - 'fixtures/packaging/babel-standalone/dev.html' - ); - console.log( - chalk` - {red.bold A release candidate has been prepared but you're not done yet!} + theme` + {caution A release candidate has been prepared but you're not done yet!} - You can review the contents of this release in {yellow ./build/node_modules/} + You can review the contents of this release in {path ./build/node_modules/} - {green.bold Before publishing, please smoke test the packages!} + {header Before publishing, please smoke test the packages!} - 1. Open {yellow ${standaloneFixturePath}} in the browser. - 2. It should say {italic "Hello world!"} - 3. Next go to {yellow ${packagingFixturesPath}} and run {green node build-all.js} - 4. Install the "pushstate-server" module ({green npm install -g pushstate-server}) - 5. Go to the repo root and {green pushstate-server -s .} - 6. Open {cyan.underline http://localhost:9000/fixtures/packaging} - 7. Verify every iframe shows {italic "Hello world!"} + 1. Open {path ./fixtures/packaging/babel-standalone/dev.html} in the browser. + 2. It should say {quote "Hello world!"} + 3. Next go to {path ./fixtures/packaging} and run {command node build-all.js} + 4. Install the pushstate-server module ({command npm install -g pushstate-server}) + 5. Go to the repo root and {command pushstate-server -s .} + 6. Open {link http://localhost:9000/fixtures/packaging} + 7. Verify every iframe shows {quote "Hello world!"} After completing the above steps, you can publish this release by running: - {yellow ${publishPath}} + {path ${publishPath}} ` .replace(/\n +/g, '\n') .trim() diff --git a/scripts/release/theme.js b/scripts/release/theme.js new file mode 100644 index 0000000000000..5c23185cba8ce --- /dev/null +++ b/scripts/release/theme.js @@ -0,0 +1,35 @@ +'use strict'; + +const chalk = require('chalk'); + +const colors = { + blue: '#0091ea', + gray: '#78909c', + green: '#00c853', + red: '#d50000', + yellow: '#ffd600', +}; + +const theme = chalk.constructor(); +theme.package = theme.hex(colors.green); +theme.version = theme.hex(colors.yellow); +theme.tag = theme.hex(colors.yellow); +theme.build = theme.hex(colors.yellow); +theme.error = theme.hex(colors.red).bold; +theme.dimmed = theme.hex(colors.gray); +theme.caution = theme.hex(colors.red).bold; +theme.link = theme.hex(colors.blue).underline.italic; +theme.header = theme.hex(colors.green).bold; +theme.path = theme.hex(colors.gray).italic; +theme.command = theme.hex(colors.gray); +theme.quote = theme.italic; + +theme.diffHeader = theme.hex(colors.gray); +theme.diffAdded = theme.hex(colors.green); +theme.diffRemoved = theme.hex(colors.red); + +theme.spinnerInProgress = theme.hex(colors.yellow); +theme.spinnerError = theme.hex(colors.red); +theme.spinnerSuccess = theme.hex(colors.green); + +module.exports = theme; diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 463c758662076..be11db17d9d54 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -1,6 +1,5 @@ 'use strict'; -const chalk = require('chalk'); const {dots} = require('cli-spinners'); const {exec} = require('child-process-promise'); const {createPatch} = require('diff'); @@ -10,14 +9,13 @@ const {readJson, writeJson} = require('fs-extra'); const logUpdate = require('log-update'); const {join} = require('path'); const prompt = require('prompt-promise'); +const theme = require('./theme'); const confirm = async message => { - const confirmation = await prompt( - chalk`\n{red.bold ${message}} (y/{underline N}) ` - ); + const confirmation = await prompt(theme`\n{caution ${message}} (y/N) `); prompt.done(); if (confirmation !== 'y' && confirmation !== 'Y') { - console.log(chalk.red('\nRelease cancelled.')); + console.log(theme`\n{caution Release cancelled.}`); process.exit(0); } }; @@ -79,11 +77,7 @@ const handleError = error => { 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 - )}` - ); + console.log(theme`{error ${message}}\n\n{path ${stack}}`); process.exit(1); }; @@ -100,7 +94,9 @@ const logPromise = async (promise, text, isLongRunningTask = false) => { const id = setInterval(() => { index = ++index % frames.length; logUpdate( - `${chalk.yellow(frames[index])} ${text} ${chalk.gray(inProgressMessage)}` + theme`{spinnerInProgress ${ + frames[index] + }} ${text} {dimmed ${inProgressMessage}}` ); }, interval); @@ -109,7 +105,7 @@ const logPromise = async (promise, text, isLongRunningTask = false) => { clearInterval(id); - logUpdate(`${chalk.green('✓')} ${text}`); + logUpdate(theme`{spinnerSuccess ✓} ${text}`); logUpdate.done(); return returnValue; @@ -127,13 +123,13 @@ const printDiff = (path, beforeContents, afterContents) => { .slice(2) // Trim index file .map((line, index) => { if (index <= 1) { - return chalk.gray(line); + return theme.diffHeader(line); } switch (line[0]) { case '+': - return chalk.green(line); + return theme.diffAdded(line); case '-': - return chalk.red(line); + return theme.diffRemoved(line); case ' ': return line; case '@': @@ -217,5 +213,6 @@ module.exports = { handleError, logPromise, printDiff, + theme, updateVersionsForCanary, }; diff --git a/scripts/release/yarn.lock b/scripts/release/yarn.lock index c2bfdab10019e..f46257a8e524d 100644 --- a/scripts/release/yarn.lock +++ b/scripts/release/yarn.lock @@ -236,10 +236,6 @@ 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 baecb2e6a118ac947a1d91445ac6ee56ce8bb875 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 19 Nov 2018 10:14:09 -0800 Subject: [PATCH 11/22] Updated release script README --- scripts/release/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/release/README.md b/scripts/release/README.md index 78b431981a3c4..2eda549ed76ad 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -4,7 +4,7 @@ The release process consists of several phases, each one represented by one of t A typical release goes like this: 1. When a commit is pushed to the React repo, [Circle CI](https://circleci.com/gh/facebook/react/) will build all release bundles and run unit tests against both the source code and the built bundles. -2. Next the release is published as a canary using the [`prepare-canary`](#prepare-canary) and [`publish`](#publish) scripts. (Currently this process is manual but will be automated in the future using [GitHub "actions"](https://github.com/features/actions).) +2. Next the release is published as a canary using the [`prepare-canary`](#prepare-canary) and [`publish`](#publish) scripts. (Currently this process is manual but might be automated in the future using [GitHub "actions"](https://github.com/features/actions).) 3. Finally, a canary releases can be promoted to stable using the [`prepare-stable`](#prepare-stable) and [`publish`](#publish) scripts. (This process is always manual.) One or more release scripts are used for each of the above phases. Learn more about these scripts below: @@ -18,6 +18,8 @@ Creates a canary build from the current (local) Git revision. **This script is an escape hatch.** It allows a canary release to be created without pushing a commit to be verified by Circle CI. **It does not run any automated unit tests.** Testing is solely the responsibility of the release engineer. +Note that this script git-archives the React repo (at the current revision) to a temporary directory before building, so **uncommitted changes are not included in the build**. + #### Example usage To create a canary from the current branch and revision: ```sh From 26a00aa2810b053accf73d6262d1c82bb16771ba Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 19 Nov 2018 11:06:05 -0800 Subject: [PATCH 12/22] Store error codes JSON as a build artifact also --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c1735f674f668..4536865fea183 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,4 +42,7 @@ jobs: - node_modules - store_artifacts: - path: ./node_modules.tgz \ No newline at end of file + path: ./node_modules.tgz + + - store_artifacts: + path: ./scripts/error-codes/codes.json \ No newline at end of file From b3d1a81a958c3031a521b425cc357123e6db698a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 19 Nov 2018 11:32:09 -0800 Subject: [PATCH 13/22] Store Circle CI build number in build-info.json This commit also temporarily changes an error message so I can verify the updated error codes JSON --- packages/shared/HostConfigWithNoHydration.js | 2 +- scripts/release/ci-add-build-info-json.js | 9 ++++++++- .../create-canary-commands/add-build-info-json.js | 1 + scripts/release/utils.js | 6 +++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/shared/HostConfigWithNoHydration.js b/packages/shared/HostConfigWithNoHydration.js index 36ac74184cae3..3b62d7135543c 100644 --- a/packages/shared/HostConfigWithNoHydration.js +++ b/packages/shared/HostConfigWithNoHydration.js @@ -15,7 +15,7 @@ import invariant from 'shared/invariant'; function shim(...args: any) { invariant( false, - 'The current renderer does not support hydration. ' + + 'The current renderer does not support hydration! ' + 'This error is likely caused by a bug in React. ' + 'Please file an issue.', ); diff --git a/scripts/release/ci-add-build-info-json.js b/scripts/release/ci-add-build-info-json.js index 78924c8433da4..4a42038f65cf0 100755 --- a/scripts/release/ci-add-build-info-json.js +++ b/scripts/release/ci-add-build-info-json.js @@ -19,13 +19,20 @@ const run = async () => { const cwd = join(__dirname, '..', '..'); - const {branch, checksum, commit, reactVersion} = await getBuildInfo(); + const { + branch, + buildNumber, + checksum, + commit, + reactVersion, + } = await getBuildInfo(); const packages = getPublicPackages(join(cwd, 'packages')); const packagesDir = join(cwd, 'packages'); const buildInfoJSON = { branch, + buildNumber, checksum, commit, environment: 'ci', diff --git a/scripts/release/create-canary-commands/add-build-info-json.js b/scripts/release/create-canary-commands/add-build-info-json.js index a62c8b837bd7e..10ff6df371fec 100644 --- a/scripts/release/create-canary-commands/add-build-info-json.js +++ b/scripts/release/create-canary-commands/add-build-info-json.js @@ -17,6 +17,7 @@ const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => { const buildInfoJSON = { branch, + buildNumber: null, checksum, commit, environment: 'local', diff --git a/scripts/release/utils.js b/scripts/release/utils.js index be11db17d9d54..0e3838730d9a3 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -36,6 +36,10 @@ const getBuildInfo = async () => { const checksum = await getChecksumForCurrentRevision(cwd); const version = `0.0.0-${commit}`; + // Only available for Circle CI builds. + // https://circleci.com/docs/2.0/env-vars/ + const buildNumber = process.env.CIRCLE_BUILD_NUM; + // React version is stored explicitly, separately for DevTools support. // See updateVersionsForCanary() below for more info. const packageJSON = await readJson( @@ -43,7 +47,7 @@ const getBuildInfo = async () => { ); const reactVersion = `${packageJSON.version}-canary-${commit}`; - return {branch, checksum, commit, reactVersion, version}; + return {branch, buildNumber, checksum, commit, reactVersion, version}; }; const getChecksumForCurrentRevision = async cwd => { From e624d1403cb7be35f4e38fba559644e64803c72c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 19 Nov 2018 11:32:35 -0800 Subject: [PATCH 14/22] Revert (intentional) temporary error message change --- packages/shared/HostConfigWithNoHydration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/HostConfigWithNoHydration.js b/packages/shared/HostConfigWithNoHydration.js index 3b62d7135543c..36ac74184cae3 100644 --- a/packages/shared/HostConfigWithNoHydration.js +++ b/packages/shared/HostConfigWithNoHydration.js @@ -15,7 +15,7 @@ import invariant from 'shared/invariant'; function shim(...args: any) { invariant( false, - 'The current renderer does not support hydration! ' + + 'The current renderer does not support hydration. ' + 'This error is likely caused by a bug in React. ' + 'Please file an issue.', ); From 35859298ef17e3b41ab91b6314dc4f4d91d715c2 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 19 Nov 2018 14:16:03 -0800 Subject: [PATCH 15/22] Download error-codes from Circle CI after publishing --- .../update-stable-version-numbers.js | 4 +- .../download-error-codes-from-ci.js | 51 +++++++++++++++++++ .../print-follow-up-instructions.js | 22 +++++++- scripts/release/publish.js | 2 + 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 scripts/release/publish-commands/download-error-codes-from-ci.js diff --git a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js index 9fab86c02eb81..cbb633bb80c7f 100644 --- a/scripts/release/prepare-stable-commands/update-stable-version-numbers.js +++ b/scripts/release/prepare-stable-commands/update-stable-version-numbers.js @@ -158,9 +158,9 @@ const run = async ({cwd, packages, version}, versionsMap) => { writeFileSync(diffPath, diff, {cwd}); console.log(theme.header(`\n${numFilesModified} files have been updated.`)); console.log( - theme`A full diff is availbale at {path ${relative(cwd, diffPath)}}.` + theme`A full diff is available at {path ${relative(cwd, diffPath)}}.` ); - await confirm('Do changes changes look correct?'); + await confirm('Do the changes above look correct?'); }; // Run this directly because logPromise would interfere with printing package dependencies. diff --git a/scripts/release/publish-commands/download-error-codes-from-ci.js b/scripts/release/publish-commands/download-error-codes-from-ci.js new file mode 100644 index 0000000000000..97961381cf1d9 --- /dev/null +++ b/scripts/release/publish-commands/download-error-codes-from-ci.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node + +'use strict'; + +const http = require('request-promise-json'); +const {exec} = require('child-process-promise'); +const {readJsonSync} = require('fs-extra'); +const {logPromise} = require('../utils'); +const theme = require('../theme'); + +const run = async ({cwd, tags}) => { + if (!tags.includes('latest')) { + // Don't update error-codes for alphas. + return; + } + + // All packages are built from a single source revision, + // so it is safe to read build info from any one of them. + const {buildNumber, environment} = readJsonSync( + `${cwd}/build/node_modules/react/build-info.json` + ); + + // If this release was created on Circle CI, grab the updated error codes from there. + // Else the user will have to manually regenerate them. + if (environment === 'ci') { + // https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci + // eslint-disable-next-line max-len + const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildNumber}/artifacts?circle-token=${ + process.env.CIRCLE_CI_API_TOKEN + }`; + const metadata = await http.get(metadataURL, true); + + // Each container stores an "error-codes" artifact, unfortunately. + // We want to use the one that also ran `yarn build` since it may have modifications. + const {node_index} = metadata.find( + entry => entry.path === 'home/circleci/project/node_modules.tgz' + ); + const {url} = metadata.find( + entry => + entry.node_index === node_index && + entry.path === 'home/circleci/project/scripts/error-codes/codes.json' + ); + + // Download and stage changers + await exec(`curl ${url} --output ${cwd}/scripts/error-codes/codes.json`); + } +}; + +module.exports = async params => { + return logPromise(run(params), theme`Retrieving error codes`); +}; diff --git a/scripts/release/publish-commands/print-follow-up-instructions.js b/scripts/release/publish-commands/print-follow-up-instructions.js index a81aa30bb3db0..7eb12c77723cf 100644 --- a/scripts/release/publish-commands/print-follow-up-instructions.js +++ b/scripts/release/publish-commands/print-follow-up-instructions.js @@ -9,7 +9,7 @@ const theme = require('../theme'); const run = async ({cwd, packages, tags}) => { // All packages are built from a single source revision, // so it is safe to read the commit number from any one of them. - const {commit} = readJsonSync( + const {commit, environment} = readJsonSync( `${cwd}/build/node_modules/react/build-info.json` ); @@ -48,6 +48,26 @@ const run = async ({cwd, packages, tags}) => { commit ); console.log(theme.command` git push origin --tags`); + + if (tags.includes('latest')) { + console.log(); + console.log( + theme`{header Don't forget to commit the generated }{path scripts/error-codes/codes.json}` + ); + if (environment === 'ci') { + console.log( + `This file has been updated locally. Please review it before committing.` + ); + } else { + console.log( + `The release that was just published was created locally. ` + + `Because of this, you will need to update error codes manually with the following commands:` + ); + console.log(theme` {command git checkout} {version ${commit}}`); + console.log(theme` {command yarn build -- --extract-errors}`); + } + } + console.log(); console.log( theme`{header Don't forget to update and commit the }{path CHANGELOG}` diff --git a/scripts/release/publish.js b/scripts/release/publish.js index 69299947732e5..3a28d1bf536b5 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -7,6 +7,7 @@ const {getPublicPackages, handleError} = require('./utils'); const checkNPMPermissions = require('./publish-commands/check-npm-permissions'); const confirmVersionAndTags = require('./publish-commands/confirm-version-and-tags'); +const downloadErrorCodesFromCI = require('./publish-commands/download-error-codes-from-ci'); const parseParams = require('./publish-commands/parse-params'); const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions'); const promptForOTP = require('./publish-commands/prompt-for-otp'); @@ -24,6 +25,7 @@ const run = async () => { await checkNPMPermissions(params); const otp = await promptForOTP(params); await publishToNPM(params, otp); + await downloadErrorCodesFromCI(params); await printFollowUpInstructions(params); } catch (error) { handleError(error); From 40dde41989fb88a2535111ac5bb52d67f2870a3a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 20 Nov 2018 15:18:02 -0800 Subject: [PATCH 16/22] Update package JSONs automatically after stable release is publish --- .../print-follow-up-instructions.js | 50 +++++++++---------- .../update-stable-version-numbers.js | 49 ++++++++++++++++++ scripts/release/publish.js | 2 + 3 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 scripts/release/publish-commands/update-stable-version-numbers.js diff --git a/scripts/release/publish-commands/print-follow-up-instructions.js b/scripts/release/publish-commands/print-follow-up-instructions.js index 7eb12c77723cf..db3ed9f713084 100644 --- a/scripts/release/publish-commands/print-follow-up-instructions.js +++ b/scripts/release/publish-commands/print-follow-up-instructions.js @@ -25,16 +25,39 @@ const run = async ({cwd, packages, tags}) => { ); if (tags.includes('latest')) { + console.log(); console.log( - theme.header`\nLocal versions may require updating after a stable release. Please verify the following files:` + theme.header`Please review and commit all local, staged changes.` ); + + console.log(); + console.log('Version numbers have been updated in the following files:'); for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; console.log(theme.path`• packages/%s/package.json`, packageName); } console.log(theme.path`• packages/shared/ReactVersion.js`); + + console.log(); + if (environment === 'ci') { + console.log('Auto-generated error codes have been updated as well:'); + console.log(theme.path`• scripts/error-codes/codes.json`); + } else { + console.log( + theme`{caution The release that was just published was created locally.} ` + + theme`Because of this, you will need to update the generated ` + + theme`{path scripts/error-codes/codes.json} file manually:` + ); + console.log(theme` {command git checkout} {version ${commit}}`); + console.log(theme` {command yarn build -- --extract-errors}`); + } } + console.log(); + console.log( + theme`{header Don't forget to update and commit the }{path CHANGELOG}` + ); + // Prompt the release engineer to tag the commit and update the CHANGELOG. // (The script could automatically do this, but this seems safer.) console.log(); @@ -49,31 +72,8 @@ const run = async ({cwd, packages, tags}) => { ); console.log(theme.command` git push origin --tags`); - if (tags.includes('latest')) { - console.log(); - console.log( - theme`{header Don't forget to commit the generated }{path scripts/error-codes/codes.json}` - ); - if (environment === 'ci') { - console.log( - `This file has been updated locally. Please review it before committing.` - ); - } else { - console.log( - `The release that was just published was created locally. ` + - `Because of this, you will need to update error codes manually with the following commands:` - ); - console.log(theme` {command git checkout} {version ${commit}}`); - console.log(theme` {command yarn build -- --extract-errors}`); - } - } - - console.log(); - console.log( - theme`{header Don't forget to update and commit the }{path CHANGELOG}` - ); console.log(); - console.log(theme.header`Then fill in the release on GitHub:`); + console.log(theme.header`Lastly, please fill in the release on GitHub:`); console.log( theme.link`https://github.com/facebook/react/releases/tag/v%s`, version diff --git a/scripts/release/publish-commands/update-stable-version-numbers.js b/scripts/release/publish-commands/update-stable-version-numbers.js new file mode 100644 index 0000000000000..b94caa43c147f --- /dev/null +++ b/scripts/release/publish-commands/update-stable-version-numbers.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +'use strict'; + +const {readFileSync, writeFileSync} = require('fs'); +const {readJson, writeJson} = require('fs-extra'); +const {join} = require('path'); + +const run = async ({cwd, packages, tags}) => { + if (!tags.includes('latest')) { + // Don't update version numbers for alphas. + return; + } + + const nodeModulesPath = join(cwd, 'build/node_modules'); + const packagesPath = join(cwd, 'packages'); + + // Update package versions and dependencies (in source) to mirror what was published to NPM. + for (let i = 0; i < packages.length; i++) { + const packageName = packages[i]; + const publishedPackageJSON = await readJson( + join(nodeModulesPath, packageName, 'package.json') + ); + const sourcePackageJSONPath = join( + packagesPath, + packageName, + 'package.json' + ); + const sourcePackageJSON = await readJson(sourcePackageJSONPath); + sourcePackageJSON.version = publishedPackageJSON.version; + sourcePackageJSON.dependencies = publishedPackageJSON.dependencies; + sourcePackageJSON.peerDependencies = publishedPackageJSON.peerDependencies; + + await writeJson(sourcePackageJSONPath, sourcePackageJSON, {spaces: 2}); + } + + // Update the shared React version source file. + const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js'); + const {version} = await readJson( + join(nodeModulesPath, 'react', 'package.json') + ); + const sourceReactVersion = readFileSync( + sourceReactVersionPath, + 'utf8' + ).replace(/module\.exports = '[^']+';/, `module.exports = '${version}';`); + writeFileSync(sourceReactVersionPath, sourceReactVersion); +}; + +module.exports = run; diff --git a/scripts/release/publish.js b/scripts/release/publish.js index 3a28d1bf536b5..4923204a1333f 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -12,6 +12,7 @@ const parseParams = require('./publish-commands/parse-params'); const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions'); const promptForOTP = require('./publish-commands/prompt-for-otp'); const publishToNPM = require('./publish-commands/publish-to-npm'); +const updateStableVersionNumbers = require('./publish-commands/update-stable-version-numbers'); const validateTags = require('./publish-commands/validate-tags'); const run = async () => { @@ -26,6 +27,7 @@ const run = async () => { const otp = await promptForOTP(params); await publishToNPM(params, otp); await downloadErrorCodesFromCI(params); + await updateStableVersionNumbers(params); await printFollowUpInstructions(params); } catch (error) { handleError(error); From 03abe2ac5e9fe4baa5eb2febe3bf27ba8308dafd Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 23 Nov 2018 11:51:47 -0800 Subject: [PATCH 17/22] Fixed a typo --- scripts/release/publish-commands/parse-params.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/publish-commands/parse-params.js b/scripts/release/publish-commands/parse-params.js index bacd9eb5e3c1f..635e22c812a49 100644 --- a/scripts/release/publish-commands/parse-params.js +++ b/scripts/release/publish-commands/parse-params.js @@ -27,7 +27,7 @@ module.exports = () => { const usage = commandLineUsage([ { content: - 'Publishes the current contents of "build/node_modules" to NPM.}', + 'Publishes the current contents of "build/node_modules" to NPM.', }, { header: 'Options', From 8747fa68c3b8dd78c5938f3f391200e4d3752f63 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 23 Nov 2018 11:52:02 -0800 Subject: [PATCH 18/22] Updated fixtures/packaging tests to use build/node_modules rather than build/dist --- fixtures/attribute-behavior/package.json | 2 +- fixtures/dom/package.json | 2 +- fixtures/expiration/package.json | 2 +- fixtures/fiber-triangle/index.html | 4 ++-- fixtures/packaging/babel-standalone/dev.html | 4 ++-- fixtures/packaging/globals/dev.html | 4 ++-- fixtures/packaging/globals/prod.html | 4 ++-- fixtures/packaging/requirejs/dev.html | 4 ++-- fixtures/packaging/requirejs/prod.html | 4 ++-- fixtures/packaging/rjs/dev/config.js | 5 +++-- fixtures/packaging/rjs/prod/config.js | 5 +++-- fixtures/packaging/systemjs-builder/dev/config.js | 5 +++-- fixtures/packaging/systemjs-builder/prod/config.js | 5 +++-- fixtures/packaging/systemjs/dev.html | 4 ++-- fixtures/packaging/systemjs/prod.html | 4 ++-- fixtures/scheduler/index.html | 2 +- 16 files changed, 32 insertions(+), 28 deletions(-) diff --git a/fixtures/attribute-behavior/package.json b/fixtures/attribute-behavior/package.json index 69b928e56f147..4149300e1be58 100644 --- a/fixtures/attribute-behavior/package.json +++ b/fixtures/attribute-behavior/package.json @@ -12,7 +12,7 @@ }, "scripts": { "prestart": - "cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/ && cp ../../build/dist/react-dom-server.browser.development.js public/", + "cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js public/", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", diff --git a/fixtures/dom/package.json b/fixtures/dom/package.json index 90cb22479c124..940b66736ce4e 100644 --- a/fixtures/dom/package.json +++ b/fixtures/dom/package.json @@ -18,7 +18,7 @@ }, "scripts": { "start": "react-scripts start", - "prestart": "cp ../../build/dist/react.development.js ../../build/dist/react-dom.development.js ../../build/dist/react.production.min.js ../../build/dist/react-dom.production.min.js ../../build/dist/react-dom-server.browser.development.js ../../build/dist/react-dom-server.browser.production.min.js public/", + "prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/", "build": "react-scripts build && cp build/index.html build/200.html", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" diff --git a/fixtures/expiration/package.json b/fixtures/expiration/package.json index 1ffb0ccbe15fc..263812a1ee2de 100644 --- a/fixtures/expiration/package.json +++ b/fixtures/expiration/package.json @@ -9,7 +9,7 @@ }, "scripts": { "prestart": - "cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/", + "cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", diff --git a/fixtures/fiber-triangle/index.html b/fixtures/fiber-triangle/index.html index 1b5941f09b317..c7e22fdd4345e 100644 --- a/fixtures/fiber-triangle/index.html +++ b/fixtures/fiber-triangle/index.html @@ -16,8 +16,8 @@

Fiber Example

If you checked out the source from GitHub make sure to run npm run build.

- - + + - + +
- + +
- + +
+