mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 12:20:38 +01:00
Change format of @next and @experimental release versions from <number>-<sha> to <number>-<sha>-<date> to make them more human readable. This format still preserves the ability for us to easily map a version number to the changes it contains, while also being able to more easily know at a glance how recent a release is.
297 lines
9.7 KiB
JavaScript
297 lines
9.7 KiB
JavaScript
'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');
|
|
|
|
const {
|
|
ReactVersion,
|
|
stablePackages,
|
|
experimentalPackages,
|
|
nextChannelLabel,
|
|
} = 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();
|
|
|
|
let dateString = (
|
|
spawnSync('git', ['show', '-s', '--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);
|
|
}
|
|
|
|
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;
|
|
updateTheReactVersionThatDevToolsReads(
|
|
ReactVersion + '-' + sha + '-' + dateString
|
|
);
|
|
buildForChannel('stable', nodeTotal, nodeIndex);
|
|
processStable('./build');
|
|
} else {
|
|
const nodeTotal = total - halfTotal;
|
|
const nodeIndex = index - halfTotal;
|
|
updateTheReactVersionThatDevToolsReads(
|
|
ReactVersion + '-experimental-' + sha + '-' + dateString
|
|
);
|
|
buildForChannel('experimental', nodeTotal, nodeIndex);
|
|
processExperimental('./build');
|
|
}
|
|
|
|
// TODO: Currently storing artifacts as `./build2` so that it doesn't conflict
|
|
// with old build job. Remove once we migrate rest of build/test pipeline.
|
|
fs.renameSync('./build', './build2');
|
|
} else {
|
|
// Running locally, no concurrency. Move each channel's build artifacts into
|
|
// a temporary directory so that they don't conflict.
|
|
updateTheReactVersionThatDevToolsReads(
|
|
ReactVersion + '-' + sha + '-' + dateString
|
|
);
|
|
buildForChannel('stable', '', '');
|
|
const stableDir = tmp.dirSync().name;
|
|
crossDeviceRenameSync('./build', stableDir);
|
|
processStable(stableDir);
|
|
updateTheReactVersionThatDevToolsReads(
|
|
ReactVersion + '-experimental-' + sha + '-' + dateString
|
|
);
|
|
buildForChannel('experimental', '', '');
|
|
const experimentalDir = tmp.dirSync().name;
|
|
crossDeviceRenameSync('./build', experimentalDir);
|
|
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
|
|
// TODO: Currently storing artifacts as `./build2` so that it doesn't conflict
|
|
// with old build job. Remove once we migrate rest of build/test pipeline.
|
|
crossDeviceRenameSync(stableDir, './build2');
|
|
}
|
|
|
|
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,
|
|
},
|
|
});
|
|
}
|
|
|
|
function processStable(buildDir) {
|
|
if (fs.existsSync(buildDir + '/node_modules')) {
|
|
const defaultVersionIfNotFound = '0.0.0' + '-' + sha + '-' + dateString;
|
|
const versionsMap = new Map();
|
|
for (const moduleName in stablePackages) {
|
|
const version = stablePackages[moduleName];
|
|
versionsMap.set(
|
|
moduleName,
|
|
version + '-' + nextChannelLabel + '-' + sha + '-' + dateString,
|
|
defaultVersionIfNotFound
|
|
);
|
|
}
|
|
updatePackageVersions(
|
|
buildDir + '/node_modules',
|
|
versionsMap,
|
|
defaultVersionIfNotFound,
|
|
true
|
|
);
|
|
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
|
|
|
|
// Identical to `oss-stable` but with real, semver versions. This is what
|
|
// will get published to @latest.
|
|
spawnSync('cp', [
|
|
'-r',
|
|
buildDir + '/oss-stable',
|
|
buildDir + '/oss-stable-semver',
|
|
]);
|
|
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
|
|
);
|
|
}
|
|
|
|
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'));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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
|
|
);
|
|
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-experimental');
|
|
}
|
|
|
|
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'));
|
|
}
|
|
}
|
|
}
|
|
|
|
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).
|
|
*/
|
|
function updatePackageVersions(
|
|
modulesDir,
|
|
versionsMap,
|
|
defaultVersionIfNotFound,
|
|
pinToExactVersion
|
|
) {
|
|
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()) {
|
|
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) {
|
|
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 updateTheReactVersionThatDevToolsReads(version) {
|
|
// Overwrite the ReactVersion module before the build script runs so that it
|
|
// is included in the final bundles. This only runs in CI, so it's fine to
|
|
// edit the source file.
|
|
fs.writeFileSync(
|
|
'./packages/shared/ReactVersion.js',
|
|
`export default '${version}';\n`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
}
|