react/scripts/rollup/build-all-release-channels.js

353 lines
11 KiB
JavaScript
Raw Normal View History

'use strict';
/* eslint-disable no-for-of-loops/no-for-of-loops */
const fs = require('fs');
const fse = require('fs-extra');
const {spawnSync} = require('child_process');
const path = require('path');
const tmp = require('tmp');
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
const {
ReactVersion,
stablePackages,
experimentalPackages,
nextChannelLabel,
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
} = require('../../ReactVersions');
// Runs the build script for both stable and experimental release channels,
// by configuring an environment variable.
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
const sha = String(
spawnSync('git', ['show', '-s', '--no-show-signature', '--format=%h']).stdout
).trim();
Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064) * Revise ESLint rules for string coercion Currently, react uses `'' + value` to coerce mixed values to strings. This code will throw for Temporal objects or symbols. To make string-coercion safer and to improve user-facing error messages, This commit adds a new ESLint rule called `safe-string-coercion`. This rule has two modes: a production mode and a non-production mode. * If the `isProductionUserAppCode` option is true, then `'' + value` coercions are allowed (because they're faster, although they may throw) and `String(value)` coercions are disallowed. Exception: when building error messages or running DEV-only code in prod files, `String()` should be used because it won't throw. * If the `isProductionUserAppCode` option is false, then `'' + value` coercions are disallowed (because they may throw, and in non-prod code it's not worth the risk) and `String(value)` are allowed. Production mode is used for all files which will be bundled with developers' userland apps. Non-prod mode is used for all other React code: tests, DEV blocks, devtools extension, etc. In production mode, in addiiton to flagging `String(value)` calls, the rule will also flag `'' + value` or `value + ''` coercions that may throw. The rule is smart enough to silence itself in the following "will never throw" cases: * When the coercion is wrapped in a `typeof` test that restricts to safe (non-symbol, non-object) types. Example: if (typeof value === 'string' || typeof value === 'number') { thisWontReport('' + value); } * When what's being coerced is a unary function result, because unary functions never return an object or a symbol. * When the coerced value is a commonly-used numeric identifier: `i`, `idx`, or `lineNumber`. * When the statement immeidately before the coercion is a DEV-only call to a function from shared/CheckStringCoercion.js. This call is a no-op in production, but in DEV it will show a console error explaining the problem, then will throw right after a long explanatory code comment so that debugger users will have an idea what's going on. The check function call must be in the following format: if (__DEV__) { checkXxxxxStringCoercion(value); }; Manually disabling the rule is usually not necessary because almost all prod use of the `'' + value` pattern falls into one of the categories above. But in the rare cases where the rule isn't smart enough to detect safe usage (e.g. when a coercion is inside a nested ternary operator), manually disabling the rule will be needed. The rule should also be manually disabled in prod error handling code where `String(value)` should be used for coercions, because it'd be bad to throw while building an error message or stack trace! The prod and non-prod modes have differentiated error messages to explain how to do a proper coercion in that mode. If a production check call is needed but is missing or incorrect (e.g. not in a DEV block or not immediately before the coercion), then a context-sensitive error message will be reported so that developers can figure out what's wrong and how to fix the problem. Because string coercions are now handled by the `safe-string-coercion` rule, the `no-primitive-constructor` rule no longer flags `String()` usage. It still flags `new String(value)` because that usage is almost always a bug. * Add DEV-only string coercion check functions This commit adds DEV-only functions to check whether coercing values to strings using the `'' + value` pattern will throw. If it will throw, these functions will: 1. Display a console error with a friendly error message describing the problem and the developer can fix it. 2. Perform the coercion, which will throw. Right before the line where the throwing happens, there's a long code comment that will help debugger users (or others looking at the exception call stack) figure out what happened and how to fix the problem. One of these check functions should be called before all string coercion of user-provided values, except when the the coercion is guaranteed not to throw, e.g. * if inside a typeof check like `if (typeof value === 'string')` * if coercing the result of a unary function like `+value` or `value++` * if coercing a variable named in a whitelist of numeric identifiers: `i`, `idx`, or `lineNumber`. The new `safe-string-coercion` internal ESLint rule enforces that these check functions are called when they are required. Only use these check functions in production code that will be bundled with user apps. For non-prod code (and for production error-handling code), use `String(value)` instead which may be a little slower but will never throw. * Add failing tests for string coercion Added failing tests to verify: * That input, select, and textarea elements with value and defaultValue set to Temporal-like objects which will throw when coerced to string using the `'' + value` pattern. * That text elements will throw for Temporal-like objects * That dangerouslySetInnerHTML will *not* throw for Temporal-like objects because this value is not cast to a string before passing to the DOM. * That keys that are Temporal-like objects will throw All tests above validate the friendly error messages thrown. * Use `String(value)` for coercion in non-prod files This commit switches non-production code from `'' + value` (which throws for Temporal objects and symbols) to instead use `String(value)` which won't throw for these or other future plus-phobic types. "Non-produciton code" includes anything not bundled into user apps: * Tests and test utilities. Note that I didn't change legacy React test fixtures because I assumed it was good for those files to act just like old React, including coercion behavior. * Build scripts * Dev tools package - In addition to switching to `String`, I also removed special-case code for coercing symbols which is now unnecessary. * Add DEV-only string coercion checks to prod files This commit adds DEV-only function calls to to check if string coercion using `'' + value` will throw, which it will if the value is a Temporal object or a symbol because those types can't be added with `+`. If it will throw, then in DEV these checks will show a console error to help the user undertsand what went wrong and how to fix the problem. After emitting the console error, the check functions will retry the coercion which will throw with a call stack that's easy (or at least easier!) to troubleshoot because the exception happens right after a long comment explaining the issue. So whether the user is in a debugger, looking at the browser console, or viewing the in-browser DEV call stack, it should be easy to understand and fix the problem. In most cases, the safe-string-coercion ESLint rule is smart enough to detect when a coercion is safe. But in rare cases (e.g. when a coercion is inside a ternary) this rule will have to be manually disabled. This commit also switches error-handling code to use `String(value)` for coercion, because it's bad to crash when you're trying to build an error message or a call stack! Because `String()` is usually disallowed by the `safe-string-coercion` ESLint rule in production code, the rule must be disabled when `String()` is used.
2021-09-28 01:05:07 +08:00
let dateString = String(
spawnSync('git', [
'show',
'-s',
'--no-show-signature',
'--format=%cd',
'--date=format:%Y%m%d',
sha,
]).stdout
).trim();
// On CI environment, this string is wrapped with quotes '...'s
if (dateString.startsWith("'")) {
dateString = dateString.substr(1, 8);
}
// Build the artifacts using a placeholder React version. We'll then do a string
// replace to swap it with the correct version per release channel.
//
// The placeholder version is the same format that the "next" channel uses
const PLACEHOLDER_REACT_VERSION =
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString;
// TODO: We should inject the React version using a build-time parameter
// instead of overwriting the source files.
fs.writeFileSync(
'./packages/shared/ReactVersion.js',
`export default '${PLACEHOLDER_REACT_VERSION}';\n`
);
if (process.env.CIRCLE_NODE_TOTAL) {
// In CI, we use multiple concurrent processes. Allocate half the processes to
// build the stable channel, and the other half for experimental. Override
// the environment variables to "trick" the underlying build script.
const total = parseInt(process.env.CIRCLE_NODE_TOTAL, 10);
const halfTotal = Math.floor(total / 2);
const index = parseInt(process.env.CIRCLE_NODE_INDEX, 10);
if (index < halfTotal) {
const nodeTotal = halfTotal;
const nodeIndex = index;
buildForChannel('stable', nodeTotal, nodeIndex);
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
processStable('./build');
} else {
const nodeTotal = total - halfTotal;
const nodeIndex = index - halfTotal;
buildForChannel('experimental', nodeTotal, nodeIndex);
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
processExperimental('./build');
}
} else {
// Running locally, no concurrency. Move each channel's build artifacts into
// a temporary directory so that they don't conflict.
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
processStable(stableDir);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
processExperimental(experimentalDir);
// Then merge the experimental folder into the stable one. processExperimental
// will have already removed conflicting files.
//
// In CI, merging is handled automatically by CircleCI's workspace feature.
mergeDirsSync(experimentalDir + '/', stableDir + '/');
// Now restore the combined directory back to its original name
crossDeviceRenameSync(stableDir, './build');
}
function buildForChannel(channel, nodeTotal, nodeIndex) {
spawnSync('node', ['./scripts/rollup/build.js', ...process.argv.slice(2)], {
stdio: ['pipe', process.stdout, process.stderr],
env: {
...process.env,
RELEASE_CHANNEL: channel,
CIRCLE_NODE_TOTAL: nodeTotal,
CIRCLE_NODE_INDEX: nodeIndex,
},
});
}
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
function processStable(buildDir) {
if (fs.existsSync(buildDir + '/node_modules')) {
// Identical to `oss-stable` but with real, semver versions. This is what
// will get published to @latest.
spawnSync('cp', [
'-r',
buildDir + '/node_modules',
buildDir + '/oss-stable-semver',
]);
const defaultVersionIfNotFound = '0.0.0' + '-' + sha + '-' + dateString;
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
const versionsMap = new Map();
for (const moduleName in stablePackages) {
const version = stablePackages[moduleName];
versionsMap.set(
moduleName,
version + '-' + nextChannelLabel + '-' + sha + '-' + dateString,
defaultVersionIfNotFound
);
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
}
updatePackageVersions(
buildDir + '/node_modules',
versionsMap,
defaultVersionIfNotFound,
true
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable',
ReactVersion + '-' + nextChannelLabel + '-' + sha + '-' + dateString
);
// Now do the semver ones
const semverVersionsMap = new Map();
for (const moduleName in stablePackages) {
const version = stablePackages[moduleName];
semverVersionsMap.set(moduleName, version);
}
updatePackageVersions(
buildDir + '/oss-stable-semver',
semverVersionsMap,
defaultVersionIfNotFound,
false
);
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable-semver',
ReactVersion
);
}
if (fs.existsSync(buildDir + '/facebook-www')) {
for (const fileName of fs.readdirSync(buildDir + '/facebook-www')) {
const filePath = buildDir + '/facebook-www/' + fileName;
const stats = fs.statSync(filePath);
if (!stats.isDirectory()) {
fs.renameSync(filePath, filePath.replace('.js', '.classic.js'));
}
}
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
ReactVersion + '-www-classic-' + sha + '-' + dateString
);
}
if (fs.existsSync(buildDir + '/sizes')) {
fs.renameSync(buildDir + '/sizes', buildDir + '/sizes-stable');
}
}
function processExperimental(buildDir, version) {
if (fs.existsSync(buildDir + '/node_modules')) {
const defaultVersionIfNotFound =
'0.0.0' + '-experimental-' + sha + '-' + dateString;
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
const versionsMap = new Map();
for (const moduleName in stablePackages) {
versionsMap.set(moduleName, defaultVersionIfNotFound);
}
for (const moduleName of experimentalPackages) {
versionsMap.set(moduleName, defaultVersionIfNotFound);
}
updatePackageVersions(
buildDir + '/node_modules',
versionsMap,
defaultVersionIfNotFound,
true
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-experimental');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-experimental',
// TODO: The npm version for experimental releases does not include the
// React version, but the runtime version does so that DevTools can do
// feature detection. Decide what to do about this later.
ReactVersion + '-experimental-' + sha + '-' + dateString
);
}
if (fs.existsSync(buildDir + '/facebook-www')) {
for (const fileName of fs.readdirSync(buildDir + '/facebook-www')) {
const filePath = buildDir + '/facebook-www/' + fileName;
const stats = fs.statSync(filePath);
if (!stats.isDirectory()) {
fs.renameSync(filePath, filePath.replace('.js', '.modern.js'));
}
}
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
ReactVersion + '-www-modern-' + sha + '-' + dateString
);
}
if (fs.existsSync(buildDir + '/sizes')) {
fs.renameSync(buildDir + '/sizes', buildDir + '/sizes-experimental');
}
// Delete all other artifacts that weren't handled above. We assume they are
// duplicates of the corresponding artifacts in the stable channel. Ideally,
// the underlying build script should not have produced these files in the
// first place.
for (const pathName of fs.readdirSync(buildDir)) {
if (
pathName !== 'oss-experimental' &&
pathName !== 'facebook-www' &&
pathName !== 'sizes-experimental'
) {
spawnSync('rm', ['-rm', buildDir + '/' + pathName]);
}
}
}
function crossDeviceRenameSync(source, destination) {
return fse.moveSync(source, destination, {overwrite: true});
}
/*
* Grabs the built packages in ${tmp_build_dir}/node_modules and updates the
* `version` key in their package.json to 0.0.0-${date}-${commitHash} for the commit
* you're building. Also updates the dependencies and peerDependencies
* to match this version for all of the 'React' packages
* (packages available in this repo).
*/
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
function updatePackageVersions(
modulesDir,
versionsMap,
defaultVersionIfNotFound,
pinToExactVersion
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
) {
for (const moduleName of fs.readdirSync(modulesDir)) {
Add single source of truth for package versions (#21608) The versioning scheme for `@next` releases does not include semver information. Like `@experimental`, the versions are based only on the hash, i.e. `0.0.0-<commit_sha>`. The reason we do this is to prevent the use of a tilde (~) or caret (^) to match a range of prerelease versions. For `@experimental`, I think this rationale still makes sense — those releases are very unstable, with frequent breaking changes. But `@next` is not as volatile. It represents the next stable release. So, I think we can afford to include an actual verison number at the beginning of the string instead of `0.0.0`. We can also add a label that indicates readiness of the upcoming release, like "alpha", "beta", "rc", etc. To prepare for this the new versioning scheme, I updated the build script. However, **this PR does not enable the new versioning scheme yet**. I left a TODO above the line that we'll change once we're ready. We need to specify the expected next version numbers for each package, somewhere. These aren't encoded anywhere today — we don't specify version numbers until right before publishing to `@latest`, using an interactive script: `prepare-release-from-npm`. Instead, what we can do is track these version numbers in a module. I added `ReactVersions.js` that acts as the single source of truth for every package's version. The build script uses this module to build the `@next` packages. In the future, I want to start building the `@latest` packages the same way we do `@next` and `@experimental`. (What we do now is download a `@next` release from npm and swap out its version numbers.) Then we could run automated tests in CI to confirm the packages are releasable, instead of waiting to verify that right before publish.
2021-06-03 11:54:26 +08:00
let version = versionsMap.get(moduleName);
if (version === undefined) {
// TODO: If the module is not in the version map, we should exclude it
// from the build artifacts.
version = defaultVersionIfNotFound;
}
const packageJSONPath = path.join(modulesDir, moduleName, 'package.json');
const stats = fs.statSync(packageJSONPath);
if (stats.isFile()) {
const packageInfo = JSON.parse(fs.readFileSync(packageJSONPath));
// Update version
packageInfo.version = version;
if (packageInfo.dependencies) {
for (const dep of Object.keys(packageInfo.dependencies)) {
const depVersion = versionsMap.get(dep);
if (depVersion !== undefined) {
packageInfo.dependencies[dep] = pinToExactVersion
? depVersion
: '^' + depVersion;
}
}
}
if (packageInfo.peerDependencies) {
if (
!pinToExactVersion &&
(moduleName === 'use-sync-external-store' ||
moduleName === 'use-subscription')
) {
// use-sync-external-store supports older versions of React, too, so
// we don't override to the latest version. We should figure out some
// better way to handle this.
// TODO: Remove this special case.
} else {
for (const dep of Object.keys(packageInfo.peerDependencies)) {
const depVersion = versionsMap.get(dep);
if (depVersion !== undefined) {
packageInfo.peerDependencies[dep] = pinToExactVersion
? depVersion
: '^' + depVersion;
}
}
}
}
// Write out updated package.json
fs.writeFileSync(packageJSONPath, JSON.stringify(packageInfo, null, 2));
}
}
}
function updatePlaceholderReactVersionInCompiledArtifacts(
artifactsDirectory,
newVersion
) {
// Update the version of React in the compiled artifacts by searching for
// the placeholder string and replacing it with a new one.
const artifactFilenames = String(
spawnSync('grep', [
'-lr',
PLACEHOLDER_REACT_VERSION,
'--',
artifactsDirectory,
]).stdout
)
.trim()
.split('\n')
.filter(filename => filename.endsWith('.js'));
for (const artifactFilename of artifactFilenames) {
const originalText = fs.readFileSync(artifactFilename, 'utf8');
const replacedText = originalText.replace(
PLACEHOLDER_REACT_VERSION,
newVersion
);
fs.writeFileSync(artifactFilename, replacedText);
}
}
/**
* cross-platform alternative to `rsync -ar`
* @param {string} source
* @param {string} destination
*/
function mergeDirsSync(source, destination) {
for (const sourceFileBaseName of fs.readdirSync(source)) {
const sourceFileName = path.join(source, sourceFileBaseName);
const targetFileName = path.join(destination, sourceFileBaseName);
const sourceFile = fs.statSync(sourceFileName);
if (sourceFile.isDirectory()) {
fse.ensureDirSync(targetFileName);
mergeDirsSync(sourceFileName, targetFileName);
} else {
fs.copyFileSync(sourceFileName, targetFileName);
}
}
}