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
.
-
-
+
+
-
+
+
-
+
+
-
+
+
+