Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add single source of truth for package versions #21608

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions ReactVersions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

// This module is the single source of truth for versioning packages that we
// publish to npm.
//
// Packages will not be published unless they are added here.
//
// The @latest channel uses the version as-is, e.g.:
//
// 18.0.0
//
// The @next channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 18.0.0-next-a1c2d3e4
//
// (TODO: ^ this isn't enabled quite yet. We still use <version>-<commit_sha>.)
//
// The @experimental channel doesn't include a version, only a sha, e.g.:
//
// 0.0.0-experimental-a1c2d3e4

// TODO: Main includes breaking changes. Bump this to 18.0.0.
const ReactVersion = '17.0.3';

// The label used by the @next channel. Represents the upcoming release's
// stability. Could be "alpha", "beta", "rc", etc.
const nextChannelLabel = 'next';

const stablePackages = {
'create-subscription': ReactVersion,
'eslint-plugin-react-hooks': '4.2.1',
'jest-react': '0.12.1',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.27.0',
'react-refresh': '0.11.0',
'react-test-renderer': ReactVersion,
'use-subscription': '1.6.0',
scheduler: '0.21.0',
};

// These packages do not exist in the @next or @latest channel, only
// @experimental. We don't use semver, just the commit sha, so this is just a
// list of package names instead of a map.
const experimentalPackages = [
'react-fetch',
'react-fs',
'react-pg',
'react-server-dom-webpack',
'react-server',
];

// TODO: Export a map of every package and its version.
module.exports = {
ReactVersion,
nextChannelLabel,
stablePackages,
experimentalPackages,
};
3 changes: 3 additions & 0 deletions packages/shared/ReactVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@
// TODO: 17.0.3 has not been released to NPM;
// It exists as a placeholder so that DevTools can support work tag changes between releases.
// When we next publish a release (either 17.0.3 or 17.1.0), update the matching TODO in backend/renderer.js
// TODO: This module is used both by the release scripts and to expose a version
// at runtime. We should instead inject the version number as part of the build
// process, and use the ReactVersions.js module as the single source of truth.
export default '17.0.3';
1 change: 1 addition & 0 deletions scripts/release/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const getCommitFromCurrentBuild = async () => {
};

const getPublicPackages = isExperimental => {
// TODO: Use ReactVersions.js as source of truth.
if (isExperimental) {
return [
'create-subscription',
Expand Down
72 changes: 52 additions & 20 deletions scripts/rollup/build-all-release-channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ const {spawnSync} = require('child_process');
const path = require('path');
const tmp = require('tmp');

const {
ReactVersion,
stablePackages,
experimentalPackages,
} = require('../../ReactVersions');

// Runs the build script for both stable and experimental release channels,
// by configuring an environment variable.

const sha = (
spawnSync('git', ['show', '-s', '--format=%h']).stdout + ''
).trim();
const ReactVersion = JSON.parse(fs.readFileSync('packages/react/package.json'))
.version;

if (process.env.CIRCLE_NODE_TOTAL) {
// In CI, we use multiple concurrent processes. Allocate half the processes to
Expand All @@ -27,19 +31,17 @@ if (process.env.CIRCLE_NODE_TOTAL) {
if (index < halfTotal) {
const nodeTotal = halfTotal;
const nodeIndex = index;
const version = '0.0.0-' + sha;
updateTheReactVersionThatDevToolsReads(ReactVersion + '-' + sha);
buildForChannel('stable', nodeTotal, nodeIndex);
processStable('./build', version);
processStable('./build');
} else {
const nodeTotal = total - halfTotal;
const nodeIndex = index - halfTotal;
const version = '0.0.0-experimental-' + sha;
updateTheReactVersionThatDevToolsReads(
ReactVersion + '-experimental-' + sha
);
buildForChannel('experimental', nodeTotal, nodeIndex);
processExperimental('./build', version);
processExperimental('./build');
}

// TODO: Currently storing artifacts as `./build2` so that it doesn't conflict
Expand All @@ -48,17 +50,16 @@ if (process.env.CIRCLE_NODE_TOTAL) {
} else {
// Running locally, no concurrency. Move each channel's build artifacts into
// a temporary directory so that they don't conflict.
const stableVersion = '0.0.0-' + sha;
updateTheReactVersionThatDevToolsReads(ReactVersion + '-' + sha);
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
processStable(stableDir, stableVersion);

const experimentalVersion = '0.0.0-experimental-' + sha;
processStable(stableDir);
updateTheReactVersionThatDevToolsReads(ReactVersion + '-experimental-' + sha);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
processExperimental(experimentalDir, experimentalVersion);
processExperimental(experimentalDir);

// Then merge the experimental folder into the stable one. processExperimental
// will have already removed conflicting files.
Expand All @@ -84,9 +85,21 @@ function buildForChannel(channel, nodeTotal, nodeIndex) {
});
}

function processStable(buildDir, version) {
function processStable(buildDir) {
if (fs.existsSync(buildDir + '/node_modules')) {
updatePackageVersions(buildDir + '/node_modules', version);
const defaultVersionIfNotFound = '0.0.0' + '-' + sha;
const versionsMap = new Map();
for (const moduleName in stablePackages) {
// TODO: Use version declared in ReactVersions module instead of 0.0.0.
// const version = stablePackages[moduleName];
// versionsMap.set(moduleName, version + '-' + nextChannelLabel + '-' + sha);
versionsMap.set(moduleName, defaultVersionIfNotFound);
}
updatePackageVersions(
buildDir + '/node_modules',
versionsMap,
defaultVersionIfNotFound
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
}

Expand All @@ -107,7 +120,19 @@ function processStable(buildDir, version) {

function processExperimental(buildDir, version) {
if (fs.existsSync(buildDir + '/node_modules')) {
updatePackageVersions(buildDir + '/node_modules', version);
const defaultVersionIfNotFound = '0.0.0' + '-' + 'experimental' + '-' + sha;
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
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-experimental');
}

Expand Down Expand Up @@ -151,9 +176,18 @@ function crossDeviceRenameSync(source, destination) {
* to match this version for all of the 'React' packages
* (packages available in this repo).
*/
function updatePackageVersions(modulesDir, version) {
const allReactModuleNames = fs.readdirSync('packages');
function updatePackageVersions(
modulesDir,
versionsMap,
defaultVersionIfNotFound
) {
for (const moduleName of fs.readdirSync(modulesDir)) {
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()) {
Expand All @@ -164,16 +198,14 @@ function updatePackageVersions(modulesDir, version) {

if (packageInfo.dependencies) {
for (const dep of Object.keys(packageInfo.dependencies)) {
// if it's a react package (available in the current repo), update the version
// TODO: is this too broad? Assumes all of the packages were built.
if (allReactModuleNames.includes(dep)) {
if (modulesDir.includes(dep)) {
packageInfo.dependencies[dep] = version;
}
}
}
if (packageInfo.peerDependencies) {
for (const dep of Object.keys(packageInfo.peerDependencies)) {
if (allReactModuleNames.includes(dep)) {
if (modulesDir.includes(dep)) {
packageInfo.peerDependencies[dep] = version;
}
}
Expand Down