@samitouri / QOS-React / commits / 686f1060ad

Publish a local release (canary or stable) to NPM (#14260)

New release scripts. Learn more at https://github.com/facebook/react/blob/master/scripts/release/README.md

Brian Vaughn committed Nov 23, 2018 at 12:37 UTC 686f1060adeabf00e6c86701e42e9d537d1e3773
93 files changed +1731 -1274
.circleci/config.yml
+4 -1
@@ -42,4 +42,7 @@ jobs:
42 - node_modules
43
44 - store_artifacts:
45 - path: ./node_modules.tgz
\ No newline at end of file
45 + path: ./node_modules.tgz
46 +
47 + - store_artifacts:
48 + path: ./scripts/error-codes/codes.json
\ No newline at end of file
fixtures/attribute-behavior/package.json
+1 -1
@@ -12,7 +12,7 @@
12 },
13 "scripts": {
14 "prestart":
15 - "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/",
15 + "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/",
16 "start": "react-scripts start",
17 "build": "react-scripts build",
18 "test": "react-scripts test --env=jsdom",
fixtures/dom/package.json
+1 -1
@@ -18,7 +18,7 @@
18 },
19 "scripts": {
20 "start": "react-scripts start",
21 - "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/",
21 + "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/",
22 "build": "react-scripts build && cp build/index.html build/200.html",
23 "test": "react-scripts test --env=jsdom",
24 "eject": "react-scripts eject"
fixtures/expiration/package.json
+1 -1
@@ -9,7 +9,7 @@
9 },
10 "scripts": {
11 "prestart":
12 - "cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/",
12 + "cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/",
13 "start": "react-scripts start",
14 "build": "react-scripts build",
15 "test": "react-scripts test --env=jsdom",
fixtures/fiber-triangle/index.html
+2 -2
@@ -16,8 +16,8 @@
16 If you checked out the source from GitHub make sure to run <code>npm run build</code>.
17 </p>
18 </div>
19 - <script src="../../build/dist/react.development.js"></script>
20 - <script src="../../build/dist/react-dom.development.js"></script>
19 + <script src="../../build/node_modules/react/umd/react.development.js"></script>
20 + <script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
21 <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
22 <script type="text/babel">
23 var dotStyle = {
fixtures/packaging/babel-standalone/dev.html
+2 -2
@@ -1,7 +1,7 @@
1 <html>
2 <body>
3 - <script src="../../../build/dist/react.development.js"></script>
4 - <script src="../../../build/dist/react-dom.development.js"></script>
3 + <script src="../../../build/node_modules/react/umd/react.development.js"></script>
4 + <script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
5 <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
6 <div id="container"></div>
7 <script type="text/babel">
fixtures/packaging/globals/dev.html
+2 -2
@@ -1,7 +1,7 @@
1 <html>
2 <body>
3 - <script src="../../../build/dist/react.development.js"></script>
4 - <script src="../../../build/dist/react-dom.development.js"></script>
3 + <script src="../../../build/node_modules/react/umd/react.development.js"></script>
4 + <script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
5 <div id="container"></div>
6 <script>
7 ReactDOM.render(
fixtures/packaging/globals/prod.html
+2 -2
@@ -1,7 +1,7 @@
1 <html>
2 <body>
3 - <script src="../../../build/dist/react.production.min.js"></script>
4 - <script src="../../../build/dist/react-dom.production.min.js"></script>
3 + <script src="../../../build/node_modules/react/umd/react.production.min.js"></script>
4 + <script src="../../../build/node_modules/react-dom/umd/react-dom.production.min.js"></script>
5 <div id="container"></div>
6 <script>
7 ReactDOM.render(
fixtures/packaging/requirejs/dev.html
+2 -2
@@ -5,8 +5,8 @@
5 <script>
6 requirejs.config({
7 paths: {
8 - react: '../../../build/dist/react.development',
9 - 'react-dom': '../../../build/dist/react-dom.development'
8 + react: '../../../build/node_modules/react/umd/react.development',
9 + 'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development'
10 }
11 });
12
fixtures/packaging/requirejs/prod.html
+2 -2
@@ -5,8 +5,8 @@
5 <script>
6 requirejs.config({
7 paths: {
8 - react: '../../../build/dist/react.production.min',
9 - 'react-dom': '../../../build/dist/react-dom.production.min'
8 + react: '../../../build/node_modules/react/umd/react.production.min',
9 + 'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min'
10 }
11 });
12
fixtures/packaging/rjs/dev/config.js
+3 -2
@@ -4,8 +4,9 @@ module.exports = {
4 out: 'output.js',
5 optimize: 'none',
6 paths: {
7 - react: '../../../../build/dist/react.development',
8 - 'react-dom': '../../../../build/dist/react-dom.development',
7 + react: '../../../../build/node_modules/react/umd/react.development',
8 + 'react-dom':
9 + '../../../../build/node_modules/react-dom/umd/react-dom.development',
10 schedule: '../../../../build/dist/schedule.development',
11 },
12 };
fixtures/packaging/rjs/prod/config.js
+3 -2
@@ -4,8 +4,9 @@ module.exports = {
4 out: 'output.js',
5 optimize: 'none',
6 paths: {
7 - react: '../../../../build/dist/react.production.min',
8 - 'react-dom': '../../../../build/dist/react-dom.production.min',
7 + react: '../../../../build/node_modules/react/umd/react.production.min',
8 + 'react-dom':
9 + '../../../../build/node_modules/react-dom/umd/react-dom.production.min',
10 schedule: '../../../../build/dist/schedule.development',
11 },
12 };
fixtures/packaging/systemjs-builder/dev/config.js
+3 -2
@@ -1,7 +1,8 @@
1 System.config({
2 paths: {
3 - react: '../../../../build/dist/react.development.js',
4 - 'react-dom': '../../../../build/dist/react-dom.development.js',
3 + react: '../../../../build/node_modules/react/umd/react.development.js',
4 + 'react-dom':
5 + '../../../../build/node_modules/react-dom/umd/react-dom.development.js',
6 schedule: '../../../../build/dist/schedule.development',
7 },
8 });
fixtures/packaging/systemjs-builder/prod/config.js
+3 -2
@@ -1,7 +1,8 @@
1 System.config({
2 paths: {
3 - react: '../../../../build/dist/react.production.min.js',
4 - 'react-dom': '../../../../build/dist/react-dom.production.min.js',
3 + react: '../../../../build/node_modules/react/umd/react.production.min.js',
4 + 'react-dom':
5 + '../../../../build/node_modules/react-dom/umd/react-dom.production.min.js',
6 schedule: '../../../../build/dist/schedule.development',
7 },
8 });
fixtures/packaging/systemjs/dev.html
+2 -2
@@ -5,8 +5,8 @@
5 <script>
6 System.config({
7 paths: {
8 - react: '../../../build/dist/react.development.js',
9 - 'react-dom': '../../../build/dist/react-dom.development.js'
8 + react: '../../../build/node_modules/react/umd/react.development.js',
9 + 'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development.js'
10 }
11 });
12
fixtures/packaging/systemjs/prod.html
+2 -2
@@ -5,8 +5,8 @@
5 <script>
6 System.config({
7 paths: {
8 - react: '../../../build/dist/react.production.min.js',
9 - 'react-dom': '../../../build/dist/react-dom.production.min.js'
8 + react: '../../../build/node_modules/react/umd/react.production.min.js',
9 + 'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min.js'
10 }
11 });
12
fixtures/scheduler/index.html
+1 -1
@@ -91,7 +91,7 @@
91 <div> If the counter advanced while you were away from this tab, it's correct.</div>
92 </li>
93 </ol>
94 - <script src="../../build/dist/react.development.js"></script>
94 + <script src="../../build/node_modules/react/umd/react.development.js"></script>
95 <script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
96 <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
97 <script type="text/babel">
package.json
+1 -1
@@ -93,7 +93,7 @@
93 "testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
94 },
95 "scripts": {
96 - "build": "npm run version-check && node ./scripts/rollup/build.js",
96 + "build": "node ./scripts/rollup/build.js",
97 "linc": "node ./scripts/tasks/linc.js",
98 "lint": "node ./scripts/tasks/eslint.js",
99 "lint-build": "node ./scripts/rollup/validate/index.js",
packages/create-subscription/package.json
+1
@@ -6,6 +6,7 @@
6 "files": [
7 "LICENSE",
8 "README.md",
9 + "build-info.json",
10 "index.js",
11 "cjs/"
12 ],
packages/eslint-plugin-react-hooks/package.json
+1
@@ -7,6 +7,7 @@
7 "files": [
8 "LICENSE",
9 "README.md",
10 + "build-info.json",
11 "index.js",
12 "cjs"
13 ],
packages/jest-react/package.json
+1
@@ -22,6 +22,7 @@
22 "files": [
23 "LICENSE",
24 "README.md",
25 + "build-info.json",
26 "index.js",
27 "cjs/"
28 ]
packages/react-art/package.json
+1
@@ -31,6 +31,7 @@
31 "files": [
32 "LICENSE",
33 "README.md",
34 + "build-info.json",
35 "index.js",
36 "cjs/",
37 "umd/",
packages/react-cache/package.json
+1
@@ -7,6 +7,7 @@
7 "files": [
8 "LICENSE",
9 "README.md",
10 + "build-info.json",
11 "index.js",
12 "cjs/",
13 "umd/"
packages/react-debug-tools/package.json
+1
@@ -12,6 +12,7 @@
12 "files": [
13 "LICENSE",
14 "README.md",
15 + "build-info.json",
16 "index.js",
17 "cjs/"
18 ],
packages/react-dom/package.json
+1
@@ -24,6 +24,7 @@
24 "files": [
25 "LICENSE",
26 "README.md",
27 + "build-info.json",
28 "index.js",
29 "profiling.js",
30 "server.js",
packages/react-is/package.json
+1
@@ -15,6 +15,7 @@
15 "files": [
16 "LICENSE",
17 "README.md",
18 + "build-info.json",
19 "index.js",
20 "cjs/",
21 "umd/"
packages/react-noop-renderer/package.json
+1
@@ -18,6 +18,7 @@
18 "files": [
19 "LICENSE",
20 "README.md",
21 + "build-info.json",
22 "index.js",
23 "persistent.js",
24 "cjs/"
packages/react-reconciler/package.json
+1
@@ -11,6 +11,7 @@
11 "files": [
12 "LICENSE",
13 "README.md",
14 + "build-info.json",
15 "index.js",
16 "persistent.js",
17 "reflection.js",
packages/react-test-renderer/package.json
+1
@@ -26,6 +26,7 @@
26 "files": [
27 "LICENSE",
28 "README.md",
29 + "build-info.json",
30 "index.js",
31 "shallow.js",
32 "cjs/",
packages/react/package.json
+1
@@ -11,6 +11,7 @@
11 "files": [
12 "LICENSE",
13 "README.md",
14 + "build-info.json",
15 "index.js",
16 "cjs/",
17 "umd/"
packages/scheduler/package.json
+1
@@ -19,6 +19,7 @@
19 "files": [
20 "LICENSE",
21 "README.md",
22 + "build-info.json",
23 "index.js",
24 "tracing.js",
25 "tracing-profiling.js",
scripts/circleci/add_build_info_json.sh new
+5
@@ -0,0 +1,5 @@
1 +#!/bin/bash
2 +
3 +set -e
4 +
5 +node ./scripts/release/ci-add-build-info-json.js
scripts/circleci/test_entry_point.sh
+2
@@ -25,6 +25,8 @@ if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
25 fi
26
27 if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
28 + COMMANDS_TO_RUN+=('./scripts/circleci/add_build_info_json.sh')
29 + COMMANDS_TO_RUN+=('./scripts/circleci/update_package_versions.sh')
30 COMMANDS_TO_RUN+=('./scripts/circleci/build.sh')
31 COMMANDS_TO_RUN+=('yarn test-build --maxWorkers=2')
32 COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2')
scripts/circleci/update_package_versions.sh new
+5
@@ -0,0 +1,5 @@
1 +#!/bin/bash
2 +
3 +set -e
4 +
5 +node ./scripts/release/ci-update-package-versions.js
scripts/release/README.md
+60 -7
@@ -1,13 +1,66 @@
1 # React Release Scripts
2
3 -At a high-level, the release process uses two scripts: **build** and **publish**.
4 -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.
5 -1. The **publish** script then publishes the built artifacts to npm and pushes to GitHub.
3 +The release process consists of several phases, each one represented by one of the scripts below.
4
7 -Run either script without parameters to see its usage, e.g.:
5 +A typical release goes like this:
6 +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.
7 +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).)
8 +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.)
9 +
10 +One or more release scripts are used for each of the above phases. Learn more about these scripts below:
11 +* [`create-canary`](#create-canary)
12 +* [`prepare-canary`](#prepare-canary)
13 +* [`prepare-stable`](#prepare-stable)
14 +* [`publish`](#publish)
15 +
16 +## `create-canary`
17 +Creates a canary build from the current (local) Git revision.
18 +
19 +**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.
20 +
21 +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**.
22 +
23 +#### Example usage
24 +To create a canary from the current branch and revision:
25 +```sh
26 +scripts/release/create-canary.js
27 ```
9 -./scripts/release/build.js
10 -./scripts/release/publish.js
28 +
29 +## `prepare-canary`
30 +Downloads build artifacts from Circle CI in preparation to be published to NPM as a canary release.
31 +
32 +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.
33 +
34 +#### Example usage
35 +To prepare the artifacts created by [Circle CI build 12677](https://circleci.com/gh/facebook/react/12677#artifacts/containers/0) you would run:
36 +```sh
37 +scripts/release/prepare-canary.js --build=12677
38 ```
39
13 -Each script will guide the release engineer through any necessary steps (including environment setup and manual testing steps).
40 +## `prepare-stable`
41 +Checks out a canary release from NPM and prepares it to be published as a stable release.
42 +
43 +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.
44 +
45 +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.
46 +
47 +#### Example usage
48 +To promote the canary release `0.0.0-5bf84d292` (aka commit [5bf84d292](https://github.com/facebook/react/commit/5bf84d292)) to stable:
49 +```sh
50 +scripts/release/prepare-stable.js --version=0.0.0-5bf84d292
51 +```
52 +
53 +## `publish`
54 +Publishes the current contents of `build/node_modules` to NPM.
55 +
56 +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.
57 +
58 +Upon completion, this script provides instructions for tagging the Git commit that the package was created from and updating the release CHANGELOG.
59 +
60 +**Specify a `--dry` flag when running this script if you want to skip the NPM-publish step.** In this event, the script will print the NPM commands but it will not actually run them.
61 +
62 +#### Example usage
63 +To publish a release to NPM as both `next` and `latest`:
64 +```sh
65 +scripts/release/publish.js --tags next latest
66 +```
\ No newline at end of file
scripts/release/build-commands/add-git-tag.js deleted
-23
@@ -1,23 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {execUnlessDry, logPromise} = require('../utils');
7 -
8 -const run = async ({cwd, dry, version}) => {
9 - await execUnlessDry(
10 - `git tag -a v${version} -m "Tagging ${version} release"`,
11 - {
12 - cwd,
13 - dry,
14 - }
15 - );
16 -};
17 -
18 -module.exports = async ({cwd, dry, version}) => {
19 - return logPromise(
20 - run({cwd, dry, version}),
21 - `Creating git tag ${chalk.yellow.bold(`v${version}`)}`
22 - );
23 -};
scripts/release/build-commands/build-artifacts.js deleted
-32
@@ -1,32 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {exec} = require('child-process-promise');
6 -const {execRead, execUnlessDry, logPromise} = require('../utils');
7 -
8 -const run = async ({cwd, dry, version}) => {
9 - await exec('yarn build -- --extract-errors', {cwd});
10 -
11 - const modifiedFiles = await execRead('git ls-files -m', {cwd});
12 -
13 - if (modifiedFiles.includes('scripts/error-codes/codes.json')) {
14 - await execUnlessDry('git add scripts/error-codes/codes.json', {cwd, dry});
15 - await execUnlessDry(
16 - `git commit -m "Update error codes for ${version} release"`,
17 - {cwd, dry}
18 - );
19 - }
20 -
21 - if (modifiedFiles.includes('scripts/rollup/results.json')) {
22 - await execUnlessDry('git add scripts/rollup/results.json', {cwd, dry});
23 - await execUnlessDry(
24 - `git commit -m "Update bundle sizes for ${version} release"`,
25 - {cwd, dry}
26 - );
27 - }
28 -};
29 -
30 -module.exports = async params => {
31 - return logPromise(run(params), 'Building artifacts', true);
32 -};
scripts/release/build-commands/check-circle-ci-status.js deleted
-70
@@ -1,70 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const http = require('request-promise-json');
7 -const logUpdate = require('log-update');
8 -const prompt = require('prompt-promise');
9 -const {execRead, logPromise} = require('../utils');
10 -
11 -// https://circleci.com/docs/api/v1-reference/#projects
12 -const CIRCLE_CI_BASE_URL =
13 - 'https://circleci.com/api/v1.1/project/github/facebook/react/tree/master';
14 -
15 -const check = async ({cwd}) => {
16 - const token = process.env.CIRCLE_CI_API_TOKEN;
17 - const uri = `${CIRCLE_CI_BASE_URL}?circle-token=${token}&limit=1`;
18 -
19 - const response = await http.get(uri, true);
20 - const {outcome, status, vcs_revision: ciRevision} = response[0];
21 -
22 - const gitRevision = await execRead('git rev-parse HEAD', {cwd});
23 -
24 - if (gitRevision !== ciRevision) {
25 - throw Error(
26 - chalk`
27 - CircleCI is stale
28 -
29 - {white The latest Git revision is {yellow.bold ${gitRevision}}}
30 - {white The most recent CircleCI revision is {yellow.bold ${ciRevision}}}
31 - {white Please wait for CircleCI to catch up.}
32 - `
33 - );
34 - } else if (outcome !== 'success') {
35 - throw Error(
36 - chalk`
37 - CircleCI failed
38 -
39 - {white The most recent CircleCI build has a status of {red.bold ${outcome ||
40 - status}}}
41 - {white Please retry this build in CircleCI if you believe this is an error.}
42 - `
43 - );
44 - }
45 -};
46 -
47 -module.exports = async params => {
48 - if (params.local) {
49 - return;
50 - }
51 -
52 - if (params.skipCI) {
53 - logUpdate(
54 - chalk.red`Are you sure you want to skip CI? (y for yes, n for no)`
55 - );
56 - const confirm = await prompt('');
57 - logUpdate.done();
58 - if (confirm === 'y') {
59 - return;
60 - } else {
61 - throw Error(
62 - chalk`
63 - Cancelling release.
64 - `
65 - );
66 - }
67 - }
68 -
69 - return logPromise(check(params), 'Checking CircleCI status');
70 -};
scripts/release/build-commands/check-environment-variables.js deleted
-27
@@ -1,27 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -
7 -module.exports = () => {
8 - if (!process.env.CIRCLE_CI_API_TOKEN) {
9 - throw Error(
10 - chalk`
11 - {red Missing CircleCI API token}
12 -
13 - {white The CircleCI API is used to check the status of the latest commit.}
14 - {white This API requires a token which must be exposed via a {yellow.bold CIRCLE_CI_API_TOKEN} environment var.}
15 - {white In order to run this script you will need to create your own API token.}
16 - {white Instructions can be found at:}
17 -
18 - {blue.bold https://circleci.com/docs/api/v1-reference/#getting-started}
19 -
20 - {white To make this token available to the release script, add it to your {yellow.bold .bash_profile} like so:}
21 -
22 - {gray # React release script}
23 - {white export CIRCLE_CI_API_TOKEN=<your-token-here>}
24 - `
25 - );
26 - }
27 -};
scripts/release/build-commands/check-package-dependencies.js deleted
-60
@@ -1,60 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {readJson} = require('fs-extra');
7 -const {join} = require('path');
8 -const {dependencies} = require('../config');
9 -const {logPromise} = require('../utils');
10 -
11 -const check = async ({cwd, packages}) => {
12 - const rootPackage = await readJson(join(cwd, 'package.json'));
13 -
14 - const projectPackages = [];
15 - for (let i = 0; i < packages.length; i++) {
16 - const project = packages[i];
17 - projectPackages.push(
18 - await readJson(join(cwd, join('packages', project), 'package.json'))
19 - );
20 - }
21 -
22 - const invalidDependencies = [];
23 -
24 - const checkModule = module => {
25 - const rootVersion = rootPackage.devDependencies[module];
26 -
27 - projectPackages.forEach(projectPackage => {
28 - // Not all packages have dependencies (eg react-is)
29 - const projectVersion = projectPackage.dependencies
30 - ? projectPackage.dependencies[module]
31 - : undefined;
32 -
33 - if (rootVersion !== projectVersion && projectVersion !== undefined) {
34 - invalidDependencies.push(
35 - `${module} is ${chalk.red.bold(rootVersion)} in root but ` +
36 - `${chalk.red.bold(projectVersion)} in ${projectPackage.name}`
37 - );
38 - }
39 - });
40 - };
41 -
42 - dependencies.forEach(checkModule);
43 -
44 - if (invalidDependencies.length) {
45 - throw Error(
46 - chalk`
47 - Dependency mismatch
48 -
49 - {white The following dependencies do not match between the root package and NPM dependencies:}
50 - ${invalidDependencies
51 - .map(dependency => chalk.white(dependency))
52 - .join('\n')}
53 - `
54 - );
55 - }
56 -};
57 -
58 -module.exports = async params => {
59 - return logPromise(check(params), 'Checking runtime dependencies');
60 -};
scripts/release/build-commands/check-uncommitted-changes.js deleted
-20
@@ -1,20 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {execRead} = require('../utils');
7 -
8 -module.exports = async ({cwd}) => {
9 - const status = await execRead('git diff HEAD', {cwd});
10 -
11 - if (status) {
12 - throw Error(
13 - chalk`
14 - Uncommitted local changes
15 -
16 - {white Please revert or commit all local changes before making a release.}
17 - `
18 - );
19 - }
20 -};
scripts/release/build-commands/install-yarn-dependencies.js deleted
-15
@@ -1,15 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {exec} = require('child-process-promise');
6 -const {logPromise} = require('../utils');
7 -
8 -const install = async ({cwd}) => {
9 - await exec('rm -rf node_modules', {cwd});
10 - await exec('yarn', {cwd});
11 -};
12 -
13 -module.exports = async ({cwd}) => {
14 - return logPromise(install({cwd}), 'Installing NPM dependencies');
15 -};
scripts/release/build-commands/parse-build-parameters.js deleted
-57
@@ -1,57 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const commandLineArgs = require('command-line-args');
7 -const commandLineUsage = require('command-line-usage');
8 -const figlet = require('figlet');
9 -const {paramDefinitions} = require('../config');
10 -
11 -module.exports = () => {
12 - const params = commandLineArgs(paramDefinitions);
13 -
14 - if (!params.version) {
15 - const usage = commandLineUsage([
16 - {
17 - content: chalk
18 - .hex('#61dafb')
19 - .bold(figlet.textSync('react', {font: 'Graffiti'})),
20 - raw: true,
21 - },
22 - {
23 - content: 'Automated pre-release build script.',
24 - },
25 - {
26 - header: 'Options',
27 - optionList: paramDefinitions,
28 - },
29 - {
30 - header: 'Examples',
31 - content: [
32 - {
33 - desc: '1. A concise example.',
34 - example: '$ ./build.js [bold]{-v} [underline]{16.0.0}',
35 - },
36 - {
37 - desc: '2. Dry run build a release candidate (no git commits).',
38 - example:
39 - '$ ./build.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}',
40 - },
41 - {
42 - desc: '3. Release from another checkout.',
43 - example:
44 - '$ ./build.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo',
45 - },
46 - ],
47 - },
48 - ]);
49 - console.log(usage);
50 - process.exit(1);
51 - }
52 -
53 - return {
54 - ...params,
55 - cwd: params.path, // For script convenience
56 - };
57 -};
scripts/release/build-commands/print-post-build-summary.js deleted
-55
@@ -1,55 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {join, relative} = require('path');
7 -const {getUnexecutedCommands} = require('../utils');
8 -
9 -const CHANGELOG_PATH =
10 - 'https://github.com/facebook/react/edit/master/CHANGELOG.md';
11 -
12 -module.exports = ({cwd, dry, path, version}) => {
13 - const publishPath = relative(
14 - process.env.PWD,
15 - join(__dirname, '../publish.js')
16 - );
17 - const command =
18 - `${publishPath} -v ${version}` +
19 - (path ? ` -p ${path}` : '') +
20 - (dry ? ' --dry' : '');
21 -
22 - const packagingFixturesPath = join(cwd, 'fixtures/packaging');
23 - const standaloneFixturePath = join(
24 - cwd,
25 - 'fixtures/packaging/babel-standalone/dev.html'
26 - );
27 -
28 - console.log(
29 - chalk`
30 - {green.bold Build successful!}
31 - ${getUnexecutedCommands()}
32 - Next there are a couple of manual steps:
33 -
34 - {bold.underline Step 1: Update the CHANGELOG}
35 -
36 - Here are a few things to keep in mind:
37 - • The changes should be easy to understand. (Friendly one-liners are better than PR titles.)
38 - • Make sure all contributors are credited.
39 - • Verify that the markup is valid by previewing it in the editor: {blue.bold ${CHANGELOG_PATH}}
40 -
41 - {bold.underline Step 2: Smoke test the packages}
42 -
43 - 1. Open {yellow.bold ${standaloneFixturePath}} in the browser.
44 - 2. It should say {italic "Hello world!"}
45 - 3. Next go to {yellow.bold ${packagingFixturesPath}} and run {bold node build-all.js}
46 - 4. Install the "serve" module ({bold npm install -g serve})
47 - 5. Go to the repo root and {bold serve -s .}
48 - 6. Open {blue.bold http://localhost:5000/fixtures/packaging}
49 - 7. Verify every iframe shows {italic "Hello world!"}
50 -
51 - After completing the above steps, resume the release process by running:
52 - {yellow.bold ${command}}
53 - `.replace(/\n +/g, '\n')
54 - );
55 -};
scripts/release/build-commands/run-automated-bundle-tests.js deleted
-30
@@ -1,30 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {logPromise, runYarnTask} = require('../utils');
6 -
7 -module.exports = async ({cwd}) => {
8 - await logPromise(
9 - runYarnTask(cwd, 'lint-build', 'Lint bundle failed'),
10 - 'Running ESLint on bundle'
11 - );
12 - await logPromise(
13 - runYarnTask(
14 - cwd,
15 - 'test-build',
16 - 'Jest tests on the bundle failed in development'
17 - ),
18 - 'Running Jest tests on the bundle in the development environment',
19 - true
20 - );
21 - await logPromise(
22 - runYarnTask(
23 - cwd,
24 - 'test-build-prod',
25 - 'Jest tests on the bundle failed in production'
26 - ),
27 - 'Running Jest tests on the bundle in the production environment',
28 - true
29 - );
30 -};
scripts/release/build-commands/run-automated-tests.js deleted
-23
@@ -1,23 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {logPromise, runYarnTask} = require('../utils');
6 -
7 -module.exports = async ({cwd}) => {
8 - await logPromise(runYarnTask(cwd, 'lint', 'Lint failed'), 'Running ESLint');
9 - await logPromise(
10 - runYarnTask(cwd, 'flow-ci', 'Flow failed'),
11 - 'Running Flow checks'
12 - );
13 - await logPromise(
14 - runYarnTask(cwd, 'test', 'Jest tests failed in development'),
15 - 'Running Jest tests in the development environment',
16 - true
17 - );
18 - await logPromise(
19 - runYarnTask(cwd, 'test-prod', 'Jest tests failed in production'),
20 - 'Running Jest tests in the production environment',
21 - true
22 - );
23 -};
scripts/release/build-commands/update-git.js deleted
-26
@@ -1,26 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {exec} = require('child-process-promise');
7 -const {logPromise} = require('../utils');
8 -
9 -const update = async ({cwd, branch, local}) => {
10 - if (!local) {
11 - await exec('git fetch', {cwd});
12 - }
13 - await exec(`git checkout ${branch}`, {cwd});
14 - if (!local) {
15 - await exec('git pull', {cwd});
16 - }
17 -};
18 -
19 -module.exports = async params => {
20 - return logPromise(
21 - update(params),
22 - `Updating checkout ${chalk.yellow.bold(
23 - params.cwd
24 - )} on branch ${chalk.yellow.bold(params.branch)}`
25 - );
26 -};
scripts/release/build-commands/update-noop-renderer-dependencies.js deleted
-47
@@ -1,47 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {readJson, writeJson} = require('fs-extra');
6 -const {join} = require('path');
7 -const semver = require('semver');
8 -const {execRead, execUnlessDry, logPromise} = require('../utils');
9 -
10 -const getReactReconcilerVersion = async cwd => {
11 - const path = join(cwd, 'packages', 'react-reconciler', 'package.json');
12 - const json = await readJson(path);
13 - return json.version;
14 -};
15 -
16 -const update = async ({cwd, dry}) => {
17 - const path = join(cwd, 'packages', 'react-noop-renderer', 'package.json');
18 - const json = await readJson(path);
19 -
20 - // IMPORTANT: This script must be run after update-package-versions,
21 - // Since it depends up the updated react-reconciler version.
22 - const reconcilerVersion = await getReactReconcilerVersion(cwd);
23 -
24 - // There is no wildcard for semver that includes prerelease ranges as well.
25 - // This causes problems for our Yarn workspaces setup,
26 - // Since the noop-renderer depends on react-reconciler.
27 - // So we have a special case check for this that ensures semver compatibility.
28 - if (semver.prerelease(reconcilerVersion)) {
29 - json.dependencies['react-reconciler'] = `* || ${reconcilerVersion}`;
30 - } else {
31 - json.dependencies['react-reconciler'] = '*';
32 - }
33 -
34 - await writeJson(path, json, {spaces: 2});
35 -
36 - const status = await execRead('git status -s', {cwd});
37 - if (status) {
38 - await execUnlessDry(
39 - `git commit -am "Updating dependencies for react-noop-renderer"`,
40 - {cwd, dry}
41 - );
42 - }
43 -};
44 -
45 -module.exports = async params => {
46 - return logPromise(update(params), 'Updating noop renderer dependencies');
47 -};
scripts/release/build-commands/update-package-versions.js deleted
-153
@@ -1,153 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {exec} = require('child-process-promise');
7 -const {readFileSync, writeFileSync} = require('fs');
8 -const {readJson, writeJson} = require('fs-extra');
9 -const {join} = require('path');
10 -const semver = require('semver');
11 -const {execUnlessDry, logPromise} = require('../utils');
12 -
13 -const getNextVersion = (prevVersion, releaseVersion) => {
14 - const prerelease = semver.prerelease(releaseVersion);
15 -
16 - // Unstable packages (eg version < 1.0) are treated specially:
17 - // Rather than use the release version (eg 16.1.0)-
18 - // We just auto-increment the minor version (eg 0.1.0 -> 0.2.0).
19 - // If we're doing a prerelease, we also append the suffix (eg 0.2.0-beta).
20 - if (semver.lt(prevVersion, '1.0.0')) {
21 - let suffix = '';
22 - if (prerelease) {
23 - suffix = `-${prerelease.join('.')}`;
24 - }
25 -
26 - // If this is a new pre-release, increment the minor.
27 - // Else just increment (or remove) the pre-release suffix.
28 - // This way our minor version isn't incremented unnecessarily with each prerelease.
29 - const minor = semver.prerelease(prevVersion)
30 - ? semver.minor(prevVersion)
31 - : semver.minor(prevVersion) + 1;
32 -
33 - return `0.${minor}.0${suffix}`;
34 - } else {
35 - return releaseVersion;
36 - }
37 -};
38 -
39 -const update = async ({cwd, dry, packages, version}) => {
40 - try {
41 - // Update root package.json
42 - const packagePath = join(cwd, 'package.json');
43 - const rootPackage = await readJson(packagePath);
44 - rootPackage.version = version;
45 - await writeJson(packagePath, rootPackage, {spaces: 2});
46 -
47 - // Update ReactVersion source file
48 - const reactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
49 - const reactVersion = readFileSync(reactVersionPath, 'utf8').replace(
50 - /module\.exports = '[^']+';/,
51 - `module.exports = '${version}';`
52 - );
53 - writeFileSync(reactVersionPath, reactVersion);
54 -
55 - // Update renderer versions and peer dependencies
56 - const updateProjectPackage = async project => {
57 - const path = join(cwd, 'packages', project, 'package.json');
58 - const json = await readJson(path);
59 - const prerelease = semver.prerelease(version);
60 -
61 - // If this is a package we publish directly to NPM, update its version.
62 - // Skip ones that we don't directly publish though (e.g. react-native-renderer).
63 - if (json.private !== true) {
64 - json.version = getNextVersion(json.version, version);
65 - }
66 -
67 - if (project === 'react') {
68 - // Update inter-package dependencies as well.
69 - // e.g. react depends on scheduler
70 - if (json.dependencies) {
71 - Object.keys(json.dependencies).forEach(dependency => {
72 - if (packages.indexOf(dependency) >= 0) {
73 - const prevVersion = json.dependencies[dependency];
74 - const nextVersion = getNextVersion(
75 - prevVersion.replace('^', ''),
76 - version
77 - );
78 - json.dependencies[dependency] = `^${nextVersion}`;
79 - }
80 - });
81 - }
82 - } else if (json.peerDependencies) {
83 - let peerVersion = json.peerDependencies.react.replace('^', '');
84 -
85 - // If the previous release was a pre-release version,
86 - // The peer dependency will contain multiple versions, eg "^16.0.0 || 16.3.0-alpha.0"
87 - // In this case, assume the first one will be the major and extract it.
88 - if (peerVersion.includes(' || ')) {
89 - peerVersion = peerVersion.split(' || ')[0];
90 - }
91 -
92 - // Release engineers can manually update minor and bugfix versions,
93 - // But we should ensure that major versions always match.
94 - if (semver.major(version) !== semver.major(peerVersion)) {
95 - json.peerDependencies.react = `^${semver.major(version)}.0.0`;
96 - } else {
97 - json.peerDependencies.react = `^${peerVersion}`;
98 - }
99 -
100 - // If this is a prerelease, update the react dependency as well.
101 - // A dependency on a major version won't satisfy a prerelease version.
102 - // So rather than eg "^16.0.0" we need "^16.0.0 || 16.3.0-alpha.0"
103 - if (prerelease) {
104 - json.peerDependencies.react += ` || ${version}`;
105 - }
106 -
107 - // Update inter-package dependencies as well.
108 - // e.g. react-test-renderer depends on react-is
109 - if (json.dependencies) {
110 - Object.keys(json.dependencies).forEach(dependency => {
111 - if (packages.indexOf(dependency) >= 0) {
112 - const prevVersion = json.dependencies[dependency];
113 -
114 - // Special case to handle e.g. react-noop-renderer
115 - if (prevVersion === '*') {
116 - return;
117 - }
118 -
119 - const nextVersion = getNextVersion(
120 - prevVersion.replace('^', ''),
121 - version
122 - );
123 - json.dependencies[dependency] = `^${nextVersion}`;
124 - }
125 - });
126 - }
127 - }
128 -
129 - await writeJson(path, json, {spaces: 2});
130 - };
131 - await Promise.all(packages.map(updateProjectPackage));
132 -
133 - // Version sanity check
134 - await exec('yarn version-check', {cwd});
135 -
136 - await execUnlessDry(
137 - `git commit -am "Updating package versions for release ${version}"`,
138 - {cwd, dry}
139 - );
140 - } catch (error) {
141 - throw Error(
142 - chalk`
143 - Failed while updating package versions
144 -
145 - {white ${error.message}}
146 - `
147 - );
148 - }
149 -};
150 -
151 -module.exports = async params => {
152 - return logPromise(update(params), 'Updating package versions');
153 -};
scripts/release/build-commands/update-yarn-dependencies.js deleted
-38
@@ -1,38 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {exec} = require('child-process-promise');
7 -const {dependencies} = require('../config');
8 -const {execRead, execUnlessDry, logPromise} = require('../utils');
9 -
10 -const update = async ({cwd, dry, version}) => {
11 - await exec(`yarn upgrade ${dependencies.join(' ')}`, {cwd});
12 -
13 - const modifiedFiles = await execRead('git ls-files -m', {cwd});
14 -
15 - // If yarn.lock has changed we should commit it.
16 - // If anything else has changed, it's an error.
17 - if (modifiedFiles) {
18 - if (modifiedFiles !== 'yarn.lock') {
19 - throw Error(
20 - chalk`
21 - Unexpected modifications
22 -
23 - {white The following files have been modified unexpectedly:}
24 - {gray ${modifiedFiles}}
25 - `
26 - );
27 - }
28 -
29 - await execUnlessDry(
30 - `git commit -am "Updating yarn.lock file for ${version} release"`,
31 - {cwd, dry}
32 - );
33 - }
34 -};
35 -
36 -module.exports = async params => {
37 - return logPromise(update(params), 'Upgrading NPM dependencies');
38 -};
scripts/release/build-commands/validate-version.js deleted
-20
@@ -1,20 +0,0 @@
1 -'use strict';
2 -
3 -const chalk = require('chalk');
4 -const {readJson} = require('fs-extra');
5 -const {join} = require('path');
6 -const semver = require('semver');
7 -
8 -module.exports = async ({cwd, version}) => {
9 - if (!semver.valid(version)) {
10 - throw Error('Invalid version specified');
11 - }
12 -
13 - const rootPackage = await readJson(join(cwd, 'package.json'));
14 -
15 - if (!semver.gt(version, rootPackage.version)) {
16 - throw Error(
17 - chalk`Version {white ${rootPackage.version}} has already been published`
18 - );
19 - }
20 -};
scripts/release/build.js deleted
-82
@@ -1,82 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {exec} = require('child_process');
6 -
7 -// Follows the steps outlined in github.com/facebook/react/issues/10620
8 -const run = async () => {
9 - const chalk = require('chalk');
10 - const logUpdate = require('log-update');
11 - const {getPublicPackages, getPackages} = require('./utils');
12 -
13 - const addGitTag = require('./build-commands/add-git-tag');
14 - const buildArtifacts = require('./build-commands/build-artifacts');
15 - const checkCircleCiStatus = require('./build-commands/check-circle-ci-status');
16 - const checkEnvironmentVariables = require('./build-commands/check-environment-variables');
17 - const checkNpmPermissions = require('./build-commands/check-npm-permissions');
18 - const checkPackageDependencies = require('./build-commands/check-package-dependencies');
19 - const checkUncommittedChanges = require('./build-commands/check-uncommitted-changes');
20 - const installYarnDependencies = require('./build-commands/install-yarn-dependencies');
21 - const parseBuildParameters = require('./build-commands/parse-build-parameters');
22 - const printPostBuildSummary = require('./build-commands/print-post-build-summary');
23 - const runAutomatedTests = require('./build-commands/run-automated-tests');
24 - const runAutomatedBundleTests = require('./build-commands/run-automated-bundle-tests');
25 - const updateGit = require('./build-commands/update-git');
26 - const updateNoopRendererDependencies = require('./build-commands/update-noop-renderer-dependencies');
27 - const updatePackageVersions = require('./build-commands/update-package-versions');
28 - const updateYarnDependencies = require('./build-commands/update-yarn-dependencies');
29 - const validateVersion = require('./build-commands/validate-version');
30 -
31 - try {
32 - const params = parseBuildParameters();
33 - params.packages = getPublicPackages();
34 -
35 - await checkEnvironmentVariables(params);
36 - await validateVersion(params);
37 - await checkUncommittedChanges(params);
38 - await checkNpmPermissions(params);
39 - await updateGit(params);
40 - await checkCircleCiStatus(params);
41 - await installYarnDependencies(params);
42 - await checkPackageDependencies(params);
43 - await updateYarnDependencies(params);
44 - await runAutomatedTests(params);
45 - // Also update NPM dependencies for private packages (e.g. react-native-renderer)
46 - // Even though we don't publish these to NPM,
47 - // mismatching dependencies can cause `yarn install` to install duplicate packages.
48 - await updatePackageVersions({
49 - ...params,
50 - packages: getPackages(),
51 - });
52 - await updateNoopRendererDependencies(params);
53 - await buildArtifacts(params);
54 - await runAutomatedBundleTests(params);
55 - await addGitTag(params);
56 - await printPostBuildSummary(params);
57 - } catch (error) {
58 - logUpdate.clear();
59 -
60 - const message = error.message.trim().replace(/\n +/g, '\n');
61 - const stack = error.stack.replace(error.message, '');
62 -
63 - console.log(
64 - `${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(
65 - stack
66 - )}`
67 - );
68 -
69 - process.exit(1);
70 - }
71 -};
72 -
73 -// Install (or update) release script dependencies before proceeding.
74 -// This needs to be done before we require() the first NPM module.
75 -exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
76 - if (error) {
77 - console.error(error);
78 - process.exit(1);
79 - } else {
80 - run();
81 - }
82 -});
scripts/release/ci-add-build-info-json.js new
+72
@@ -0,0 +1,72 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +// This script is run by Circle CI (see ../scripts/circleci).
6 +// It is not meant to be run as part of the local build or publish process.
7 +// It exists to share code between the Node release scripts and CI bash scripts.
8 +
9 +// IMPORTANT:
10 +// Changes below should be mirrored in ./create-canary-commands/add-build-info-json.js
11 +
12 +const {exec} = require('child_process');
13 +const {existsSync} = require('fs');
14 +const {join} = require('path');
15 +
16 +const run = async () => {
17 + const {writeJson, readJson} = require('fs-extra');
18 + const {getBuildInfo, getPublicPackages} = require('./utils');
19 +
20 + const cwd = join(__dirname, '..', '..');
21 +
22 + const {
23 + branch,
24 + buildNumber,
25 + checksum,
26 + commit,
27 + reactVersion,
28 + } = await getBuildInfo();
29 +
30 + const packages = getPublicPackages(join(cwd, 'packages'));
31 + const packagesDir = join(cwd, 'packages');
32 +
33 + const buildInfoJSON = {
34 + branch,
35 + buildNumber,
36 + checksum,
37 + commit,
38 + environment: 'ci',
39 + reactVersion,
40 + };
41 +
42 + for (let i = 0; i < packages.length; i++) {
43 + const packageName = packages[i];
44 + const packagePath = join(packagesDir, packageName);
45 + const packageJSON = await readJson(join(packagePath, 'package.json'));
46 +
47 + // Verify all public packages include "build-info.json" in the files array.
48 + if (!packageJSON.files.includes('build-info.json')) {
49 + console.error(
50 + `${packageName} must include "build-info.json" in files array.`
51 + );
52 + process.exit(1);
53 + }
54 +
55 + // Add build info JSON to package.
56 + if (existsSync(join(packagePath, 'npm'))) {
57 + const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json');
58 + await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2});
59 + }
60 + }
61 +};
62 +
63 +// Install (or update) release script dependencies before proceeding.
64 +// This needs to be done before we require() the first NPM module.
65 +exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
66 + if (error) {
67 + console.error(error);
68 + process.exit(1);
69 + } else {
70 + run();
71 + }
72 +});
scripts/release/ci-update-package-versions.js new
+31
@@ -0,0 +1,31 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +// This script is run by Circle CI (see ../scripts/circleci).
6 +// It is not meant to be run as part of the local build or publish process.
7 +// It exists to share code between the Node release scripts and CI bash scripts.
8 +
9 +const {exec} = require('child_process');
10 +const {join} = require('path');
11 +
12 +const run = async () => {
13 + const {getBuildInfo, updateVersionsForCanary} = require('./utils');
14 +
15 + const cwd = join(__dirname, '..', '..');
16 +
17 + const {reactVersion, version} = await getBuildInfo();
18 +
19 + await updateVersionsForCanary(cwd, reactVersion, version);
20 +};
21 +
22 +// Install (or update) release script dependencies before proceeding.
23 +// This needs to be done before we require() the first NPM module.
24 +exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
25 + if (error) {
26 + console.error(error);
27 + process.exit(1);
28 + } else {
29 + run();
30 + }
31 +});
scripts/release/config.js deleted
-56
@@ -1,56 +0,0 @@
1 -'use strict';
2 -
3 -const dependencies = ['object-assign', 'prop-types'];
4 -
5 -const paramDefinitions = [
6 - {
7 - name: 'dry',
8 - type: Boolean,
9 - description: 'Build artifacts but do not commit or publish',
10 - defaultValue: false,
11 - },
12 - {
13 - name: 'path',
14 - type: String,
15 - alias: 'p',
16 - description:
17 - 'Location of React repository to release; defaults to [bold]{cwd}',
18 - defaultValue: '.',
19 - },
20 - {
21 - name: 'version',
22 - type: String,
23 - alias: 'v',
24 - description: 'Semantic version number',
25 - },
26 - {
27 - name: 'branch',
28 - type: String,
29 - alias: 'b',
30 - description: 'Branch to build from; defaults to [bold]{master}',
31 - defaultValue: 'master',
32 - },
33 - {
34 - name: 'local',
35 - type: Boolean,
36 - description:
37 - "Don't push or pull changes from remote repo. Don't check CI status.",
38 - },
39 - {
40 - name: 'tag',
41 - type: String,
42 - description:
43 - 'The npm dist tag; defaults to [bold]{latest} for a stable' +
44 - 'release, [bold]{next} for unstable',
45 - },
46 - {
47 - name: 'skipCI',
48 - type: Boolean,
49 - description: 'Skip Circle CI status check (requires confirmation)',
50 - },
51 -];
52 -
53 -module.exports = {
54 - dependencies,
55 - paramDefinitions,
56 -};
scripts/release/create-canary-commands/add-build-info-json.js new
+50
@@ -0,0 +1,50 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +// IMPORTANT:
6 +// Changes below should be mirrored in ../ci-add-build-info-json.js
7 +
8 +const {existsSync} = require('fs');
9 +const {writeJson, readJson} = require('fs-extra');
10 +const {join} = require('path');
11 +const {getPublicPackages, logPromise} = require('../utils');
12 +const theme = require('../theme');
13 +
14 +const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => {
15 + const packages = getPublicPackages(join(tempDirectory, 'packages'));
16 + const packagesDir = join(tempDirectory, 'packages');
17 +
18 + const buildInfoJSON = {
19 + branch,
20 + buildNumber: null,
21 + checksum,
22 + commit,
23 + environment: 'local',
24 + reactVersion,
25 + };
26 +
27 + for (let i = 0; i < packages.length; i++) {
28 + const packageName = packages[i];
29 + const packagePath = join(packagesDir, packageName);
30 + const packageJSON = await readJson(join(packagePath, 'package.json'));
31 +
32 + // Verify all public packages include "build-info.json" in the files array.
33 + if (!packageJSON.files.includes('build-info.json')) {
34 + console.error(
35 + theme`{error ${packageName} must include "build-info.json" in files array.}`
36 + );
37 + process.exit(1);
38 + }
39 +
40 + // Add build info JSON to package.
41 + if (existsSync(join(packagePath, 'npm'))) {
42 + const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json');
43 + await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2});
44 + }
45 + }
46 +};
47 +
48 +module.exports = async params => {
49 + return logPromise(run(params), 'Adding build metadata to packages');
50 +};
scripts/release/create-canary-commands/build-artifacts.js new
+25
@@ -0,0 +1,25 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {exec} = require('child-process-promise');
6 +const {join} = require('path');
7 +const {logPromise} = require('../utils');
8 +
9 +const run = async ({cwd, dry, tempDirectory}) => {
10 + const defaultOptions = {
11 + cwd: tempDirectory,
12 + };
13 +
14 + await exec('yarn install', defaultOptions);
15 + await exec('yarn build -- --extract-errors', defaultOptions);
16 +
17 + const tempNodeModulesPath = join(tempDirectory, 'build', 'node_modules');
18 + const buildPath = join(cwd, 'build');
19 +
20 + await exec(`cp -r ${tempNodeModulesPath} ${buildPath}`);
21 +};
22 +
23 +module.exports = async params => {
24 + return logPromise(run(params), 'Building artifacts', true);
25 +};
scripts/release/create-canary-commands/confirm-automated-testing.js new
+24
@@ -0,0 +1,24 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const clear = require('clear');
6 +const {confirm} = require('../utils');
7 +const theme = require('../theme');
8 +
9 +const run = async () => {
10 + clear();
11 +
12 + console.log(
13 + theme.caution(
14 + 'This script does not run any automated tests.' +
15 + 'You should run them manually before creating a canary release.'
16 + )
17 + );
18 +
19 + await confirm('Do you want to proceed?');
20 +
21 + clear();
22 +};
23 +
24 +module.exports = run;
scripts/release/create-canary-commands/copy-repo-to-temp-directory.js new
+34
@@ -0,0 +1,34 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {exec} = require('child-process-promise');
6 +const {join} = require('path');
7 +const {tmpdir} = require('os');
8 +const {logPromise} = require('../utils');
9 +const theme = require('../theme');
10 +
11 +const run = async ({commit, cwd, tempDirectory}) => {
12 + const directory = `react-${commit}`;
13 + const temp = tmpdir();
14 +
15 + if (tempDirectory !== join(tmpdir(), directory)) {
16 + throw Error(`Unexpected temporary directory "${tempDirectory}"`);
17 + }
18 +
19 + await exec(`rm -rf ${directory}`, {cwd: temp});
20 + await exec(`git archive --format=tar --output=${temp}/react.tgz ${commit}`, {
21 + cwd,
22 + });
23 + await exec(`mkdir ${directory}`, {cwd: temp});
24 + await exec(`tar -xf ./react.tgz -C ./${directory}`, {cwd: temp});
25 +};
26 +
27 +module.exports = async params => {
28 + return logPromise(
29 + run(params),
30 + theme`Copying React repo to temporary directory ({path ${
31 + params.tempDirectory
32 + }})`
33 + );
34 +};
scripts/release/create-canary-commands/npm-pack-and-unpack.js new
+57
@@ -0,0 +1,57 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {join} = require('path');
6 +const {exec} = require('child-process-promise');
7 +const {readdirSync} = require('fs');
8 +const {readJsonSync} = require('fs-extra');
9 +const {logPromise} = require('../utils');
10 +
11 +const run = async ({cwd, dry, tempDirectory}) => {
12 + // Cleanup from previous build.
13 + await exec(`rm -rf ./build`, {cwd});
14 +
15 + // NPM pack all built packages.
16 + // We do this to ensure that the package.json files array is correct.
17 + const builtPackages = readdirSync(join(tempDirectory, 'build/node_modules/'));
18 + for (let i = 0; i < builtPackages.length; i++) {
19 + await exec(`npm pack ./${builtPackages[i]}`, {
20 + cwd: `${tempDirectory}/build/node_modules/`,
21 + });
22 + }
23 +
24 + await exec('mkdir build', {cwd});
25 + await exec('mkdir build/node_modules', {cwd});
26 + await exec(
27 + `cp -r ${tempDirectory}/build/node_modules/*.tgz ./build/node_modules/`,
28 + {cwd}
29 + );
30 +
31 + // Unpack packages and prepare to publish.
32 + const compressedPackages = readdirSync(join(cwd, 'build/node_modules/'));
33 + for (let i = 0; i < compressedPackages.length; i++) {
34 + await exec(
35 + `tar -zxvf ./build/node_modules/${
36 + compressedPackages[i]
37 + } -C ./build/node_modules/`,
38 + {cwd}
39 + );
40 + const packageJSON = readJsonSync(
41 + join(cwd, `./build/node_modules/package/package.json`)
42 + );
43 + await exec(
44 + `mv ./build/node_modules/package ./build/node_modules/${
45 + packageJSON.name
46 + }`,
47 + {cwd}
48 + );
49 + }
50 +
51 + // Cleanup.
52 + await exec(`rm ./build/node_modules/*.tgz`, {cwd});
53 +};
54 +
55 +module.exports = async params => {
56 + return logPromise(run(params), 'Packing artifacts');
57 +};
scripts/release/create-canary-commands/update-version-numbers.js new
+13
@@ -0,0 +1,13 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {logPromise, updateVersionsForCanary} = require('../utils');
6 +const theme = require('../theme');
7 +
8 +module.exports = async ({reactVersion, tempDirectory, version}) => {
9 + return logPromise(
10 + updateVersionsForCanary(tempDirectory, reactVersion, version),
11 + theme`Updating version numbers ({version ${version}})`
12 + );
13 +};
scripts/release/create-canary.js new
+57
@@ -0,0 +1,57 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {tmpdir} = require('os');
6 +const {join} = require('path');
7 +const {getBuildInfo, handleError} = require('./utils');
8 +
9 +// This script is an escape hatch!
10 +// It exists for special case manual builds.
11 +// The typical suggested release process is to create a canary from a CI artifact.
12 +// This build script is optimized for speed and simplicity.
13 +// It doesn't run all of the tests that the CI environment runs.
14 +// You're expected to run those manually before publishing a release.
15 +
16 +const addBuildInfoJSON = require('./create-canary-commands/add-build-info-json');
17 +const buildArtifacts = require('./create-canary-commands/build-artifacts');
18 +const confirmAutomatedTesting = require('./create-canary-commands/confirm-automated-testing');
19 +const copyRepoToTempDirectory = require('./create-canary-commands/copy-repo-to-temp-directory');
20 +const npmPackAndUnpack = require('./create-canary-commands/npm-pack-and-unpack');
21 +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
22 +const updateVersionNumbers = require('./create-canary-commands/update-version-numbers');
23 +
24 +const run = async () => {
25 + try {
26 + const cwd = join(__dirname, '..', '..');
27 + const {
28 + branch,
29 + checksum,
30 + commit,
31 + reactVersion,
32 + version,
33 + } = await getBuildInfo();
34 + const tempDirectory = join(tmpdir(), `react-${commit}`);
35 + const params = {
36 + branch,
37 + checksum,
38 + commit,
39 + cwd,
40 + reactVersion,
41 + tempDirectory,
42 + version,
43 + };
44 +
45 + await confirmAutomatedTesting(params);
46 + await copyRepoToTempDirectory(params);
47 + await updateVersionNumbers(params);
48 + await addBuildInfoJSON(params);
49 + await buildArtifacts(params);
50 + await npmPackAndUnpack(params);
51 + await printPrereleaseSummary(params);
52 + } catch (error) {
53 + handleError(error);
54 + }
55 +};
56 +
57 +run();
scripts/release/package.json
+3 -1
@@ -7,10 +7,12 @@
7 "dependencies": {
8 "chalk": "^2.1.0",
9 "child-process-promise": "^2.2.1",
10 + "clear": "^0.1.0",
11 "cli-spinners": "^1.1.0",
12 "command-line-args": "^4.0.7",
13 "command-line-usage": "^4.0.1",
13 - "figlet": "^1.2.0",
14 + "diff": "^3.5.0",
15 + "folder-hash": "^2.1.2",
16 "fs-extra": "^4.0.2",
17 "log-update": "^2.1.0",
18 "prompt-promise": "^1.0.3",
scripts/release/prepare-canary-commands/check-environment-variables.js new
+30
@@ -0,0 +1,30 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const theme = require('../theme');
6 +
7 +module.exports = () => {
8 + if (!process.env.CIRCLE_CI_API_TOKEN) {
9 + console.error(
10 + theme`
11 + {error Missing CircleCI API token}
12 +
13 + The CircleCI API is used to download build artifacts.
14 + This API requires a token which must be exposed via a {underline CIRCLE_CI_API_TOKEN} environment var.
15 + In order to run this script you will need to create your own API token.
16 + Instructions can be found at:
17 +
18 + {link https://circleci.com/docs/api/v1-reference/#getting-started}
19 +
20 + To make this token available to the release script, add it to your {path .bash_profile} like so:
21 +
22 + {dimmed # React release script}
23 + export CIRCLE_CI_API_TOKEN=<your-token-here>
24 + `
25 + .replace(/\n +/g, '\n')
26 + .trim()
27 + );
28 + process.exit(1);
29 + }
30 +};
scripts/release/prepare-canary-commands/download-build-artifacts.js new
+61
@@ -0,0 +1,61 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const http = require('request-promise-json');
6 +const {exec} = require('child-process-promise');
7 +const {readdirSync} = require('fs');
8 +const {readJsonSync} = require('fs-extra');
9 +const {join} = require('path');
10 +const {logPromise} = require('../utils');
11 +const theme = require('../theme');
12 +
13 +const run = async ({build, cwd}) => {
14 + // https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci
15 + const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${build}/artifacts?circle-token=${
16 + process.env.CIRCLE_CI_API_TOKEN
17 + }`;
18 + const metadata = await http.get(metadataURL, true);
19 + const nodeModulesURL = metadata.find(
20 + entry => entry.path === 'home/circleci/project/node_modules.tgz'
21 + ).url;
22 +
23 + // Download and extract artifact
24 + await exec(`rm -rf ./build/node_modules*`, {cwd});
25 + await exec(`curl ${nodeModulesURL} --output ./build/node_modules.tgz`, {cwd});
26 + await exec(`mkdir ./build/node_modules`, {cwd});
27 + await exec(`tar zxvf ./build/node_modules.tgz -C ./build/node_modules/`, {
28 + cwd,
29 + });
30 +
31 + // Unpack packages and prepare to publish
32 + const compressedPackages = readdirSync(join(cwd, 'build/node_modules/'));
33 + for (let i = 0; i < compressedPackages.length; i++) {
34 + await exec(
35 + `tar zxvf ./build/node_modules/${
36 + compressedPackages[i]
37 + } -C ./build/node_modules/`,
38 + {cwd}
39 + );
40 + const packageJSON = readJsonSync(
41 + join(cwd, `/build/node_modules/package/package.json`)
42 + );
43 + await exec(
44 + `mv ./build/node_modules/package ./build/node_modules/${
45 + packageJSON.name
46 + }`,
47 + {cwd}
48 + );
49 + }
50 +
51 + // Cleanup
52 + await exec(`rm ./build/node_modules.tgz`, {cwd});
53 + await exec(`rm ./build/node_modules/*.tgz`, {cwd});
54 +};
55 +
56 +module.exports = async ({build, cwd}) => {
57 + return logPromise(
58 + run({build, cwd}),
59 + theme`Downloading artifacts from Circle CI for build {build ${build}}`
60 + );
61 +};
scripts/release/prepare-canary-commands/parse-params.js new
+45
@@ -0,0 +1,45 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const commandLineArgs = require('command-line-args');
6 +const commandLineUsage = require('command-line-usage');
7 +
8 +const paramDefinitions = [
9 + {
10 + name: 'build',
11 + type: Number,
12 + description:
13 + 'Circle CI build identifier (e.g. https://circleci.com/gh/facebook/react/<build>)',
14 + },
15 +];
16 +
17 +module.exports = () => {
18 + const params = commandLineArgs(paramDefinitions);
19 +
20 + if (!params.build) {
21 + const usage = commandLineUsage([
22 + {
23 + content:
24 + 'Prepare a Circle CI build to be published to NPM as a canary.',
25 + },
26 + {
27 + header: 'Options',
28 + optionList: paramDefinitions,
29 + },
30 + {
31 + header: 'Examples',
32 + content: [
33 + {
34 + desc: 'Example:',
35 + example: '$ ./prepare-canary.js [bold]{--build=}[underline]{12639}',
36 + },
37 + ],
38 + },
39 + ]);
40 + console.log(usage);
41 + process.exit(1);
42 + }
43 +
44 + return params;
45 +};
scripts/release/prepare-canary.js new
+27
@@ -0,0 +1,27 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {join} = require('path');
6 +const {getPublicPackages, handleError} = require('./utils');
7 +
8 +const checkEnvironmentVariables = require('./prepare-canary-commands/check-environment-variables');
9 +const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts');
10 +const parseParams = require('./prepare-canary-commands/parse-params');
11 +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
12 +
13 +const run = async () => {
14 + try {
15 + const params = parseParams();
16 + params.cwd = join(__dirname, '..', '..');
17 + params.packages = await getPublicPackages();
18 +
19 + await checkEnvironmentVariables(params);
20 + await downloadBuildArtifacts(params);
21 + await printPrereleaseSummary(params);
22 + } catch (error) {
23 + handleError(error);
24 + }
25 +};
26 +
27 +run();
scripts/release/prepare-stable-commands/check-out-packages.js new
+29
@@ -0,0 +1,29 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {exec} = require('child-process-promise');
6 +const {join} = require('path');
7 +const {logPromise} = require('../utils');
8 +const theme = require('../theme');
9 +
10 +const run = async ({cwd, packages, version}) => {
11 + // Cleanup from previous builds
12 + await exec(`rm -rf ./build/node_modules*`, {cwd});
13 + await exec(`mkdir ./build/node_modules`, {cwd});
14 +
15 + const nodeModulesPath = join(cwd, 'build/node_modules');
16 +
17 + // Checkout canary release from NPM for all local packages
18 + for (let i = 0; i < packages.length; i++) {
19 + const packageName = packages[i];
20 + await exec(`npm i ${packageName}@${version}`, {cwd: nodeModulesPath});
21 + }
22 +};
23 +
24 +module.exports = async params => {
25 + return logPromise(
26 + run(params),
27 + theme`Checking out canary from NPM {version ${params.version}}`
28 + );
29 +};
scripts/release/prepare-stable-commands/confirm-stable-version-numbers.js new
+58
@@ -0,0 +1,58 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const prompt = require('prompt-promise');
6 +const semver = require('semver');
7 +const theme = require('../theme');
8 +
9 +const run = async (params, versionsMap) => {
10 + const groupedVersionsMap = new Map();
11 +
12 + // Group packages with the same source versions.
13 + // We want these to stay lock-synced anyway.
14 + // This will require less redundant input from the user later,
15 + // and reduce the likelihood of human error (entering the wrong version).
16 + versionsMap.forEach((version, packageName) => {
17 + if (!groupedVersionsMap.has(version)) {
18 + groupedVersionsMap.set(version, [packageName]);
19 + } else {
20 + groupedVersionsMap.get(version).push(packageName);
21 + }
22 + });
23 +
24 + // Prompt user to confirm or override each version group.
25 + const entries = [...groupedVersionsMap.entries()];
26 + for (let i = 0; i < entries.length; i++) {
27 + const [bestGuessVersion, packages] = entries[i];
28 + const packageNames = packages.map(name => theme.package(name)).join(', ');
29 + const defaultVersion = bestGuessVersion
30 + ? theme.version(` (default ${bestGuessVersion})`)
31 + : '';
32 + const version =
33 + (await prompt(
34 + theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: `
35 + )) || bestGuessVersion;
36 + prompt.done();
37 +
38 + // Verify a valid version has been supplied.
39 + try {
40 + semver(version);
41 +
42 + packages.forEach(packageName => {
43 + versionsMap.set(packageName, version);
44 + });
45 + } catch (error) {
46 + console.log(
47 + theme`{spinnerError ✘} Version {version ${version}} is invalid.`
48 + );
49 +
50 + // Prompt again
51 + i--;
52 + }
53 + }
54 +};
55 +
56 +// Run this directly because it's fast,
57 +// and logPromise would interfere with console prompting.
58 +module.exports = run;
scripts/release/prepare-stable-commands/guess-stable-version-numbers.js new
+34
@@ -0,0 +1,34 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const semver = require('semver');
6 +const {execRead, logPromise} = require('../utils');
7 +
8 +const run = async ({cwd, packages}, versionsMap) => {
9 + for (let i = 0; i < packages.length; i++) {
10 + const packageName = packages[i];
11 +
12 + try {
13 + // In case local package JSONs are outdated,
14 + // guess the next version based on the latest NPM release.
15 + const version = await execRead(`npm show ${packageName} version`);
16 + const {major, minor, patch} = semver(version);
17 +
18 + // Guess the next version by incrementing patch.
19 + // The script will confirm this later.
20 + versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
21 + } catch (error) {
22 + // If the package has not yet been published,
23 + // we'll require a version number to be entered later.
24 + versionsMap.set(packageName, null);
25 + }
26 + }
27 +};
28 +
29 +module.exports = async (params, versionsMap) => {
30 + return logPromise(
31 + run(params, versionsMap),
32 + 'Guessing stable version numbers'
33 + );
34 +};
scripts/release/prepare-stable-commands/parse-params.js new
+44
@@ -0,0 +1,44 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const commandLineArgs = require('command-line-args');
6 +const commandLineUsage = require('command-line-usage');
7 +
8 +const paramDefinitions = [
9 + {
10 + name: 'version',
11 + type: String,
12 + description: 'Version of published canary release (e.g. 0.0.0-ddaf2b07c)',
13 + },
14 +];
15 +
16 +module.exports = () => {
17 + const params = commandLineArgs(paramDefinitions);
18 +
19 + if (!params.version) {
20 + const usage = commandLineUsage([
21 + {
22 + content: 'Prepare a published canary release to be promoted to stable.',
23 + },
24 + {
25 + header: 'Options',
26 + optionList: paramDefinitions,
27 + },
28 + {
29 + header: 'Examples',
30 + content: [
31 + {
32 + desc: 'Example:',
33 + example:
34 + '$ ./prepare-stable.js [bold]{--version=}[underline]{0.0.0-ddaf2b07c}',
35 + },
36 + ],
37 + },
38 + ]);
39 + console.log(usage);
40 + process.exit(1);
41 + }
42 +
43 + return params;
44 +};
scripts/release/prepare-stable-commands/update-stable-version-numbers.js new
+175
@@ -0,0 +1,175 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const clear = require('clear');
6 +const {readFileSync, writeFileSync} = require('fs');
7 +const {readJson, writeJson} = require('fs-extra');
8 +const {join, relative} = require('path');
9 +const {confirm, execRead, printDiff} = require('../utils');
10 +const theme = require('../theme');
11 +
12 +const run = async ({cwd, packages, version}, versionsMap) => {
13 + const nodeModulesPath = join(cwd, 'build/node_modules');
14 +
15 + // Cache all package JSONs for easy lookup below.
16 + const sourcePackageJSONs = new Map();
17 + for (let i = 0; i < packages.length; i++) {
18 + const packageName = packages[i];
19 + const sourcePackageJSON = await readJson(
20 + join(cwd, 'packages', packageName, 'package.json')
21 + );
22 + sourcePackageJSONs.set(packageName, sourcePackageJSON);
23 + }
24 +
25 + const updateDependencies = async (targetPackageJSON, key) => {
26 + const targetDependencies = targetPackageJSON[key];
27 + if (targetDependencies) {
28 + const sourceDependencies = sourcePackageJSONs.get(targetPackageJSON.name)[
29 + key
30 + ];
31 +
32 + for (let i = 0; i < packages.length; i++) {
33 + const dependencyName = packages[i];
34 + const targetDependency = targetDependencies[dependencyName];
35 +
36 + if (targetDependency) {
37 + // For example, say we're updating react-dom's dependency on scheduler.
38 + // We compare source packages to determine what the new scheduler dependency constraint should be.
39 + // To do this, we look at both the local version of the scheduler (e.g. 0.11.0),
40 + // and the dependency constraint in the local version of react-dom (e.g. scheduler@^0.11.0).
41 + const sourceDependencyVersion = sourcePackageJSONs.get(dependencyName)
42 + .version;
43 + const sourceDependencyConstraint = sourceDependencies[dependencyName];
44 +
45 + // If the source dependency's version and the constraint match,
46 + // we will need to update the constraint to point at the dependency's new release version,
47 + // (e.g. scheduler@^0.11.0 becomes scheduler@^0.12.0 when we release scheduler 0.12.0).
48 + // Otherwise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases).
49 + // Note that in both cases, we must update the target package JSON,
50 + // since canary releases are all locked to the canary version (e.g. 0.0.0-ddaf2b07c).
51 + if (
52 + sourceDependencyVersion ===
53 + sourceDependencyConstraint.replace(/^[\^\~]/, '')
54 + ) {
55 + targetDependencies[
56 + dependencyName
57 + ] = sourceDependencyConstraint.replace(
58 + sourceDependencyVersion,
59 + versionsMap.get(dependencyName)
60 + );
61 + } else {
62 + targetDependencies[dependencyName] = sourceDependencyConstraint;
63 + }
64 + }
65 + }
66 + }
67 + };
68 +
69 + // Update all package JSON versions and their dependencies/peerDependencies.
70 + // This must be done in a way that respects semver constraints (e.g. 16.7.0, ^16.7.0, ^16.0.0).
71 + // To do this, we use the dependencies defined in the source package JSONs,
72 + // because the canary dependencies have already been flattened to an exact match (e.g. 0.0.0-ddaf2b07c).
73 + for (let i = 0; i < packages.length; i++) {
74 + const packageName = packages[i];
75 + const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
76 + const packageJSON = await readJson(packageJSONPath);
77 + packageJSON.version = versionsMap.get(packageName);
78 +
79 + await updateDependencies(packageJSON, 'dependencies');
80 + await updateDependencies(packageJSON, 'peerDependencies');
81 +
82 + await writeJson(packageJSONPath, packageJSON, {spaces: 2});
83 + }
84 +
85 + clear();
86 +
87 + // Print the map of versions and their dependencies for confirmation.
88 + const printDependencies = (maybeDependency, label) => {
89 + if (maybeDependency) {
90 + for (let dependencyName in maybeDependency) {
91 + if (packages.includes(dependencyName)) {
92 + console.log(
93 + theme`• {package ${dependencyName}} {version ${
94 + maybeDependency[dependencyName]
95 + }} {dimmed ${label}}`
96 + );
97 + }
98 + }
99 + }
100 + };
101 + for (let i = 0; i < packages.length; i++) {
102 + const packageName = packages[i];
103 + const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
104 + const packageJSON = await readJson(packageJSONPath);
105 + console.log(
106 + theme`\n{package ${packageName}} {version ${versionsMap.get(
107 + packageName
108 + )}}`
109 + );
110 + printDependencies(packageJSON.dependencies, 'dependency');
111 + printDependencies(packageJSON.peerDependencies, 'peer');
112 + }
113 + await confirm('Do the versions above look correct?');
114 +
115 + clear();
116 +
117 + // A separate "React version" is used for the embedded renderer version to support DevTools,
118 + // since it needs to distinguish between different version ranges of React.
119 + // We need to replace it as well as the canary version number.
120 + const buildInfoPath = join(nodeModulesPath, 'react', 'build-info.json');
121 + const {reactVersion} = await readJson(buildInfoPath);
122 +
123 + if (!reactVersion) {
124 + console.error(
125 + theme`{error Unsupported or invalid build metadata in} {path build/node_modules/react/build-info.json}` +
126 + theme`{error . This could indicate that you have specified an outdated canary version.}`
127 + );
128 + process.exit(1);
129 + }
130 +
131 + // We print the diff to the console for review,
132 + // but it can be large so let's also write it to disk.
133 + const diffPath = join(cwd, 'build', 'temp.diff');
134 + let diff = '';
135 + let numFilesModified = 0;
136 +
137 + // Find-and-replace hard coded version (in built JS) for renderers.
138 + for (let i = 0; i < packages.length; i++) {
139 + const packageName = packages[i];
140 + const packagePath = join(nodeModulesPath, packageName);
141 +
142 + let files = await execRead(
143 + `find ${packagePath} -name '*.js' -exec echo {} \\;`,
144 + {cwd}
145 + );
146 + files = files.split('\n');
147 + files.forEach(path => {
148 + const newStableVersion = versionsMap.get(packageName);
149 + const beforeContents = readFileSync(path, 'utf8', {cwd});
150 + let afterContents = beforeContents;
151 + // Replace all canary version numbers (e.g. header @license).
152 + while (afterContents.indexOf(version) >= 0) {
153 + afterContents = afterContents.replace(version, newStableVersion);
154 + }
155 + // Replace inline renderer version numbers (e.g. shared/ReactVersion).
156 + while (afterContents.indexOf(reactVersion) >= 0) {
157 + afterContents = afterContents.replace(reactVersion, newStableVersion);
158 + }
159 + if (beforeContents !== afterContents) {
160 + numFilesModified++;
161 + diff += printDiff(path, beforeContents, afterContents);
162 + writeFileSync(path, afterContents, {cwd});
163 + }
164 + });
165 + }
166 + writeFileSync(diffPath, diff, {cwd});
167 + console.log(theme.header(`\n${numFilesModified} files have been updated.`));
168 + console.log(
169 + theme`A full diff is available at {path ${relative(cwd, diffPath)}}.`
170 + );
171 + await confirm('Do the changes above look correct?');
172 +};
173 +
174 +// Run this directly because logPromise would interfere with printing package dependencies.
175 +module.exports = run;
scripts/release/prepare-stable.js new
+36
@@ -0,0 +1,36 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {join} = require('path');
6 +const {getPublicPackages, handleError} = require('./utils');
7 +
8 +const checkOutPackages = require('./prepare-stable-commands/check-out-packages');
9 +const confirmStableVersionNumbers = require('./prepare-stable-commands/confirm-stable-version-numbers');
10 +const guessStableVersionNumbers = require('./prepare-stable-commands/guess-stable-version-numbers');
11 +const parseParams = require('./prepare-stable-commands/parse-params');
12 +const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
13 +const updateStableVersionNumbers = require('./prepare-stable-commands/update-stable-version-numbers');
14 +
15 +const run = async () => {
16 + try {
17 + const params = parseParams();
18 + params.cwd = join(__dirname, '..', '..');
19 + params.packages = await getPublicPackages();
20 +
21 + // Map of package name to upcoming stable version.
22 + // This Map is initially populated with guesses based on local versions.
23 + // The developer running the release later confirms or overrides each version.
24 + const versionsMap = new Map();
25 +
26 + await checkOutPackages(params);
27 + await guessStableVersionNumbers(params, versionsMap);
28 + await confirmStableVersionNumbers(params, versionsMap);
29 + await updateStableVersionNumbers(params, versionsMap);
30 + await printPrereleaseSummary(params);
31 + } catch (error) {
32 + handleError(error);
33 + }
34 +};
35 +
36 +run();
scripts/release/publish-commands/check-build-status.js deleted
-35
@@ -1,35 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const {existsSync} = require('fs');
7 -const {readJson} = require('fs-extra');
8 -const {join} = require('path');
9 -
10 -module.exports = async ({cwd, version, local}) => {
11 - if (local) {
12 - return;
13 - }
14 - const packagePath = join(
15 - cwd,
16 - 'build',
17 - 'node_modules',
18 - 'react',
19 - 'package.json'
20 - );
21 -
22 - if (!existsSync(packagePath)) {
23 - throw Error('No build found');
24 - }
25 -
26 - const packageJson = await readJson(packagePath);
27 -
28 - if (packageJson.version !== version) {
29 - throw Error(
30 - chalk`Expected version {bold.white ${version}} but found {bold.white ${
31 - packageJson.version
32 - }}`
33 - );
34 - }
35 -};
scripts/release/publish-commands/check-npm-permissions.js renamed
+15 -11
@@ -2,10 +2,10 @@
2
3 'use strict';
4
5 -const chalk = require('chalk');
5 const {execRead, logPromise} = require('../utils');
6 +const theme = require('../theme');
7
8 -module.exports = async ({packages}) => {
8 +const run = async ({cwd, packages, version}) => {
9 const currentUser = await execRead('npm whoami');
10 const failedProjects = [];
11
@@ -22,19 +22,23 @@ module.exports = async ({packages}) => {
22
23 await logPromise(
24 Promise.all(packages.map(checkProject)),
25 - `Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions`
25 + theme`Checking NPM permissions for {underline ${currentUser}}.`
26 );
27
28 if (failedProjects.length) {
29 - throw Error(
30 - chalk`
31 - Insufficient NPM permissions
32 -
33 - {white NPM user {yellow.bold ${currentUser}} is not an owner for:}
34 - {red ${failedProjects.join(', ')}}
35 -
36 - {white Please contact a React team member to be added to the above project(s).}
29 + console.error(
30 + theme`
31 + {error Insufficient NPM permissions}
32 + \nNPM user {underline ${currentUser}} is not an owner for: ${failedProjects
33 + .map(name => theme.package(name))
34 + .join(', ')}
35 + \nPlease contact a React team member to be added to the above project(s).
36 `
37 + .replace(/\n +/g, '\n')
38 + .trim()
39 );
40 + process.exit(1);
41 }
42 };
43 +
44 +module.exports = run;
scripts/release/publish-commands/commit-changelog.js deleted
-24
@@ -1,24 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {exec} = require('child-process-promise');
6 -const {execRead, logPromise} = require('../utils');
7 -
8 -const update = async ({cwd, dry, version}) => {
9 - const modifiedFiles = await execRead('git ls-files -m', {cwd});
10 -
11 - if (!dry && modifiedFiles.includes('CHANGELOG.md')) {
12 - await exec('git add CHANGELOG.md', {cwd});
13 - await exec(
14 - `git commit -am "Updating CHANGELOG.md for ${version} release"`,
15 - {
16 - cwd,
17 - }
18 - );
19 - }
20 -};
21 -
22 -module.exports = async params => {
23 - return logPromise(update(params), 'Committing CHANGELOG updates');
24 -};
scripts/release/publish-commands/confirm-version-and-tags.js new
+46
@@ -0,0 +1,46 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const clear = require('clear');
6 +const {readJson} = require('fs-extra');
7 +const {join} = require('path');
8 +const {confirm} = require('../utils');
9 +const theme = require('../theme');
10 +
11 +const run = async ({cwd, packages, tags}) => {
12 + clear();
13 +
14 + if (tags.length === 1) {
15 + console.log(
16 + theme`{spinnerSuccess ✓} You are about the publish the following packages under the tag {tag ${tags}}`
17 + );
18 + } else {
19 + console.log(
20 + theme`{spinnerSuccess ✓} You are about the publish the following packages under the tags {tag ${tags.join(
21 + ', '
22 + )}}`
23 + );
24 + }
25 +
26 + // Cache all package JSONs for easy lookup below.
27 + for (let i = 0; i < packages.length; i++) {
28 + const packageName = packages[i];
29 + const packageJSONPath = join(
30 + cwd,
31 + 'build/node_modules',
32 + packageName,
33 + 'package.json'
34 + );
35 + const packageJSON = await readJson(packageJSONPath);
36 + console.log(
37 + theme`• {package ${packageName}} {version ${packageJSON.version}}`
38 + );
39 + }
40 +
41 + await confirm('Do you want to proceed?');
42 +};
43 +
44 +// Run this directly because it's fast,
45 +// and logPromise would interfere with console prompting.
46 +module.exports = run;
scripts/release/publish-commands/download-error-codes-from-ci.js new
+51
@@ -0,0 +1,51 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const http = require('request-promise-json');
6 +const {exec} = require('child-process-promise');
7 +const {readJsonSync} = require('fs-extra');
8 +const {logPromise} = require('../utils');
9 +const theme = require('../theme');
10 +
11 +const run = async ({cwd, tags}) => {
12 + if (!tags.includes('latest')) {
13 + // Don't update error-codes for alphas.
14 + return;
15 + }
16 +
17 + // All packages are built from a single source revision,
18 + // so it is safe to read build info from any one of them.
19 + const {buildNumber, environment} = readJsonSync(
20 + `${cwd}/build/node_modules/react/build-info.json`
21 + );
22 +
23 + // If this release was created on Circle CI, grab the updated error codes from there.
24 + // Else the user will have to manually regenerate them.
25 + if (environment === 'ci') {
26 + // https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci
27 + // eslint-disable-next-line max-len
28 + const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildNumber}/artifacts?circle-token=${
29 + process.env.CIRCLE_CI_API_TOKEN
30 + }`;
31 + const metadata = await http.get(metadataURL, true);
32 +
33 + // Each container stores an "error-codes" artifact, unfortunately.
34 + // We want to use the one that also ran `yarn build` since it may have modifications.
35 + const {node_index} = metadata.find(
36 + entry => entry.path === 'home/circleci/project/node_modules.tgz'
37 + );
38 + const {url} = metadata.find(
39 + entry =>
40 + entry.node_index === node_index &&
41 + entry.path === 'home/circleci/project/scripts/error-codes/codes.json'
42 + );
43 +
44 + // Download and stage changers
45 + await exec(`curl ${url} --output ./scripts/error-codes/codes.json`, {cwd});
46 + }
47 +};
48 +
49 +module.exports = async params => {
50 + return logPromise(run(params), theme`Retrieving error codes`);
51 +};
scripts/release/publish-commands/get-npm-two-factor-auth.js deleted
-15
@@ -1,15 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const logUpdate = require('log-update');
7 -const prompt = require('prompt-promise');
8 -
9 -module.exports = async params => {
10 - logUpdate(chalk`{green ✓} Npm two-factor auth code {gray (or blank)}: `);
11 - const otp = await prompt('');
12 - prompt.done();
13 - logUpdate.clear();
14 - return otp.trim() || null;
15 -};
scripts/release/publish-commands/parse-params.js new
+55
@@ -0,0 +1,55 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const commandLineArgs = require('command-line-args');
6 +const commandLineUsage = require('command-line-usage');
7 +
8 +const paramDefinitions = [
9 + {
10 + name: 'dry',
11 + type: Boolean,
12 + description: 'Dry run command without actually publishing to NPM.',
13 + defaultValue: false,
14 + },
15 + {
16 + name: 'tags',
17 + type: String,
18 + multiple: true,
19 + description: 'NPM tags to point to the new release.',
20 + },
21 +];
22 +
23 +module.exports = () => {
24 + const params = commandLineArgs(paramDefinitions);
25 +
26 + if (!params.tags || params.tags.length === 0) {
27 + const usage = commandLineUsage([
28 + {
29 + content:
30 + 'Publishes the current contents of "build/node_modules" to NPM.',
31 + },
32 + {
33 + header: 'Options',
34 + optionList: paramDefinitions,
35 + },
36 + {
37 + header: 'Examples',
38 + content: [
39 + {
40 + desc: 'Dry run test:',
41 + example: '$ scripts/release/publish.js --dry --tags next',
42 + },
43 + {
44 + desc: 'Publish a new stable:',
45 + example: '$ scripts/release/publish.js --tags next latest',
46 + },
47 + ],
48 + },
49 + ]);
50 + console.log(usage);
51 + process.exit(1);
52 + }
53 +
54 + return params;
55 +};
scripts/release/publish-commands/parse-publish-params.js deleted
-57
@@ -1,57 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const commandLineArgs = require('command-line-args');
7 -const commandLineUsage = require('command-line-usage');
8 -const figlet = require('figlet');
9 -const {paramDefinitions} = require('../config');
10 -
11 -module.exports = () => {
12 - const params = commandLineArgs(paramDefinitions);
13 -
14 - if (!params.version) {
15 - const usage = commandLineUsage([
16 - {
17 - content: chalk
18 - .hex('#61dafb')
19 - .bold(figlet.textSync('react', {font: 'Graffiti'})),
20 - raw: true,
21 - },
22 - {
23 - content: 'Automated release publishing script.',
24 - },
25 - {
26 - header: 'Options',
27 - optionList: paramDefinitions,
28 - },
29 - {
30 - header: 'Examples',
31 - content: [
32 - {
33 - desc: '1. A concise example.',
34 - example: '$ ./publish.js [bold]{-v} [underline]{16.0.0}',
35 - },
36 - {
37 - desc: '2. Dry run publish a release candidate.',
38 - example:
39 - '$ ./publish.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}',
40 - },
41 - {
42 - desc: '3. Release from another checkout.',
43 - example:
44 - '$ ./publish.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo',
45 - },
46 - ],
47 - },
48 - ]);
49 - console.log(usage);
50 - process.exit(1);
51 - }
52 -
53 - return {
54 - ...params,
55 - cwd: params.path, // For script convenience
56 - };
57 -};
scripts/release/publish-commands/print-follow-up-instructions.js new
+84
@@ -0,0 +1,84 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const clear = require('clear');
6 +const {readJsonSync} = require('fs-extra');
7 +const theme = require('../theme');
8 +
9 +const run = async ({cwd, packages, tags}) => {
10 + // All packages are built from a single source revision,
11 + // so it is safe to read the commit number from any one of them.
12 + const {commit, environment} = readJsonSync(
13 + `${cwd}/build/node_modules/react/build-info.json`
14 + );
15 +
16 + // Tags are named after the react version.
17 + const {version} = readJsonSync(
18 + `${cwd}/build/node_modules/react/package.json`
19 + );
20 +
21 + clear();
22 +
23 + console.log(
24 + theme.caution`The release has been published but you're not done yet!`
25 + );
26 +
27 + if (tags.includes('latest')) {
28 + console.log();
29 + console.log(
30 + theme.header`Please review and commit all local, staged changes.`
31 + );
32 +
33 + console.log();
34 + console.log('Version numbers have been updated in the following files:');
35 + for (let i = 0; i < packages.length; i++) {
36 + const packageName = packages[i];
37 + console.log(theme.path`• packages/%s/package.json`, packageName);
38 + }
39 + console.log(theme.path`• packages/shared/ReactVersion.js`);
40 +
41 + console.log();
42 + if (environment === 'ci') {
43 + console.log('Auto-generated error codes have been updated as well:');
44 + console.log(theme.path`• scripts/error-codes/codes.json`);
45 + } else {
46 + console.log(
47 + theme`{caution The release that was just published was created locally.} ` +
48 + theme`Because of this, you will need to update the generated ` +
49 + theme`{path scripts/error-codes/codes.json} file manually:`
50 + );
51 + console.log(theme` {command git checkout} {version ${commit}}`);
52 + console.log(theme` {command yarn build -- --extract-errors}`);
53 + }
54 + }
55 +
56 + console.log();
57 + console.log(
58 + theme`{header Don't forget to update and commit the }{path CHANGELOG}`
59 + );
60 +
61 + // Prompt the release engineer to tag the commit and update the CHANGELOG.
62 + // (The script could automatically do this, but this seems safer.)
63 + console.log();
64 + console.log(
65 + theme.header`Tag the source for this release in Git with the following command:`
66 + );
67 + console.log(
68 + theme` {command git tag -a v}{version %s} {command -m "v%s"} {version %s}`,
69 + version,
70 + version,
71 + commit
72 + );
73 + console.log(theme.command` git push origin --tags`);
74 +
75 + console.log();
76 + console.log(theme.header`Lastly, please fill in the release on GitHub:`);
77 + console.log(
78 + theme.link`https://github.com/facebook/react/releases/tag/v%s`,
79 + version
80 + );
81 + console.log();
82 +};
83 +
84 +module.exports = run;
scripts/release/publish-commands/print-post-publish-summary.js deleted
-78
@@ -1,78 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const chalk = require('chalk');
6 -const semver = require('semver');
7 -const {getUnexecutedCommands} = require('../utils');
8 -
9 -const printSteps = steps => {
10 - return steps
11 - .filter(Boolean) // Remove no-op steps
12 - .map((step, index) => `${index + 1}. ${step}`)
13 - .join('\n');
14 -};
15 -
16 -const printSections = sections => {
17 - return sections
18 - .map((section, index) => {
19 - const [title, ...steps] = section;
20 -
21 - return chalk`
22 - {bold.underline Step ${index + 1}: ${title}}
23 -
24 - ${printSteps(steps)}
25 - `.replace(/\n +/g, '\n');
26 - })
27 - .join('');
28 -};
29 -
30 -module.exports = ({dry, version}) => {
31 - const isPrerelease = semver.prerelease(version);
32 -
33 - const sections = [];
34 -
35 - // Certain follow-up steps are for stable releases only.
36 - if (!isPrerelease) {
37 - sections.push([
38 - 'Create GitHub release',
39 - chalk`Open new release page: {blue.bold https://github.com/facebook/react/releases/new}`,
40 - chalk`Choose {bold ${version}} from the dropdown menu`,
41 - chalk`Paste the new release notes from {yellow.bold CHANGELOG.md}`,
42 - chalk`Attach all files in {yellow.bold build/dist/*.js} except {yellow.bold react-art.*} to the release.`,
43 - chalk`Press {bold "Publish release"}!`,
44 - ]);
45 -
46 - sections.push([
47 - 'Update the version on reactjs.org',
48 - chalk`Git clone (or update) {blue.bold https://github.com/reactjs/reactjs.org}`,
49 - chalk`Open the {bold.yellow src/site-constants.js} file`,
50 - chalk`Update the {bold version} value to {bold ${version}}`,
51 - chalk`Open a Pull Request to {bold master}`,
52 - ]);
53 - }
54 -
55 - sections.push([
56 - 'Test the new release',
57 - chalk`Install CRA: {bold npm i -g create-react-app}`,
58 - chalk`Create a test application: {bold create-react-app myapp && cd myapp}`,
59 - isPrerelease
60 - ? chalk`Install the pre-release versions: {bold yarn add react@next react-dom@next}`
61 - : null,
62 - chalk`Run the app: {bold yarn start}`,
63 - ]);
64 -
65 - sections.push([
66 - 'Notify the DOM team',
67 - chalk`Notify DOM team members: {bold @nhunzaker @jquense @aweary}`,
68 - ]);
69 -
70 - console.log(
71 - chalk`
72 - {green.bold Publish successful!}
73 - ${getUnexecutedCommands()}
74 - Next there are a couple of manual steps:
75 - ${printSections(sections)}
76 - `.replace(/\n +/g, '\n')
77 - );
78 -};
scripts/release/publish-commands/prompt-for-otp.js new
+23
@@ -0,0 +1,23 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const prompt = require('prompt-promise');
6 +const theme = require('../theme');
7 +
8 +const run = async () => {
9 + while (true) {
10 + const otp = await prompt('NPM 2-factor auth code: ');
11 + prompt.done();
12 +
13 + if (otp) {
14 + return otp;
15 + } else {
16 + console.log();
17 + console.log(theme.error`Two-factor auth is required to publish.`);
18 + // (Ask again.)
19 + }
20 + }
21 +};
22 +
23 +module.exports = run;
scripts/release/publish-commands/publish-to-npm.js
+47 -80
@@ -2,94 +2,61 @@
2
3 'use strict';
4
5 -const chalk = require('chalk');
6 -const {readJson} = require('fs-extra');
5 +const {exec} = require('child-process-promise');
6 +const {readJsonSync} = require('fs-extra');
7 const {join} = require('path');
8 -const semver = require('semver');
9 -const {execRead, execUnlessDry, logPromise} = require('../utils');
10 -
11 -const push = async ({cwd, dry, otp, packages, version, tag}) => {
12 - const errors = [];
13 - const isPrerelease = semver.prerelease(version);
14 -
15 - let resolvedTag = tag;
16 - if (tag === undefined) {
17 - // No tag was provided. Default to `latest` for stable releases and `next`
18 - // for prereleases
19 - resolvedTag = isPrerelease ? 'next' : 'latest';
20 - } else if (tag === 'latest' && isPrerelease) {
21 - throw new Error('The tag `latest` can only be used for stable versions.');
22 - }
23 -
24 - // Pass two factor auth code if provided:
25 - // https://docs.npmjs.com/getting-started/using-two-factor-authentication
26 - const twoFactorAuth = otp != null ? `--otp ${otp}` : '';
27 -
28 - const publishProject = async project => {
29 - try {
30 - const path = join(cwd, 'build', 'node_modules', project);
31 - await execUnlessDry(`npm publish --tag ${resolvedTag} ${twoFactorAuth}`, {
32 - cwd: path,
33 - dry,
34 - });
35 -
36 - const packagePath = join(
37 - cwd,
38 - 'build',
39 - 'node_modules',
40 - project,
41 - 'package.json'
8 +const {confirm, execRead} = require('../utils');
9 +const theme = require('../theme');
10 +
11 +const run = async ({cwd, dry, packages, tags}, otp) => {
12 + for (let i = 0; i < packages.length; i++) {
13 + const packageName = packages[i];
14 + const packagePath = join(cwd, 'build/node_modules', packageName);
15 + const {version} = readJsonSync(join(packagePath, 'package.json'));
16 +
17 + // Check if this package version has already been published.
18 + // If so we might be resuming from a previous run.
19 + // We could infer this by comparing the build-info.json,
20 + // But for now the easiest way is just to ask if this is expected.
21 + const info = await execRead(`npm view ${packageName}@${version}`);
22 + if (info) {
23 + console.log(
24 + theme`{package ${packageName}} {version ${version}} has already been published.`
25 + );
26 + await confirm('Is this expected?');
27 + } else {
28 + console.log(
29 + theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`
30 );
43 - const packageJSON = await readJson(packagePath);
44 - const packageVersion = packageJSON.version;
31
32 + // Publish the package and tag it.
33 if (!dry) {
47 - // Wait a couple of seconds before querying NPM for status;
48 - // Anecdotally, querying too soon can result in a false negative.
49 - await new Promise(resolve => setTimeout(resolve, 5000));
50 -
51 - const status = JSON.parse(
52 - await execRead(`npm info ${project} dist-tags --json`)
53 - );
54 - const remoteVersion = status[resolvedTag];
55 -
56 - // Compare remote version to package.json version,
57 - // To better handle the case of pre-release versions.
58 - if (remoteVersion !== packageVersion) {
59 - throw Error(
60 - chalk`Published version {yellow.bold ${packageVersion}} for ` +
61 - chalk`{bold ${project}} but NPM shows {yellow.bold ${remoteVersion}}`
62 - );
63 - }
64 -
65 - // If we've just published a stable release,
66 - // Update the @next tag to also point to it (so @next doesn't lag behind).
67 - // Skip this step if we have a manually specified tag.
68 - // This is an escape hatch for us to interleave alpha and stable releases.
69 - if (tag === undefined && !isPrerelease) {
70 - await execUnlessDry(
71 - `npm dist-tag add ${project}@${packageVersion} next ${twoFactorAuth}`,
72 - {cwd: path, dry}
34 + await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
35 + cwd: packagePath,
36 + });
37 + }
38 + console.log(theme.command(` cd ${packagePath}`));
39 + console.log(theme.command(` npm publish --tag=${tags[0]} --otp=${otp}`));
40 +
41 + for (let j = 1; j < tags.length; j++) {
42 + if (!dry) {
43 + await exec(
44 + `npm dist-tag add ${packageName}@${version} ${
45 + tags[j]
46 + } --otp=${otp}`,
47 + {cwd: packagePath}
48 );
49 }
50 + console.log(
51 + theme.command(
52 + ` npm dist-tag add ${packageName}@${version} ${
53 + tags[j]
54 + } --otp=${otp}`
55 + )
56 + );
57 }
76 - } catch (error) {
77 - errors.push(error.stack);
58 }
79 - };
80 -
81 - await Promise.all(packages.map(publishProject));
82 -
83 - if (errors.length > 0) {
84 - throw Error(
85 - chalk`
86 - Failure publishing to NPM
87 -
88 - {white ${errors.join('\n\n')}}`
89 - );
59 }
60 };
61
93 -module.exports = async params => {
94 - return logPromise(push(params), 'Publishing packages to NPM');
95 -};
62 +module.exports = run;
scripts/release/publish-commands/push-git-remote.js deleted
-17
@@ -1,17 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {execUnlessDry, logPromise} = require('../utils');
6 -
7 -const push = async ({cwd, dry}) => {
8 - await execUnlessDry('git push', {cwd, dry});
9 - await execUnlessDry('git push --tags', {cwd, dry});
10 -};
11 -
12 -module.exports = async params => {
13 - if (params.local) {
14 - return;
15 - }
16 - return logPromise(push(params), 'Pushing to git remote');
17 -};
scripts/release/publish-commands/update-stable-version-numbers.js new
+49
@@ -0,0 +1,49 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {readFileSync, writeFileSync} = require('fs');
6 +const {readJson, writeJson} = require('fs-extra');
7 +const {join} = require('path');
8 +
9 +const run = async ({cwd, packages, tags}) => {
10 + if (!tags.includes('latest')) {
11 + // Don't update version numbers for alphas.
12 + return;
13 + }
14 +
15 + const nodeModulesPath = join(cwd, 'build/node_modules');
16 + const packagesPath = join(cwd, 'packages');
17 +
18 + // Update package versions and dependencies (in source) to mirror what was published to NPM.
19 + for (let i = 0; i < packages.length; i++) {
20 + const packageName = packages[i];
21 + const publishedPackageJSON = await readJson(
22 + join(nodeModulesPath, packageName, 'package.json')
23 + );
24 + const sourcePackageJSONPath = join(
25 + packagesPath,
26 + packageName,
27 + 'package.json'
28 + );
29 + const sourcePackageJSON = await readJson(sourcePackageJSONPath);
30 + sourcePackageJSON.version = publishedPackageJSON.version;
31 + sourcePackageJSON.dependencies = publishedPackageJSON.dependencies;
32 + sourcePackageJSON.peerDependencies = publishedPackageJSON.peerDependencies;
33 +
34 + await writeJson(sourcePackageJSONPath, sourcePackageJSON, {spaces: 2});
35 + }
36 +
37 + // Update the shared React version source file.
38 + const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
39 + const {version} = await readJson(
40 + join(nodeModulesPath, 'react', 'package.json')
41 + );
42 + const sourceReactVersion = readFileSync(
43 + sourceReactVersionPath,
44 + 'utf8'
45 + ).replace(/module\.exports = '[^']+';/, `module.exports = '${version}';`);
46 + writeFileSync(sourceReactVersionPath, sourceReactVersion);
47 +};
48 +
49 +module.exports = run;
scripts/release/publish-commands/validate-tags.js new
+29
@@ -0,0 +1,29 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {readJson} = require('fs-extra');
6 +const {join} = require('path');
7 +const theme = require('../theme');
8 +
9 +const run = async ({cwd, packages, tags}) => {
10 + // Prevent a canary release from ever being published as @latest
11 + const packageJSONPath = join(
12 + cwd,
13 + 'build',
14 + 'node_modules',
15 + 'react',
16 + 'package.json'
17 + );
18 + const {version} = await readJson(packageJSONPath);
19 + if (version.indexOf('0.0.0') === 0) {
20 + if (tags.includes('latest')) {
21 + console.log(
22 + theme`{error Canary release} {version ${version}} {error cannot be tagged as} {tag latest}`
23 + );
24 + process.exit(1);
25 + }
26 + }
27 +};
28 +
29 +module.exports = run;
scripts/release/publish.js
+25 -33
@@ -2,43 +2,35 @@
2
3 'use strict';
4
5 -const chalk = require('chalk');
6 -const logUpdate = require('log-update');
7 -const {getPublicPackages} = require('./utils');
5 +const {join} = require('path');
6 +const {getPublicPackages, handleError} = require('./utils');
7 +
8 +const checkNPMPermissions = require('./publish-commands/check-npm-permissions');
9 +const confirmVersionAndTags = require('./publish-commands/confirm-version-and-tags');
10 +const downloadErrorCodesFromCI = require('./publish-commands/download-error-codes-from-ci');
11 +const parseParams = require('./publish-commands/parse-params');
12 +const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions');
13 +const promptForOTP = require('./publish-commands/prompt-for-otp');
14 +const publishToNPM = require('./publish-commands/publish-to-npm');
15 +const updateStableVersionNumbers = require('./publish-commands/update-stable-version-numbers');
16 +const validateTags = require('./publish-commands/validate-tags');
17
9 -const checkBuildStatus = require('./publish-commands/check-build-status');
10 -const commitChangelog = require('./publish-commands/commit-changelog');
11 -const getNpmTwoFactorAuth = require('./publish-commands/get-npm-two-factor-auth');
12 -const parsePublishParams = require('./publish-commands/parse-publish-params');
13 -const printPostPublishSummary = require('./publish-commands/print-post-publish-summary');
14 -const pushGitRemote = require('./publish-commands/push-git-remote');
15 -const publishToNpm = require('./publish-commands/publish-to-npm');
16 -
17 -// Follows the steps outlined in github.com/facebook/react/issues/10620
18 const run = async () => {
19 - const params = parsePublishParams();
20 - params.packages = getPublicPackages();
21 -
19 try {
23 - await checkBuildStatus(params);
24 - await commitChangelog(params);
25 - await pushGitRemote(params);
26 - params.otp = await getNpmTwoFactorAuth(params);
27 - await publishToNpm(params);
28 - await printPostPublishSummary(params);
20 + const params = parseParams();
21 + params.cwd = join(__dirname, '..', '..');
22 + params.packages = await getPublicPackages();
23 +
24 + await validateTags(params);
25 + await confirmVersionAndTags(params);
26 + await checkNPMPermissions(params);
27 + const otp = await promptForOTP(params);
28 + await publishToNPM(params, otp);
29 + await downloadErrorCodesFromCI(params);
30 + await updateStableVersionNumbers(params);
31 + await printFollowUpInstructions(params);
32 } catch (error) {
30 - logUpdate.clear();
31 -
32 - const message = error.message.trim().replace(/\n +/g, '\n');
33 - const stack = error.stack.replace(error.message, '');
34 -
35 - console.log(
36 - `${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(
37 - stack
38 - )}`
39 - );
40 -
41 - process.exit(1);
33 + handleError(error);
34 }
35 };
36
scripts/release/shared-commands/print-prerelease-summary.js new
+38
@@ -0,0 +1,38 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const clear = require('clear');
6 +const {join, relative} = require('path');
7 +const theme = require('../theme');
8 +
9 +module.exports = ({cwd}) => {
10 + const publishPath = relative(
11 + process.env.PWD,
12 + join(__dirname, '../publish.js')
13 + );
14 +
15 + clear();
16 +
17 + console.log(
18 + theme`
19 + {caution A release candidate has been prepared but you're not done yet!}
20 +
21 + You can review the contents of this release in {path ./build/node_modules/}
22 +
23 + {header Before publishing, please smoke test the packages!}
24 +
25 + 1. Open {path ./fixtures/packaging/babel-standalone/dev.html} in the browser.
26 + 2. It should say {quote "Hello world!"}
27 + 3. Next go to {path ./fixtures/packaging} and run {command node build-all.js}
28 + 4. Go to the repo root and {command npx pushstate-server . 9000}
29 + 5. Open {link http://localhost:9000/fixtures/packaging}
30 + 6. Verify every iframe shows {quote "Hello world!"}
31 +
32 + After completing the above steps, you can publish this release by running:
33 + {path ${publishPath}}
34 + `
35 + .replace(/\n +/g, '\n')
36 + .trim()
37 + );
38 +};
scripts/release/theme.js new
+35
@@ -0,0 +1,35 @@
1 +'use strict';
2 +
3 +const chalk = require('chalk');
4 +
5 +const colors = {
6 + blue: '#0091ea',
7 + gray: '#78909c',
8 + green: '#00c853',
9 + red: '#d50000',
10 + yellow: '#ffd600',
11 +};
12 +
13 +const theme = chalk.constructor();
14 +theme.package = theme.hex(colors.green);
15 +theme.version = theme.hex(colors.yellow);
16 +theme.tag = theme.hex(colors.yellow);
17 +theme.build = theme.hex(colors.yellow);
18 +theme.error = theme.hex(colors.red).bold;
19 +theme.dimmed = theme.hex(colors.gray);
20 +theme.caution = theme.hex(colors.red).bold;
21 +theme.link = theme.hex(colors.blue).underline.italic;
22 +theme.header = theme.hex(colors.green).bold;
23 +theme.path = theme.hex(colors.gray).italic;
24 +theme.command = theme.hex(colors.gray);
25 +theme.quote = theme.italic;
26 +
27 +theme.diffHeader = theme.hex(colors.gray);
28 +theme.diffAdded = theme.hex(colors.green);
29 +theme.diffRemoved = theme.hex(colors.red);
30 +
31 +theme.spinnerInProgress = theme.hex(colors.yellow);
32 +theme.spinnerError = theme.hex(colors.red);
33 +theme.spinnerSuccess = theme.hex(colors.green);
34 +
35 +module.exports = theme;
scripts/release/utils.js
+146 -48
@@ -1,11 +1,24 @@
1 'use strict';
2
3 -const chalk = require('chalk');
3 const {dots} = require('cli-spinners');
4 const {exec} = require('child-process-promise');
6 -const {readdirSync, readFileSync, statSync} = require('fs');
5 +const {createPatch} = require('diff');
6 +const {hashElement} = require('folder-hash');
7 +const {readdirSync, readFileSync, statSync, writeFileSync} = require('fs');
8 +const {readJson, writeJson} = require('fs-extra');
9 const logUpdate = require('log-update');
10 const {join} = require('path');
11 +const prompt = require('prompt-promise');
12 +const theme = require('./theme');
13 +
14 +const confirm = async message => {
15 + const confirmation = await prompt(theme`\n{caution ${message}} (y/N) `);
16 + prompt.done();
17 + if (confirmation !== 'y' && confirmation !== 'Y') {
18 + console.log(theme`\n{caution Release cancelled.}`);
19 + process.exit(0);
20 + }
21 +};
22
23 const execRead = async (command, options) => {
24 const {stdout} = await exec(command, options);
@@ -13,31 +26,37 @@ const execRead = async (command, options) => {
26 return stdout.trim();
27 };
28
16 -const unexecutedCommands = [];
29 +const getBuildInfo = async () => {
30 + const cwd = join(__dirname, '..', '..');
31
18 -const execUnlessDry = async (command, {cwd, dry}) => {
19 - if (dry) {
20 - unexecutedCommands.push(`${command} # {cwd: ${cwd}}`);
21 - } else {
22 - await exec(command, {cwd});
23 - }
32 + const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
33 + cwd,
34 + });
35 + const commit = await execRead('git show -s --format=%h', {cwd});
36 + const checksum = await getChecksumForCurrentRevision(cwd);
37 + const version = `0.0.0-${commit}`;
38 +
39 + // Only available for Circle CI builds.
40 + // https://circleci.com/docs/2.0/env-vars/
41 + const buildNumber = process.env.CIRCLE_BUILD_NUM;
42 +
43 + // React version is stored explicitly, separately for DevTools support.
44 + // See updateVersionsForCanary() below for more info.
45 + const packageJSON = await readJson(
46 + join(cwd, 'packages', 'react', 'package.json')
47 + );
48 + const reactVersion = `${packageJSON.version}-canary-${commit}`;
49 +
50 + return {branch, buildNumber, checksum, commit, reactVersion, version};
51 };
52
26 -const getPackages = () => {
27 - const packagesRoot = join(__dirname, '..', '..', 'packages');
28 -
29 - return readdirSync(packagesRoot).filter(dir => {
30 - const packagePath = join(packagesRoot, dir, 'package.json');
31 -
32 - if (dir.charAt(0) !== '.' && statSync(packagePath).isFile()) {
33 - const packageJSON = JSON.parse(readFileSync(packagePath));
34 -
35 - // Skip packages like "shared" and "events" that shouldn't be updated.
36 - return packageJSON.version !== '0.0.0';
37 - }
38 -
39 - return false;
53 +const getChecksumForCurrentRevision = async cwd => {
54 + const packagesDir = join(cwd, 'packages');
55 + const hashedPackages = await hashElement(packagesDir, {
56 + encoding: 'hex',
57 + files: {exclude: ['.DS_Store']},
58 });
59 + return hashedPackages.hash.slice(0, 7);
60 };
61
62 const getPublicPackages = () => {
@@ -56,15 +75,15 @@ const getPublicPackages = () => {
75 });
76 };
77
59 -const getUnexecutedCommands = () => {
60 - if (unexecutedCommands.length > 0) {
61 - return chalk`
62 - The following commands were not executed because of the {bold --dry} flag:
63 - {gray ${unexecutedCommands.join('\n')}}
64 - `;
65 - } else {
66 - return '';
67 - }
78 +const handleError = error => {
79 + logUpdate.clear();
80 +
81 + const message = error.message.trim().replace(/\n +/g, '\n');
82 + const stack = error.stack.replace(error.message, '');
83 +
84 + console.log(theme`{error ${message}}\n\n{path ${stack}}`);
85 +
86 + process.exit(1);
87 };
88
89 const logPromise = async (promise, text, isLongRunningTask = false) => {
@@ -79,7 +98,9 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
98 const id = setInterval(() => {
99 index = ++index % frames.length;
100 logUpdate(
82 - `${chalk.yellow(frames[index])} ${text} ${chalk.gray(inProgressMessage)}`
101 + theme`{spinnerInProgress ${
102 + frames[index]
103 + }} ${text} {dimmed ${inProgressMessage}}`
104 );
105 }, interval);
106
@@ -88,7 +109,7 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
109
110 clearInterval(id);
111
91 - logUpdate(`${chalk.green('✓')} ${text}`);
112 + logUpdate(theme`{spinnerSuccess ✓} ${text}`);
113 logUpdate.done();
114
115 return returnValue;
@@ -99,26 +120,103 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
120 }
121 };
122
102 -const runYarnTask = async (cwd, task, errorMessage) => {
103 - try {
104 - await exec(`yarn ${task}`, {cwd});
105 - } catch (error) {
106 - throw Error(
107 - chalk`
108 - ${errorMessage}
123 +const printDiff = (path, beforeContents, afterContents) => {
124 + const patch = createPatch(path, beforeContents, afterContents);
125 + const coloredLines = patch
126 + .split('\n')
127 + .slice(2) // Trim index file
128 + .map((line, index) => {
129 + if (index <= 1) {
130 + return theme.diffHeader(line);
131 + }
132 + switch (line[0]) {
133 + case '+':
134 + return theme.diffAdded(line);
135 + case '-':
136 + return theme.diffRemoved(line);
137 + case ' ':
138 + return line;
139 + case '@':
140 + return null;
141 + case '\\':
142 + return null;
143 + }
144 + })
145 + .filter(line => line);
146 + console.log(coloredLines.join('\n'));
147 + return patch;
148 +};
149
110 - {white ${error.stdout}}
111 - `
112 - );
150 +// This method is used by both local Node release scripts and Circle CI bash scripts.
151 +// It updates version numbers in package JSONs (both the version field and dependencies),
152 +// As well as the embedded renderer version in "packages/shared/ReactVersion".
153 +// Canaries version numbers use the format of 0.0.0-<sha> to be easily recognized (e.g. 0.0.0-57239eac8).
154 +// A separate "React version" is used for the embedded renderer version to support DevTools,
155 +// since it needs to distinguish between different version ranges of React.
156 +// It is based on the version of React in the local package.json (e.g. 16.6.1-canary-57239eac8).
157 +// Both numbers will be replaced if the canary is promoted to a stable release.
158 +const updateVersionsForCanary = async (cwd, reactVersion, version) => {
159 + const packages = getPublicPackages(join(cwd, 'packages'));
160 + const packagesDir = join(cwd, 'packages');
161 +
162 + // Update the shared React version source file.
163 + // This is bundled into built renderers.
164 + // The promote script will replace this with a final version later.
165 + const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
166 + const sourceReactVersion = readFileSync(
167 + sourceReactVersionPath,
168 + 'utf8'
169 + ).replace(
170 + /module\.exports = '[^']+';/,
171 + `module.exports = '${reactVersion}';`
172 + );
173 + writeFileSync(sourceReactVersionPath, sourceReactVersion);
174 +
175 + // Update the root package.json.
176 + // This is required to pass a later version check script.
177 + {
178 + const packageJSONPath = join(cwd, 'package.json');
179 + const packageJSON = await readJson(packageJSONPath);
180 + packageJSON.version = version;
181 + await writeJson(packageJSONPath, packageJSON, {spaces: 2});
182 + }
183 +
184 + for (let i = 0; i < packages.length; i++) {
185 + const packageName = packages[i];
186 + const packagePath = join(packagesDir, packageName);
187 +
188 + // Update version numbers in package JSONs
189 + const packageJSONPath = join(packagePath, 'package.json');
190 + const packageJSON = await readJson(packageJSONPath);
191 + packageJSON.version = version;
192 +
193 + // Also update inter-package dependencies.
194 + // Canary releases always have exact version matches.
195 + // The promote script may later relax these (e.g. "^x.x.x") based on source package JSONs.
196 + const {dependencies, peerDependencies} = packageJSON;
197 + for (let j = 0; j < packages.length; j++) {
198 + const dependencyName = packages[j];
199 + if (dependencies && dependencies[dependencyName]) {
200 + dependencies[dependencyName] = version;
201 + }
202 + if (peerDependencies && peerDependencies[dependencyName]) {
203 + peerDependencies[dependencyName] = version;
204 + }
205 + }
206 +
207 + await writeJson(packageJSONPath, packageJSON, {spaces: 2});
208 }
209 };
210
211 module.exports = {
212 + confirm,
213 execRead,
118 - execUnlessDry,
119 - getPackages,
214 + getBuildInfo,
215 + getChecksumForCurrentRevision,
216 getPublicPackages,
121 - getUnexecutedCommands,
217 + handleError,
218 logPromise,
123 - runYarnTask,
219 + printDiff,
220 + theme,
221 + updateVersionsForCanary,
222 };
scripts/release/yarn.lock
+61 -4
@@ -63,6 +63,11 @@ aws4@^1.6.0:
63 version "1.6.0"
64 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
65
66 +balanced-match@^1.0.0:
67 + version "1.0.0"
68 + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
69 + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
70 +
71 bcrypt-pbkdf@^1.0.0:
72 version "1.0.1"
73 resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
@@ -81,6 +86,14 @@ boom@5.x.x:
86 dependencies:
87 hoek "4.x.x"
88
89 +brace-expansion@^1.1.7:
90 + version "1.1.11"
91 + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
92 + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
93 + dependencies:
94 + balanced-match "^1.0.0"
95 + concat-map "0.0.1"
96 +
97 caseless@~0.12.0:
98 version "0.12.0"
99 resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -101,6 +114,11 @@ child-process-promise@^2.2.1:
114 node-version "^1.0.0"
115 promise-polyfill "^6.0.1"
116
117 +clear@^0.1.0:
118 + version "0.1.0"
119 + resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a"
120 + integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==
121 +
122 cli-cursor@^2.0.0:
123 version "2.1.0"
124 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@@ -148,6 +166,11 @@ command-line-usage@^4.0.1:
166 table-layout "^0.4.1"
167 typical "^2.6.1"
168
169 +concat-map@0.0.1:
170 + version "0.0.1"
171 + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
172 + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
173 +
174 core-util-is@1.0.2:
175 version "1.0.2"
176 resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -171,6 +194,13 @@ dashdash@^1.12.0:
194 dependencies:
195 assert-plus "^1.0.0"
196
197 +debug@^3.1.0:
198 + version "3.2.6"
199 + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
200 + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
201 + dependencies:
202 + ms "^2.1.1"
203 +
204 deep-extend@~0.5.0:
205 version "0.5.0"
206 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803"
@@ -179,6 +209,11 @@ delayed-stream@~1.0.0:
209 version "1.0.0"
210 resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
211
212 +diff@^3.5.0:
213 + version "3.5.0"
214 + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
215 + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
216 +
217 ecc-jsbn@~0.1.1:
218 version "0.1.1"
219 resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -201,10 +236,6 @@ fast-deep-equal@^1.0.0:
236 version "1.0.0"
237 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
238
204 -figlet@^1.2.0:
205 - version "1.2.0"
206 - resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410"
207 -
239 find-replace@^1.0.3:
240 version "1.0.3"
241 resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0"
@@ -212,6 +243,15 @@ find-replace@^1.0.3:
243 array-back "^1.0.4"
244 test-value "^2.1.0"
245
246 +folder-hash@^2.1.2:
247 + version "2.1.2"
248 + resolved "https://registry.yarnpkg.com/folder-hash/-/folder-hash-2.1.2.tgz#7109f9cd0cbca271936d1b5544b156d6571e6cfd"
249 + integrity sha512-PmMwEZyNN96EMshf7sek4OIB7ADNsHOJ7VIw7pO0PBI0BNfEsi7U8U56TBjjqqwQ0WuBv8se0HEfmbw5b/Rk+w==
250 + dependencies:
251 + debug "^3.1.0"
252 + graceful-fs "~4.1.11"
253 + minimatch "~3.0.4"
254 +
255 forever-agent@~0.6.1:
256 version "0.6.1"
257 resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -242,6 +282,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6:
282 version "4.1.11"
283 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
284
285 +graceful-fs@~4.1.11:
286 + version "4.1.15"
287 + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
288 + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
289 +
290 har-schema@^2.0.0:
291 version "2.0.0"
292 resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -372,6 +417,18 @@ mimic-fn@^1.0.0:
417 version "1.1.0"
418 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
419
420 +minimatch@~3.0.4:
421 + version "3.0.4"
422 + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
423 + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
424 + dependencies:
425 + brace-expansion "^1.1.7"
426 +
427 +ms@^2.1.1:
428 + version "2.1.1"
429 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
430 + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
431 +
432 native-or-another@~2.0.0:
433 version "2.0.0"
434 resolved "https://registry.yarnpkg.com/native-or-another/-/native-or-another-2.0.0.tgz#17a567f92beea9cd71acff96a7681a735eca3bff"