'use strict';
const fs = require('fs');
const fse = require('fs-extra');
const {spawnSync} = require('child_process');
const path = require('path');
const tmp = require('tmp');
const shell = require('shelljs');
const {
ReactVersion,
stablePackages,
experimentalPackages,
canaryChannelLabel,
rcNumber,
} = require('../../ReactVersions');
const yargs = require('yargs');
const Bundles = require('./bundles');
const sha = String(spawnSync('git', ['rev-parse', 'HEAD']).stdout).slice(0, 8);
let dateString = String(
spawnSync('git', [
'show',
'-s',
'--no-show-signature',
'--format=%cd',
'--date=format:%Y%m%d',
sha,
]).stdout
).trim();
if (dateString.startsWith("'")) {
dateString = dateString.slice(1, 9);
}
const PLACEHOLDER_REACT_VERSION =
ReactVersion + '-' + canaryChannelLabel + '-' + sha + '-' + dateString;
fs.writeFileSync(
'./packages/shared/ReactVersion.js',
`export default '${PLACEHOLDER_REACT_VERSION}';\n`
);
const argv = yargs.wrap(yargs.terminalWidth()).options({
releaseChannel: {
alias: 'r',
describe: 'Build the given release channel.',
requiresArg: true,
type: 'string',
choices: ['experimental', 'stable'],
},
index: {
alias: 'i',
describe: 'Worker id.',
requiresArg: true,
type: 'number',
},
total: {
alias: 't',
describe: 'Total number of workers.',
requiresArg: true,
type: 'number',
},
ci: {
describe: 'Run tests in CI',
requiresArg: false,
type: 'boolean',
default: false,
},
type: {
describe: `Build the given bundle type. (${Object.values(
Bundles.bundleTypes
)})`,
requiresArg: false,
type: 'string',
},
pretty: {
describe: 'Force pretty output.',
requiresArg: false,
type: 'boolean',
},
'sync-fbsource': {
describe: 'Include to sync build to fbsource.',
requiresArg: false,
type: 'string',
},
'sync-www': {
describe: 'Include to sync build to www.',
requiresArg: false,
type: 'string',
},
'unsafe-partial': {
describe: 'Do not clean ./build first.',
requiresArg: false,
type: 'boolean',
},
}).argv;
async function main() {
if (argv.ci === true) {
buildForChannel(argv.releaseChannel, argv.total, argv.index);
switch (argv.releaseChannel) {
case 'stable': {
processStable('./build');
break;
}
case 'experimental': {
processExperimental('./build');
break;
}
default:
throw new Error(`Unknown release channel ${argv.releaseChannel}`);
}
} else {
buildForChannel('stable', '', '');
const stableDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', stableDir);
processStable(stableDir);
buildForChannel('experimental', '', '');
const experimentalDir = tmp.dirSync().name;
crossDeviceRenameSync('./build', experimentalDir);
processExperimental(experimentalDir);
mergeDirsSync(experimentalDir + '/', stableDir + '/');
crossDeviceRenameSync(stableDir, './build');
}
}
function buildForChannel(channel, total, index) {
const {status} = spawnSync(
'node',
['./scripts/rollup/build.js', ...process.argv.slice(2)],
{
stdio: ['pipe', process.stdout, process.stderr],
env: {
...process.env,
RELEASE_CHANNEL: channel,
CI_TOTAL: total,
CI_INDEX: index,
},
}
);
if (status !== 0) {
process.exit(status);
}
}
function processStable(buildDir) {
if (fs.existsSync(buildDir + '/node_modules')) {
shell.cp('-r', buildDir + '/node_modules', buildDir + '/oss-stable-semver');
if (canaryChannelLabel === 'rc') {
shell.cp('-r', buildDir + '/node_modules', buildDir + '/oss-stable-rc');
}
const defaultVersionIfNotFound = '0.0.0' + '-' + sha + '-' + dateString;
const versionsMap = new Map();
for (const moduleName in stablePackages) {
const version = stablePackages[moduleName];
versionsMap.set(
moduleName,
version + '-' + canaryChannelLabel + '-' + sha + '-' + dateString,
defaultVersionIfNotFound
);
}
updatePackageVersions(
buildDir + '/node_modules',
versionsMap,
defaultVersionIfNotFound,
true
);
fs.renameSync(buildDir + '/node_modules', buildDir + '/oss-stable');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable',
ReactVersion + '-' + canaryChannelLabel + '-' + sha + '-' + dateString
);
if (canaryChannelLabel === 'rc') {
const rcVersionsMap = new Map();
for (const moduleName in stablePackages) {
const version = stablePackages[moduleName];
rcVersionsMap.set(moduleName, version + `-rc.${rcNumber}`);
}
updatePackageVersions(
buildDir + '/oss-stable-rc',
rcVersionsMap,
defaultVersionIfNotFound,
true
);
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-stable-rc',
ReactVersion
);
}
const rnVersionString =
ReactVersion + '-native-fb-' + sha + '-' + dateString;
if (fs.existsSync(buildDir + '/facebook-react-native')) {
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-react-native',
rnVersionString
);
}
if (fs.existsSync(buildDir + '/react-native')) {
updatePlaceholderReactVersionInCompiledArtifactsFb(
buildDir + '/react-native',
rnVersionString
);
}
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'));
}
}
const versionString =
ReactVersion + '-www-classic-' + sha + '-' + dateString;
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
versionString
);
fs.writeFileSync(buildDir + '/facebook-www/VERSION_CLASSIC', versionString);
}
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');
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/oss-experimental',
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'));
}
}
const versionString =
ReactVersion + '-www-modern-' + sha + '-' + dateString;
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-www',
versionString
);
fs.writeFileSync(buildDir + '/facebook-www/VERSION_MODERN', versionString);
}
const rnVersionString = ReactVersion + '-native-fb-' + sha + '-' + dateString;
if (fs.existsSync(buildDir + '/facebook-react-native')) {
updatePlaceholderReactVersionInCompiledArtifacts(
buildDir + '/facebook-react-native',
rnVersionString
);
fs.writeFileSync(
buildDir + '/facebook-react-native/VERSION_NATIVE_FB',
rnVersionString
);
}
if (fs.existsSync(buildDir + '/react-native')) {
updatePlaceholderReactVersionInCompiledArtifactsFb(
buildDir + '/react-native',
rnVersionString
);
}
if (fs.existsSync(buildDir + '/sizes')) {
fs.renameSync(buildDir + '/sizes', buildDir + '/sizes-experimental');
}
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});
}
function updatePackageVersions(
modulesDir,
versionsMap,
defaultVersionIfNotFound,
pinToExactVersion
) {
for (const moduleName of fs.readdirSync(modulesDir)) {
let version = versionsMap.get(moduleName);
if (version === undefined) {
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));
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')
) {
} else {
for (const dep of Object.keys(packageInfo.peerDependencies)) {
const depVersion = versionsMap.get(dep);
if (depVersion !== undefined) {
packageInfo.peerDependencies[dep] = pinToExactVersion
? depVersion
: '^' + depVersion;
}
}
}
}
fs.writeFileSync(packageJSONPath, JSON.stringify(packageInfo, null, 2));
}
}
}
function updatePlaceholderReactVersionInCompiledArtifacts(
artifactsDirectory,
newVersion
) {
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.replaceAll(
PLACEHOLDER_REACT_VERSION,
newVersion
);
fs.writeFileSync(artifactFilename, replacedText);
}
}
function updatePlaceholderReactVersionInCompiledArtifactsFb(
artifactsDirectory,
newVersion
) {
const artifactFilenames = String(
spawnSync('grep', [
'-lr',
PLACEHOLDER_REACT_VERSION,
'--',
artifactsDirectory,
]).stdout
)
.trim()
.split('\n')
.filter(filename => filename.endsWith('.fb.js'));
for (const artifactFilename of artifactFilenames) {
const originalText = fs.readFileSync(artifactFilename, 'utf8');
const replacedText = originalText.replaceAll(
PLACEHOLDER_REACT_VERSION,
newVersion
);
fs.writeFileSync(artifactFilename, replacedText);
}
}
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);
}
}
}
main();