'use strict';
const {exec} = require('child-process-promise');
const {createPatch} = require('diff');
const {hashElement} = require('folder-hash');
const {existsSync, readFileSync, writeFileSync} = require('fs');
const {readJson, writeJson} = require('fs-extra');
const fetch = require('node-fetch');
const logUpdate = require('log-update');
const {join} = require('path');
const createLogger = require('progress-estimator');
const prompt = require('prompt-promise');
const theme = require('./theme');
const {stablePackages, experimentalPackages} = require('../../ReactVersions');
const logger = createLogger({
storagePath: join(__dirname, '.progress-estimator'),
});
const addDefaultParamValue = (optionalShortName, longName, defaultValue) => {
let found = false;
for (let i = 0; i < process.argv.length; i++) {
const current = process.argv[i];
if (current === optionalShortName || current.startsWith(`${longName}=`)) {
found = true;
break;
}
}
if (!found) {
process.argv.push(`${longName}=${defaultValue}`);
}
};
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);
return stdout.trim();
};
const extractCommitFromVersionNumber = version => {
const match = version.match(/0\.0\.0\-([a-z]+\-){0,1}([^-]+).+/);
if (match === null) {
throw Error(`Could not extra commit from version "${version}"`);
}
return match[2];
};
const getArtifactsList = async buildID => {
const headers = {};
const {CIRCLE_CI_API_TOKEN} = process.env;
if (CIRCLE_CI_API_TOKEN != null) {
headers['Circle-Token'] = CIRCLE_CI_API_TOKEN;
}
const jobArtifactsURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildID}/artifacts`;
const jobArtifacts = await fetch(jobArtifactsURL, {headers});
return jobArtifacts.json();
};
const getBuildInfo = async () => {
const cwd = join(__dirname, '..', '..');
const isExperimental = process.env.RELEASE_CHANNEL === 'experimental';
const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
cwd,
});
const commit = await execRead('git show -s --no-show-signature --format=%h', {
cwd,
});
const checksum = await getChecksumForCurrentRevision(cwd);
const dateString = await getDateStringForCommit(commit);
const version = isExperimental
? `0.0.0-experimental-${commit}-${dateString}`
: `0.0.0-${commit}-${dateString}`;
const buildNumber = process.env.CIRCLE_BUILD_NUM;
const packageJSON = await readJson(
join(cwd, 'packages', 'react', 'package.json')
);
const reactVersion = isExperimental
? `${packageJSON.version}-experimental-${commit}-${dateString}`
: `${packageJSON.version}-${commit}-${dateString}`;
return {branch, buildNumber, checksum, commit, reactVersion, version};
};
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 getDateStringForCommit = async commit => {
let dateString = await execRead(
`git show -s --no-show-signature --format=%cd --date=format:%Y%m%d ${commit}`
);
if (dateString.startsWith("'")) {
dateString = dateString.slice(1, 9);
}
return dateString;
};
const getCommitFromCurrentBuild = async () => {
const cwd = join(__dirname, '..', '..');
const buildInfoJSON = join(
cwd,
'build',
'oss-experimental',
'react',
'build-info.json'
);
if (existsSync(buildInfoJSON)) {
const buildInfo = await readJson(buildInfoJSON);
return buildInfo.commit;
} else {
const packageJSON = join(
cwd,
'build',
'oss-experimental',
'react',
'package.json'
);
const {version} = await readJson(packageJSON);
return extractCommitFromVersionNumber(version);
}
};
const getPublicPackages = isExperimental => {
const packageNames = Object.keys(stablePackages);
if (isExperimental) {
packageNames.push(...experimentalPackages);
}
return packageNames;
};
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, estimate) =>
logger(promise, text, {estimate});
const printDiff = (path, beforeContents, afterContents) => {
const patch = createPatch(path, beforeContents, afterContents);
const coloredLines = patch
.split('\n')
.slice(2)
.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;
};
const splitCommaParams = array => {
for (let i = array.length - 1; i >= 0; i--) {
const param = array[i];
if (param.includes(',')) {
array.splice(i, 1, ...param.split(','));
}
}
};
const updateVersionsForNext = async (cwd, reactVersion, version) => {
const isExperimental = reactVersion.includes('experimental');
const packages = getPublicPackages(isExperimental);
const packagesDir = join(cwd, 'packages');
const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
const sourceReactVersion = readFileSync(
sourceReactVersionPath,
'utf8'
).replace(/export default '[^']+';/, `export default '${reactVersion}';`);
writeFileSync(sourceReactVersionPath, sourceReactVersion);
{
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);
const packageJSONPath = join(packagePath, 'package.json');
const packageJSON = await readJson(packageJSONPath);
packageJSON.version = version;
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 = {
addDefaultParamValue,
confirm,
execRead,
getArtifactsList,
getBuildInfo,
getChecksumForCurrentRevision,
getCommitFromCurrentBuild,
getDateStringForCommit,
getPublicPackages,
handleError,
logPromise,
printDiff,
splitCommaParams,
theme,
updateVersionsForNext,
};