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