embark/scripts/release.js

355 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const chalk = require('chalk');
const {execSync} = require('child_process');
const minimist = require('minimist');
const path = require('path');
const {readJsonSync} = require('fs-extra');
const args = minimist(process.argv.slice(2));
const DEFAULT_BUMP = null;
const bump = args._[0] || DEFAULT_BUMP;
const DEFAULT_COMMIT_MSG = `chore(release): %v`;
const commitMsg = args['commit-message'] || DEFAULT_COMMIT_MSG;
const DEFAULT_CONVENTIONAL_COMMITS = true;
let cCommits;
// with --no-conventional-commits cli option `args['conventional-commits']` will be `false`
// there is never a need to use --conventional-commits cli option because the
// default behavior is `true`
if (args['conventional-commits'] === false) {
cCommits = false;
} else {
cCommits = DEFAULT_CONVENTIONAL_COMMITS;
}
const DEFAULT_CONVENTIONAL_GRADUATE = false;
const cGraduate = args['conventional-graduate'] || DEFAULT_CONVENTIONAL_GRADUATE;
const DEFAULT_CONVENTIONAL_PRERELEASE = false;
const cPrerelease = args['conventional-prerelease'] || DEFAULT_CONVENTIONAL_PRERELEASE;
// if not using --no-create-release or --no-push cli option then an environment
// variable named GH_TOKEN must be defined with a properly scoped GitHub
// personal access token; for local releases, e.g. with verdaccio,
// --no-create-release should be used (unless using --no-push, which implies
// --no-create-release)
// See: https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type
const DEFAULT_CREATE_RELEASE = true;
let createRelease;
// with --no-create-release cli option `args['create-release']` will be `false`
// there is never a need to use --create-release cli option because the default
// is `true`
if (args['create-release'] === false) {
createRelease = false;
} else {
createRelease = DEFAULT_CREATE_RELEASE;
}
const DEFAULT_DIST_TAG = `latest`;
const distTag = args['dist-tag'] || DEFAULT_DIST_TAG;
const DEFAULT_FORCE_PUBLISH = false;
const forcePublish = args['force-publish'] || DEFAULT_FORCE_PUBLISH;
const DEFAULT_GIT_REMOTE = `origin`;
const remote = args['git-remote'] || DEFAULT_GIT_REMOTE;
const DEFAULT_NO_PUSH = false;
let noPush;
// with --no-push cli option `args['push']` will be `false`
// there is never a need to use --push cli option because the default behavior
// is to push
if (args['push'] === false) {
noPush = true;
} else {
noPush = DEFAULT_NO_PUSH;
}
const DEFAULT_PRE_ID = null;
const preId = args.preid || DEFAULT_PRE_ID;
const DEFAULT_RELEASE_BRANCH = `master`;
const branch = args['release-branch'] || DEFAULT_RELEASE_BRANCH;
const DEFAULT_SIGN = false;
const sign = args.sign || DEFAULT_SIGN;
const DEFAULT_SKIP_QA = false;
const skipQa = args['skip-qa'] || DEFAULT_SKIP_QA;
const DEFAULT_VERSION_ONLY = false;
const versionOnly = args['version-only'] || DEFAULT_VERSION_ONLY;
const cyan = (str) => chalk.cyan(str);
const execSyncInherit = (cmd) => execSync(cmd, {stdio: 'inherit'});
const log = (mark, str, which = 'log') => console[which](
mark, str.filter(s => !!s).join(` `)
);
const logError = (...str) => log(chalk.red(``), str, 'error');
const logInfo = (...str) => log(chalk.blue(``), str);
const logSuccess = (...str) => log(chalk.green(``), str);
const logWarning = (...str) => log(chalk.yellow('‼︎'), str);
const failMsg = `${chalk.red(`RELEASE FAILED!`)} Stopping right here.`;
const reportSetting = (desc, val, def) => {
logInfo(`${desc} is set to ${cyan(val)}${val === def ? ` (default).`: `.`}`);
};
const runCommand = (cmd, inherit = true, display) => {
logInfo(`Running command ${cyan(display || cmd)}.`);
let out;
if (inherit) {
execSyncInherit(cmd);
} else {
out = execSync(cmd);
}
return out;
};
// eslint-disable-next-line complexity
(async () => {
if (!noPush && createRelease && !process.env.GH_TOKEN) {
logError(
`Environment variable ${cyan('GH_TOKEN')} was not defined or falsy. It`,
`must be defined with a properly scoped GitHub personal access token,`,
`or else the ${cyan('--no-create-release')} or ${cyan('--no-push')}`,
`option should be used. Always use ${cyan('--no-create-release')} when`,
`running a local release, e.g. with ${cyan('verdaccio')} (unless using`,
`${cyan('--no-push')}, which implies ${cyan('--no-create-release')}).`
);
logError(failMsg);
process.exit(1);
}
const lernaJsonPath = path.join(__dirname, '../lerna.json');
logInfo(`Reading ${cyan(lernaJsonPath)}...`);
let lernaJson;
try {
lernaJson = readJsonSync(lernaJsonPath);
} catch (e) {
console.error(e.stack || e);
logError(
`Could not read ${cyan(lernaJsonPath)}. Please check the error above.`
);
logError(failMsg);
process.exit(1);
}
let DEFAULT_REGISTRY, registry;
if (!versionOnly) {
try {
DEFAULT_REGISTRY = lernaJson.command.publish.registry;
if (!DEFAULT_REGISTRY) throw new Error('missing registry in lerna.json');
registry = args.registry || DEFAULT_REGISTRY;
} catch (e) {
console.error(e.stack || e);
logError(
`Could not read values from ${cyan(lernaJsonPath)}. Please check the`,
`error above.`
);
logError(failMsg);
process.exit(1);
}
}
logInfo(`Checking the working tree...`);
try {
runCommand(`npm run --silent cwtree`, true, `npm run cwtree`);
logSuccess(`Working tree is clean.`);
} catch (e) {
logError(
`Working tree is dirty or has untracked files.`,
`Please make necessary changes or commits before rerunning this script.`
);
logError(failMsg);
process.exit(1);
}
reportSetting(`Release branch`, branch, DEFAULT_RELEASE_BRANCH);
logInfo(`Determining the current branch...`);
let currentBranch;
try {
currentBranch = runCommand(`git rev-parse --abbrev-ref HEAD`, false)
.toString()
.trim();
} catch (e) {
logError(`Could not determine the branch. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (currentBranch === branch) {
logSuccess(`Current branch and release branch are the same.`);
} else {
logError(
`Current branch ${cyan(currentBranch)} is not the same as release`,
`branch ${cyan(branch)}. Please checkout the release branch before`,
`rerunning this script or rerun with`,
`${cyan(`--release-branch ${currentBranch}`)}.`
);
logError(failMsg);
process.exit(1);
}
let localRef;
if (!noPush) {
reportSetting(`Git remote`, remote, DEFAULT_GIT_REMOTE);
logInfo(
`Fetching commits from ${cyan(remote)} to compare local and remote`,
`branches...`
);
try {
runCommand(`git fetch ${remote}`, false);
} catch (e) {
logError(`Could not fetch latest commits. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
let remoteRef;
try {
localRef = runCommand(`git rev-parse ${branch}`, false).toString().trim();
remoteRef = runCommand(`git rev-parse ${remote}/${branch}`, false)
.toString()
.trim();
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (localRef === remoteRef) {
logSuccess(`Local branch is in sync with remote branch.`);
} else {
logError(
`Local branch ${cyan(branch)} is not in sync with`,
`${cyan(`${remote}/${branch}`)}.`,
`Please sync branches before rerunning this script.`
);
logError(failMsg);
process.exit(1);
}
} else {
try {
localRef = runCommand(`git rev-parse ${branch}`, false).toString().trim();
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
}
if (skipQa) {
logWarning(
`Skipping the QA suite. You already built the packages, right?`
);
} else {
logInfo(
`It's time to run the QA suite, this will take awhile...`
);
try {
runCommand(`npm run qa:full`);
logSuccess(`All steps succeeded in the QA suite.`);
} catch (e) {
logError(`A step failed in the QA suite. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
}
logInfo(`${versionOnly ? 'Versioning' : 'Publishing'} with Lerna...`);
if (bump) reportSetting(`Version bump`, bump, DEFAULT_BUMP);
reportSetting(`Conventional commits option`, cCommits, DEFAULT_CONVENTIONAL_COMMITS);
reportSetting(`Conventional graduate option`, cGraduate, DEFAULT_CONVENTIONAL_GRADUATE);
reportSetting(`Conventional prerelease option`, cPrerelease, DEFAULT_CONVENTIONAL_PRERELEASE);
if (!noPush) reportSetting(`Create GitHub release option`, createRelease, DEFAULT_CREATE_RELEASE);
if (!versionOnly) reportSetting(`NPM dist-tag`, distTag, DEFAULT_DIST_TAG);
reportSetting(`Force publish option`, forcePublish, DEFAULT_FORCE_PUBLISH);
reportSetting(`Commit message format`, commitMsg, DEFAULT_COMMIT_MSG);
reportSetting(`No push option`, noPush, DEFAULT_NO_PUSH);
if (preId) reportSetting(`Prerelease identifier`, preId, DEFAULT_PRE_ID);
if(!versionOnly) reportSetting(`Package registry`, registry, DEFAULT_REGISTRY);
reportSetting(`Signature option`, sign, DEFAULT_SIGN);
const lernaCmd = [
`lerna`,
(versionOnly && `version`) || `publish`,
bump || ``,
(cCommits && `--conventional-commits`) || ``,
(cCommits && cGraduate && `--conventional-graduate${cGraduate === true ? '' : `=${cGraduate}`}`) || ``,
(cCommits && cPrerelease && `--conventional-prerelease${cPrerelease === true ? '' : `=${cPrerelease}`}`) || ``,
(!noPush && createRelease && `--create-release github`) || ``,
(!versionOnly && `--dist-tag ${distTag}`) || ``,
(forcePublish && `--force-publish${forcePublish === true ? '' : `=${forcePublish}`}`) || ``,
(!noPush && `--git-remote ${remote}`) || ``,
`--message "${commitMsg}"`,
(noPush && `--no-push`) || ``,
(preId && `--preid ${preId}`) || ``,
(!versionOnly && `--registry ${registry}`) || ``,
(sign && `--sign-git-commit`) || ``,
(sign && `--sign-git-tag`) || ``
].filter(str => !!str).join(` `);
try {
runCommand(lernaCmd);
if (bump !== 'from-package' && localRef ===
runCommand(`git rev-parse ${branch}`, false).toString().trim()) {
let action, cmd, noPubMsg;
if (versionOnly) {
action = 'version creation';
cmd = 'version';
noPubMsg = '';
} else {
action = 'publication';
cmd = 'publish';
noPubMsg = `No packages were published. `;
}
logWarning(
chalk.yellow(`RELEASE STOPPED!`),
`No commit or tag was created. ${noPubMsg}Please check the output`,
`above if you did not stop ${action} via a prompt from the`,
`${cyan(`lerna ${cmd}`)} command.`
);
process.exit(0);
}
} catch (e) {
logError(`A problem occured. Please check the error above.`);
let checkPkgMsg;
if (versionOnly) {
checkPkgMsg = '';
} else {
const lernaDocsUrl = 'https://github.com/lerna/lerna/tree/master/commands/publish#bump-from-package';
checkPkgMsg = [
` Check the package registry to verify if any packages were published.`,
`If so they may need to be flagged as deprecated since the`,
`${cyan(`lerna publish`)} command exited with error. In some cases it`,
`may be possible to salvage an imcomplete release by using the`,
`${cyan('from-package')} keyword with the ${cyan('lerna publish')}`,
`command. See: ${lernaDocsUrl}`
].join(' ');
}
logError(
failMsg,
`Make sure to clean up the working tree and local/remote commits, tags,`,
`and releases as necessary.${checkPkgMsg}`
);
process.exit(1);
}
logSuccess(`${chalk.green(`RELEASE SUCCEEDED!`)} Woohoo! Done.`);
})().then(() => {
process.exit(0);
}).catch(e => {
console.error(e.stack || e);
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
});