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