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"