Refactor/Fix release script (#13619)

This commit is contained in:
fisker Cheung authored and GitHub committed 2022-10-14 17:17:47 +08:00
1 parent 8df3b5ee60
commit 76d0890142
23 files changed
+275 -188

No files matched your search

+2
View File
@@ -13,3 +13,5 @@
/website/static/playground.js /website/static/playground.js
/website/static/lib/ /website/static/lib/
/scripts/benchmark/*/ /scripts/benchmark/*/
**/.yarn/**
**/.pnp.*
+1 -1
View File
@@ -25,7 +25,7 @@ package-lock.json
!.yarn/plugins !.yarn/plugins
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
.pnp.* **/.pnp.*
.nyc_output .nyc_output
.devcontainer .devcontainer
# When installing software on gitpod.io, `core.*` files are generated # When installing software on gitpod.io, `core.*` files are generated
+2 -1
View File
@@ -2,7 +2,8 @@
.cache/ .cache/
coverage/ coverage/
.nyc_output/ .nyc_output/
.yarn/ **/.yarn/**
**/.pnp.*
/dist*/ /dist*/
node_modules/ node_modules/
+1 -1
View File
@@ -9,7 +9,7 @@
"author": "James Long", "author": "James Long",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"main": "./index.cjs", "main": "./src/index.cjs",
"browser": "./standalone.js", "browser": "./standalone.js",
"unpkg": "./standalone.js", "unpkg": "./standalone.js",
"exports": { "exports": {
+6
View File
@@ -8,6 +8,12 @@ node ./scripts/release/release.js --version NEW_VERSION
The script has its own `package.json` so we can reinstall the root's `node_modules/` while making the release. The script has its own `package.json` so we can reinstall the root's `node_modules/` while making the release.
## Flags
- `--manual` Manual run release process instead of publish from GitHub actions
- `--dry` Dry run
- `--skip-dependencies-install` Skip dependencies installation
## Credits ## Credits
This script was inspired by [React's release script](https://github.com/facebook/react/tree/001f9ef/scripts/release). This script was inspired by [React's release script](https://github.com/facebook/react/tree/001f9ef/scripts/release).
+2 -2
View File
@@ -26,5 +26,5 @@ await runCommand("git fetch --tags");
// Install script's dependencies before any require // Install script's dependencies before any require
await runCommand("yarn"); await runCommand("yarn");
const { default: run } = await import("./run.js"); // Ready to run
await run(); await import("./run.js");
+62 -47
View File
@@ -1,61 +1,76 @@
import chalk from "chalk";
import semver from "semver"; import semver from "semver";
import { outdent } from "outdent";
import { runGit, readJson } from "./utils.js"; import { runGit, readJson } from "./utils.js";
import parseArguments from "./parse-arguments.js"; import parseArguments from "./parse-arguments.js";
import { logPromise } from "./utils.js";
import * as steps from "./steps/index.js";
const importDefault = async (module) => (await import(module)).default; const params = parseArguments();
const { stdout: previousVersion } = await runGit([
async function run() {
const params = parseArguments();
const { stdout: previousVersion } = await runGit([
"describe", "describe",
"--tags", "--tags",
"--abbrev=0", "--abbrev=0",
]); ]);
if (semver.parse(previousVersion) === null) {
if (semver.parse(previousVersion) === null) {
throw new Error(`Unexpected previousVersion: ${previousVersion}`); throw new Error(`Unexpected previousVersion: ${previousVersion}`);
} else { } else {
params.previousVersion = previousVersion; params.previousVersion = previousVersion;
params.previousVersionOnDefaultBranch = ( params.previousVersionOnDefaultBranch = (
await readJson("package.json") await readJson("package.json")
).version; ).version;
}
const steps = await Promise.all(
[
"./steps/validate-new-version.js",
"./steps/check-git-status.js",
!params.skipDependenciesInstall && "./steps/install-dependencies.js",
params.manual && "./steps/run-tests.js",
"./steps/update-version.js",
params.manual && "./steps/generate-bundles.js",
"./steps/update-changelog.js",
"./steps/push-to-git.js",
params.manual
? "./steps/publish-to-npm.js"
: "./steps/wait-for-bot-release.js",
"./steps/show-instructions-after-npm-publish.js",
"./steps/update-dependents-count.js",
"./steps/bump-prettier.js",
"./steps/post-publish-steps.js",
]
.filter(Boolean)
.map((step) => importDefault(step))
);
try {
for (const step of steps) {
await step(params);
}
} catch (error) {
const message = outdent.string(error.message.trim());
const stack = error.stack.replace(message, "");
console.error(`${chalk.red("error")} ${message}\n${stack}`);
process.exitCode = 1;
}
} }
export default run; for (let step of [
{
name: "Validating new version",
process: steps.validateNewVersion,
},
{
name: "Checking git status",
process: steps.checkGitStatus,
skip: params.dry,
},
{
name: "Installing NPM dependencies",
process: steps.installDependencies,
skip: params.dry || params.skipDependenciesInstall || !params.manual,
},
{
name: "Linting files",
process: steps.lintFiles,
skip: params.dry,
},
{
name: "Running tests",
process: steps.runTests,
skip: params.dry || !params.manual,
},
{
name: "Bumping version",
process: steps.updateVersion,
skip: params.dry,
},
steps.generateBundles,
steps.updateChangelog,
{
name: "Committing and pushing to remote",
process: steps.pushToGit,
skip: params.dry,
},
params.manual ? steps.publishToNpm : steps.waitForBotRelease,
steps.showInstructionsAfterNpmPublish,
steps.updateDependentsCount,
steps.bumpPrettier,
steps.postPublishSteps,
]) {
if (typeof step === "function") {
step = { process: step };
}
const runStep = () => step.process(params);
if (step.name) {
await logPromise(step.name, runStep, step.skip);
} else if (!step.skip) {
await runStep();
}
}
+11 -3
View File
@@ -1,20 +1,28 @@
import chalk from "chalk"; import chalk from "chalk";
import { runYarn, logPromise, readJson } from "../utils.js"; import { runYarn, logPromise, readJson } from "../utils.js";
export default async function generateBundles({ version }) { export default async function generateBundles({ dry, version, manual }) {
if (!manual) {
return;
}
await logPromise( await logPromise(
"Generating bundles", "Generating bundles",
runYarn(["build", "--clean", "--print-size", "--compare-size"]) runYarn(["build", "--clean", "--print-size", "--compare-size"])
); );
const builtPkg = await readJson("dist/package.json"); const builtPkg = await readJson("dist/package.json");
if (builtPkg.version !== version) { if (!dry && builtPkg.version !== version) {
throw new Error( throw new Error(
`Expected ${version} in dist/package.json but found ${builtPkg.version}` `Expected ${version} in dist/package.json but found ${builtPkg.version}`
); );
} }
await logPromise("Running tests on generated bundles", runYarn("test:dist")); await logPromise(
"Running tests on generated bundles",
() => runYarn("test:dist"),
/* shouldSkip */ dry
);
console.log(chalk.green.bold("Build successful!\n")); console.log(chalk.green.bold("Build successful!\n"));
} }
+15
View File
@@ -0,0 +1,15 @@
export { default as validateNewVersion } from "./validate-new-version.js";
export { default as checkGitStatus } from "./check-git-status.js";
export { default as installDependencies } from "./install-dependencies.js";
export { default as lintFiles } from "./lint-files.js";
export { default as runTests } from "./run-tests.js";
export { default as updateVersion } from "./update-version.js";
export { default as generateBundles } from "./generate-bundles.js";
export { default as updateChangelog } from "./update-changelog.js";
export { default as pushToGit } from "./push-to-git.js";
export { default as publishToNpm } from "./publish-to-npm.js";
export { default as waitForBotRelease } from "./wait-for-bot-release.js";
export { default as showInstructionsAfterNpmPublish } from "./show-instructions-after-npm-publish.js";
export { default as updateDependentsCount } from "./update-dependents-count.js";
export { default as bumpPrettier } from "./bump-prettier.js";
export { default as postPublishSteps } from "./post-publish-steps.js";
@@ -1,7 +1,7 @@
import { execa } from "execa"; import { execa } from "execa";
import { runYarn, runGit, logPromise } from "../utils.js"; import { runYarn, runGit } from "../utils.js";
async function install() { export default async function installDependencies() {
await execa("rm", ["-rf", "node_modules"]); await execa("rm", ["-rf", "node_modules"]);
await runYarn(["install"]); await runYarn(["install"]);
@@ -15,7 +15,3 @@ async function install() {
); );
} }
} }
export default function installDependencies() {
return logPromise("Installing NPM dependencies", install());
}
+3
View File
@@ -0,0 +1,3 @@
import { runYarn } from "../utils.js";
export default () => runYarn("lint");
+23 -19
View File
@@ -3,8 +3,6 @@ import outdent from "outdent";
import { execa } from "execa"; import { execa } from "execa";
import { fetchText, logPromise } from "../utils.js"; import { fetchText, logPromise } from "../utils.js";
const outdentString = outdent.string;
const SCHEMA_REPO = "SchemaStore/schemastore"; const SCHEMA_REPO = "SchemaStore/schemastore";
const SCHEMA_PATH = "src/schemas/json/prettierrc.json"; const SCHEMA_PATH = "src/schemas/json/prettierrc.json";
const RAW_URL = `https://raw.githubusercontent.com/${SCHEMA_REPO}/master/${SCHEMA_PATH}`; const RAW_URL = `https://raw.githubusercontent.com/${SCHEMA_REPO}/master/${SCHEMA_PATH}`;
@@ -25,40 +23,46 @@ async function checkSchema() {
return; return;
} }
return outdentString(chalk` return outdent`
{bold.underline The schema in {yellow SchemaStore} needs an update.} ${chalk.bold.underline(
- Open {cyan.underline ${EDIT_URL}} "The schema in {yellow SchemaStore"
- Run {yellow node scripts/generate-schema.mjs} and copy the new schema )} needs an update.}
- Open ${chalk.cyan.underline(EDIT_URL)}
- Run ${chalk.yellow(
"node scripts/generate-schema.mjs"
)} and copy the new schema
- Paste it on GitHub interface - Paste it on GitHub interface
- Open a PR - Open a PR
`); `;
} }
function twitterAnnouncement() { function twitterAnnouncement() {
return outdentString(chalk` return outdent`
{bold.underline Announce on Twitter} ${chalk.bold.underline("Announce on Twitter")}
- Open {cyan.underline https://tweetdeck.twitter.com} - Open ${chalk.cyan.underline("https://tweetdeck.twitter.com")}
- Make sure you are tweeting from the {yellow @PrettierCode} account. - Make sure you are tweeting from the {yellow @PrettierCode} account.
- Tweet about the release, including the blog post URL. - Tweet about the release, including the blog post URL.
`); `;
} }
export default async function postPublishSteps() { export default async function postPublishSteps({ dry }) {
const steps = [await checkSchema(), twitterAnnouncement()].filter(Boolean);
console.log(chalk.bold.green("The script has finished!\n")); console.log(chalk.bold.green("The script has finished!\n"));
if (steps.length === 0) { if (dry) {
return; return;
} }
const steps = [await checkSchema(), twitterAnnouncement()].filter(Boolean);
console.log( console.log(
outdentString(chalk` outdent`
{yellow.bold The following ${ ${chalk.yellow.bold(
`The following ${
steps.length === 1 ? "step is" : "steps are" steps.length === 1 ? "step is" : "steps are"
} optional.} } optional.`
)}
${steps.join("\n\n")} ${steps.join("\n\n")}
`) `
); );
} }
+22 -14
View File
@@ -1,19 +1,35 @@
import { execa } from "execa"; import { execa } from "execa";
import enquirer from "enquirer"; import enquirer from "enquirer";
import { logPromise } from "../utils.js"; import { waitForEnter } from "../utils.js";
export default async function publishToNpm({ dry }) {
console.log(`Ready to publish to NPM${dry ? "(--dry-run)" : ""}`);
await waitForEnter();
const commonArgs = ["publish"];
if (dry) {
commonArgs.push("--dry-run");
}
/**
* Retry "npm publish" when to enter OTP is failed.
*/
async function retryNpmPublish() {
const runNpmPublish = async () => { const runNpmPublish = async () => {
const args = [...commonArgs];
if (!dry) {
const { otp } = await enquirer.prompt({ const { otp } = await enquirer.prompt({
type: "input", type: "input",
name: "otp", name: "otp",
message: "Please enter your npm OTP", message: "Please enter your npm OTP",
}); });
await execa("npm", ["publish", "--otp", otp], { cwd: "./dist" }); args.push("--otp", otp);
}
await execa("npm", args, { cwd: "./dist" });
}; };
/**
* Retry "npm publish" when to enter OTP is failed.
*/
for (let i = 5; i > 0; i--) { for (let i = 5; i > 0; i--) {
try { try {
return await runNpmPublish(); return await runNpmPublish();
@@ -26,11 +42,3 @@ async function retryNpmPublish() {
} }
} }
} }
export default async function publishToNpm({ dry }) {
if (dry) {
return;
}
await logPromise("Publishing to npm", retryNpmPublish());
}
+2 -10
View File
@@ -1,16 +1,8 @@
import { runGit, logPromise } from "../utils.js"; import { runGit } from "../utils.js";
async function pushGit({ version, repo }) { export default async function pushToGit({ version, repo }) {
await runGit(["commit", "-am", `Release ${version}`]); await runGit(["commit", "-am", `Release ${version}`]);
await runGit(["tag", "-a", version, "-m", `Release ${version}`]); await runGit(["tag", "-a", version, "-m", `Release ${version}`]);
await runGit(["push", "--repo", repo]); await runGit(["push", "--repo", repo]);
await runGit(["push", "--tags", "--repo", repo]); await runGit(["push", "--tags", "--repo", repo]);
} }
export default function pushToGit(params) {
if (params.dry) {
return;
}
return logPromise("Committing and pushing to remote", pushGit(params));
}
+2 -6
View File
@@ -1,7 +1,3 @@
import { runYarn, logPromise } from "../utils.js"; import { runYarn } from "../utils.js";
export default async function runTests() { export default () => runYarn("test");
await logPromise("Running linter", runYarn("lint:eslint"));
await logPromise("Running Prettier on docs", runYarn("lint:prettier"));
await logPromise("Running tests", runYarn("test"));
}
@@ -7,8 +7,6 @@ import {
waitForEnter, waitForEnter,
} from "../utils.js"; } from "../utils.js";
const outdentString = outdent.string;
const RELEASE_URL_BASE = "https://github.com/prettier/prettier/releases/new?"; const RELEASE_URL_BASE = "https://github.com/prettier/prettier/releases/new?";
export function getReleaseUrl(version, previousVersion) { export function getReleaseUrl(version, previousVersion) {
const semverDiff = semver.diff(version, previousVersion); const semverDiff = semver.diff(version, previousVersion);
@@ -42,22 +40,24 @@ export default async function showInstructionsAfterNpmPublish({
const releaseUrl = getReleaseUrl(version, previousVersion); const releaseUrl = getReleaseUrl(version, previousVersion);
console.log( console.log(
outdentString(chalk` outdent`
{green.bold Prettier ${version} published!} ${chalk.green.bold(`Prettier ${version} published!`)}
{yellow.bold Some manual steps are necessary.} ${chalk.yellow.bold("Some manual steps are necessary.")}
{bold.underline Create a GitHub Release} ${chalk.bold.underline("Create a GitHub Release")}
- Go to {cyan.underline ${releaseUrl}} - Go to ${chalk.cyan.underline(releaseUrl)}
- Press {bgGreen.black Publish release } - Press ${chalk.bgGreen.black("Publish release ")}
{bold.underline Test the new release} ${chalk.bold.underline("Test the new release")}
- In a new session, run {yellow npm i prettier@latest} in another directory - In a new session, run ${chalk.yellow(
"npm i prettier@latest"
)} in another directory
- Test the API and CLI - Test the API and CLI
After that, we can proceed to bump this repo's Prettier dependency. After that, we can proceed to bump this repo's Prettier dependency.
Press ENTER to continue. `
`)
); );
await waitForEnter(); await waitForEnter();
} }
+14 -10
View File
@@ -1,7 +1,6 @@
import fs from "node:fs"; import fs from "node:fs";
import { execa } from "execa"; import { execa } from "execa";
import chalk from "chalk"; import chalk from "chalk";
import outdent from "outdent";
import semver from "semver"; import semver from "semver";
import { import {
waitForEnter, waitForEnter,
@@ -11,8 +10,6 @@ import {
getChangelogContent, getChangelogContent,
} from "../utils.js"; } from "../utils.js";
const outdentString = outdent.string;
function writeChangelog(params) { function writeChangelog(params) {
const changelog = fs.readFileSync("CHANGELOG.md", "utf8"); const changelog = fs.readFileSync("CHANGELOG.md", "utf8");
const newEntry = `# ${params.version}\n\n` + getChangelogContent(params); const newEntry = `# ${params.version}\n\n` + getChangelogContent(params);
@@ -30,7 +27,15 @@ async function getChangelogForPatch({ version, previousVersion }) {
return changelog; return changelog;
} }
export default async function updateChangelog({ version, previousVersion }) { export default async function updateChangelog({
dry,
version,
previousVersion,
}) {
if (dry) {
return;
}
const semverDiff = semver.diff(version, previousVersion); const semverDiff = semver.diff(version, previousVersion);
if (semverDiff !== "patch") { if (semverDiff !== "patch") {
@@ -45,11 +50,11 @@ export default async function updateChangelog({ version, previousVersion }) {
return; return;
} }
console.warn( console.warn(
outdentString(chalk` `${chalk.yellow("warning")} The file ${chalk.bold(
{yellow warning} The file {bold ${blogPost.file}} doesn't exist, but it will be referenced in {bold CHANGELOG.md}. Make sure to create it later. blogPost.file
)} doesn't exist, but it will be referenced in ${chalk.bold(
Press ENTER to continue. "CHANGELOG.md"
`) )}. Make sure to create it later.`
); );
} else { } else {
const body = await getChangelogForPatch({ const body = await getChangelogForPatch({
@@ -61,7 +66,6 @@ export default async function updateChangelog({ version, previousVersion }) {
previousVersion, previousVersion,
body, body,
}); });
console.log("Press ENTER to continue.");
} }
await waitForEnter(); await waitForEnter();
@@ -71,7 +71,11 @@ function formatNumber(value) {
return Math.floor(value / 1e5) / 10 + " million"; return Math.floor(value / 1e5) / 10 + " million";
} }
export default async function updateDependentsCount() { export default async function updateDependentsCount({ dry }) {
if (dry) {
return;
}
try { try {
await update(); await update();
} catch (error) { } catch (error) {
+2 -12
View File
@@ -1,12 +1,6 @@
import { import { runYarn, readJson, writeJson, processFile } from "../utils.js";
runYarn,
logPromise,
readJson,
writeJson,
processFile,
} from "../utils.js";
async function bump({ version }) { export default async function updateVersion({ version }) {
const pkg = await readJson("package.json"); const pkg = await readJson("package.json");
pkg.version = version; pkg.version = version;
await writeJson("package.json", pkg); await writeJson("package.json", pkg);
@@ -34,7 +28,3 @@ async function bump({ version }) {
cwd: "./website", cwd: "./website",
}); });
} }
export default async function updateVersion(params) {
await logPromise("Bumping version", bump(params));
}
@@ -2,13 +2,19 @@ import chalk from "chalk";
import semver from "semver"; import semver from "semver";
export default function validateNewVersion({ version, previousVersion }) { export default function validateNewVersion({ version, previousVersion }) {
if (!version) {
throw new Error("'--version' is required");
}
if (!semver.valid(version)) { if (!semver.valid(version)) {
throw new Error("Invalid version specified"); throw new Error(
`Invalid version '${chalk.red.underline(version)}' specified`
);
} }
if (!semver.gt(version, previousVersion)) { if (!semver.gt(version, previousVersion)) {
throw new Error( throw new Error(
`Version ${chalk.yellow(version)} has already been published` `Version '${chalk.yellow.underline(version)}' has already been published`
); );
} }
} }
+28 -22
View File
@@ -2,8 +2,6 @@ import chalk from "chalk";
import outdent from "outdent"; import outdent from "outdent";
import { waitForEnter, logPromise } from "../utils.js"; import { waitForEnter, logPromise } from "../utils.js";
const outdentString = outdent.string;
export async function isVersionReleased(version) { export async function isVersionReleased(version) {
const response = await fetch("https://registry.npmjs.org/prettier/"); const response = await fetch("https://registry.npmjs.org/prettier/");
const result = await response.json(); const result = await response.json();
@@ -23,8 +21,6 @@ async function checkBotPermission() {
return maintainers.some(({ name }) => name === "prettier-bot"); return maintainers.some(({ name }) => name === "prettier-bot");
} }
checkBotPermission();
const sleep = () => const sleep = () =>
new Promise((resolve) => { new Promise((resolve) => {
setTimeout(resolve, 30_000); setTimeout(resolve, 30_000);
@@ -37,35 +33,45 @@ export default async function waitForBotRelease({ dry, version }) {
if (!(await checkBotPermission())) { if (!(await checkBotPermission())) {
console.log( console.log(
outdentString(chalk/* indent */ ` outdent`
1. Go to {green.underline https://www.npmjs.com/package/prettier/access} 1. Go to ${chalk.green.underline(
2. Add "{yellow prettier-bot}" as prettier package maintainer. "https://www.npmjs.com/package/prettier/access"
)}
Press ENTER to continue. 2. Add "${chalk.yellow("prettier-bot")}" as prettier package maintainer.
`) `
); );
await waitForEnter(); await waitForEnter();
} }
console.log( console.log(
outdentString(chalk/* indent */ ` outdent`
1. Go to {green.underline https://www.npmjs.com/package/prettier/access} 1. Go to ${chalk.green.underline(
2. Make sure "{yellow Publishing access}" section is set to "{yellow Require two-factor authentication or automation tokens}". "https://www.npmjs.com/package/prettier/access"
)}
Press ENTER to continue. 2. Make sure "${chalk.yellow(
`) "Publishing access"
)}" section is set to "${chalk.yellow(
"Require two-factor authentication or automation tokens"
)}".
`
); );
await waitForEnter(); await waitForEnter();
console.log( console.log(
outdentString(chalk/* indent */ ` outdent`
1. Go to {green.underline https://github.com/prettier/release-workflow/actions/workflows/release.yml} 1. Go to ${chalk.green.underline(
2. Click "{green Run workflow}" button, type "{yellow.underline ${version}}" in "Version to release", uncheck all checkboxes, hit the "{bgGreen Run workflow}" button. "https://github.com/prettier/release-workflow/actions/workflows/release.yml"
)}
Press ENTER to continue. 2. Click "${chalk.green(
`) "Run workflow"
)}" button, type "${chalk.yellow.underline(
version
)}" in "Version to release", uncheck all checkboxes, hit the "${chalk.bgGreen(
"Run workflow"
)}" button.
`
); );
await waitForEnter(); await waitForEnter();
@@ -4,12 +4,20 @@ import chalk from "chalk";
import validateNewVersion from "../steps/validate-new-version.js"; import validateNewVersion from "../steps/validate-new-version.js";
describe("validate-new-version", () => { describe("validate-new-version", () => {
it("throws error for missing version", () => {
assert.throws(
() => {
validateNewVersion({});
},
{ message: "'--version' is required" }
);
});
it("throws error for invalid semver", () => { it("throws error for invalid semver", () => {
assert.throws( assert.throws(
() => { () => {
validateNewVersion({ version: "foo" }); validateNewVersion({ version: "foo" });
}, },
{ message: "Invalid version specified" } { message: `Invalid version '${chalk.red.underline("foo")}' specified` }
); );
}); });
it("throws error when version isn't greater than prev version", () => { it("throws error when version isn't greater than prev version", () => {
@@ -17,7 +25,11 @@ describe("validate-new-version", () => {
() => { () => {
validateNewVersion({ version: "0.0.1", previousVersion: "0.0.2" }); validateNewVersion({ version: "0.0.1", previousVersion: "0.0.2" });
}, },
{ message: `Version ${chalk.yellow("0.0.1")} has already been published` } {
message: `Version '${chalk.yellow(
"0.0.1"
)}' has already been published`,
}
); );
}); });
}); });
+36 -17
View File
@@ -2,38 +2,54 @@ import fs from "node:fs";
import readline from "node:readline"; import readline from "node:readline";
import chalk from "chalk"; import chalk from "chalk";
import { execa } from "execa"; import { execa } from "execa";
import stringWidth from "string-width";
import outdent from "outdent"; import outdent from "outdent";
import getFormattedDate from "./get-formatted-date.js"; import getFormattedDate from "./get-formatted-date.js";
readline.emitKeypressEvents(process.stdin); readline.emitKeypressEvents(process.stdin);
const OK = chalk.bgGreen.black(" DONE "); const statusConfig = [
const FAIL = chalk.bgRed.black(" FAIL "); { color: "bgGreen", text: "DONE" },
{ color: "bgRed", text: "FAIL" },
{ color: "bgGray", text: "SKIPPED" },
];
const maxLength = Math.max(...statusConfig.map(({ text }) => text.length)) + 2;
const padStatusText = (text) => {
while (text.length < maxLength) {
text = text.length % 2 ? `${text} ` : ` ${text}`;
}
return text;
};
const status = {};
for (const { color, text } of statusConfig) {
status[text] = chalk[color].black(padStatusText(text));
}
function fitTerminal(input) { function fitTerminal(input, suffix = "") {
const columns = Math.min(process.stdout.columns, 80); const columns = Math.min(process.stdout.columns || 40, 80);
const WIDTH = columns - stringWidth(OK) + 1; const WIDTH = columns - maxLength + 1;
if (input.length < WIDTH) { if (input.length < WIDTH) {
input += chalk.dim(".").repeat(WIDTH - input.length - 1); const repeatCount = Math.max(WIDTH - input.length - 1 - suffix.length, 0);
input += chalk.dim(".").repeat(repeatCount) + suffix;
} }
return input; return input;
} }
async function logPromise(name, promiseOrAsyncFunction) { async function logPromise(name, promiseOrAsyncFunction, shouldSkip = false) {
const promise =
typeof promiseOrAsyncFunction === "function"
? promiseOrAsyncFunction()
: promiseOrAsyncFunction;
process.stdout.write(fitTerminal(name)); process.stdout.write(fitTerminal(name));
if (shouldSkip) {
process.stdout.write(`${status.SKIPPED}\n`);
return;
}
try { try {
const result = await promise; const result = await (typeof promiseOrAsyncFunction === "function"
process.stdout.write(`${OK}\n`); ? promiseOrAsyncFunction()
: promiseOrAsyncFunction);
process.stdout.write(`${status.DONE}\n`);
return result; return result;
} catch (error) { } catch (error) {
process.stdout.write(`${FAIL}\n`); process.stdout.write(`${status.FAIL}\n`);
throw error; throw error;
} }
} }
@@ -42,7 +58,7 @@ async function runYarn(args, options) {
args = Array.isArray(args) ? args : [args]; args = Array.isArray(args) ? args : [args];
try { try {
return await execa("yarn", ["--silent", ...args], options); return await execa("yarn", ["--silent", "run", ...args], options);
} catch (error) { } catch (error) {
throw new Error(`\`yarn ${args.join(" ")}\` failed\n${error.stdout}`); throw new Error(`\`yarn ${args.join(" ")}\` failed\n${error.stdout}`);
} }
@@ -54,6 +70,9 @@ function runGit(args, options) {
} }
function waitForEnter() { function waitForEnter() {
console.log();
console.log(chalk.gray("Press ENTER to continue."));
process.stdin.setRawMode(true); process.stdin.setRawMode(true);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {