Refactor release script (#13611)
This commit is contained in:
1 parent
31138aadb5
commit
a79b774ec6
18 files changed
+245
-3481
No files matched your search
@@ -11,7 +11,6 @@
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import fetch from "node-fetch";
|
||||
import createEsmUtils from "esm-utils";
|
||||
import enquirer from "enquirer";
|
||||
import { CHANGELOG_CATEGORIES } from "./utils/changelog-categories.mjs";
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const { jest } = import.meta;
|
||||
|
||||
jest.unstable_mockModule("../get-formatted-date", () => ({
|
||||
default: () => ({
|
||||
year: "2021",
|
||||
month: "09",
|
||||
day: "01",
|
||||
}),
|
||||
}));
|
||||
@@ -1,26 +0,0 @@
|
||||
import { getReleaseUrl } from "../../steps/show-instructions-after-npm-publish.js";
|
||||
|
||||
describe("publish-to-npm", () => {
|
||||
describe("getReleaseUrl", () => {
|
||||
it("returns URL for patch releasing", () => {
|
||||
const result = getReleaseUrl("2.3.1", "2.3.0");
|
||||
expect(result).toBe(
|
||||
"https://github.com/prettier/prettier/releases/new?tag=2.3.1&title=2.3.1&body=%F0%9F%94%97%20%5BChangelog%5D(https%3A%2F%2Fgithub.com%2Fprettier%2Fprettier%2Fblob%2Fmain%2FCHANGELOG.md%23231)"
|
||||
);
|
||||
});
|
||||
|
||||
it("returns URL for minor releasing", () => {
|
||||
const result = getReleaseUrl("2.4.0", "2.3.0");
|
||||
expect(result).toBe(
|
||||
"https://github.com/prettier/prettier/releases/new?tag=2.4.0&title=2.4.0&body=%5Bdiff%5D(https%3A%2F%2Fgithub.com%2Fprettier%2Fprettier%2Fcompare%2F2.3.0...2.4.0)%0A%0A%F0%9F%94%97%20%5BRelease%20note%5D(https%3A%2F%2Fprettier.io%2Fblog%2F2021%2F09%2F01%2F2.4.0.html)"
|
||||
);
|
||||
});
|
||||
|
||||
it("returns URL for major releasing", () => {
|
||||
const result = getReleaseUrl("2.3.0", "2.2.0");
|
||||
expect(result).toBe(
|
||||
"https://github.com/prettier/prettier/releases/new?tag=2.3.0&title=2.3.0&body=%5Bdiff%5D(https%3A%2F%2Fgithub.com%2Fprettier%2Fprettier%2Fcompare%2F2.2.0...2.3.0)%0A%0A%F0%9F%94%97%20%5BRelease%20note%5D(https%3A%2F%2Fprettier.io%2Fblog%2F2021%2F09%2F01%2F2.3.0.html)"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import chalk from "chalk";
|
||||
import step from "../../steps/validate-new-version.js";
|
||||
|
||||
describe("validate-new-version", () => {
|
||||
it("throws error for invalid semver", () => {
|
||||
expect(() => {
|
||||
step({ version: "foo" });
|
||||
}).toThrow("Invalid version specified");
|
||||
});
|
||||
it("throws error when version isn't greater than prev version", () => {
|
||||
expect(() => {
|
||||
step({ version: "0.0.1", previousVersion: "0.0.2" });
|
||||
}).toThrow(`Version ${chalk.yellow("0.0.1")} has already been published`);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { isVersionReleased } from "../../steps/wait-for-bot-release.js";
|
||||
|
||||
describe("isVersionReleased", () => {
|
||||
test("returns true for existing version", async () => {
|
||||
const result = await isVersionReleased("1.0.0");
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
test("rejects true for non-existing version", async () => {
|
||||
await expect(
|
||||
isVersionReleased("999.0.0")
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
'"prettier@999.0.0 doesn\'t exit."'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
const config = {
|
||||
testMatch: ["<rootDir>/__tests__/**/*.spec.js"],
|
||||
setupFiles: ["<rootDir>/__tests__/setup.js"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -2,19 +2,17 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
"enquirer": "2.3.6",
|
||||
"execa": "6.1.0",
|
||||
"minimist": "1.2.6",
|
||||
"node-fetch": "3.2.10",
|
||||
"outdent": "0.8.0",
|
||||
"semver": "7.3.8",
|
||||
"string-width": "5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "29.1.2"
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
function parseArguments() {
|
||||
const {
|
||||
values: {
|
||||
version,
|
||||
repo = "git@github.com:prettier/prettier.git",
|
||||
manual = false,
|
||||
dry = false,
|
||||
"skip-dependencies-install": skipDependenciesInstall = false,
|
||||
},
|
||||
} = parseArgs({
|
||||
options: {
|
||||
version: {
|
||||
type: "string",
|
||||
},
|
||||
repo: {
|
||||
type: "string",
|
||||
},
|
||||
dry: {
|
||||
type: "boolean",
|
||||
},
|
||||
manual: {
|
||||
type: "boolean",
|
||||
},
|
||||
"skip-dependencies-install": {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
version,
|
||||
repo,
|
||||
manual,
|
||||
dry,
|
||||
skipDependenciesInstall,
|
||||
};
|
||||
}
|
||||
|
||||
export default parseArguments;
|
||||
+21
-82
@@ -1,91 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* This file can't use any dependency since the dependencies may not installed yet */
|
||||
import { exec } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
|
||||
async function run() {
|
||||
const importDefault = async (module) => (await import(module)).default;
|
||||
|
||||
const chalk = await importDefault("chalk");
|
||||
const minimist = await importDefault("minimist");
|
||||
const semver = await importDefault("semver");
|
||||
const { string: outdentString } = await importDefault("outdent");
|
||||
const { runGit, readJson } = await import("./utils.js");
|
||||
|
||||
const params = minimist(process.argv.slice(2), {
|
||||
string: ["version", "repo"],
|
||||
boolean: ["dry", "manual", "skip-dependencies-install"],
|
||||
alias: { v: "version" },
|
||||
default: {
|
||||
manual: false,
|
||||
dry: false,
|
||||
"skip-dependencies-install": false,
|
||||
repo: "git@github.com:prettier/prettier.git",
|
||||
},
|
||||
const directory = path.dirname(fileURLToPath(import.meta.url));
|
||||
function runCommand(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { stdout, stderr } = exec(command, { cwd: directory }, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
stdout.pipe(process.stdout);
|
||||
stderr.pipe(process.stderr);
|
||||
});
|
||||
|
||||
const { stdout: previousVersion } = await runGit([
|
||||
"describe",
|
||||
"--tags",
|
||||
"--abbrev=0",
|
||||
]);
|
||||
|
||||
if (semver.parse(previousVersion) === null) {
|
||||
throw new Error(`Unexpected previousVersion: ${previousVersion}`);
|
||||
} else {
|
||||
params.previousVersion = previousVersion;
|
||||
params.previousVersionOnDefaultBranch = (
|
||||
await readJson("package.json")
|
||||
).version;
|
||||
}
|
||||
|
||||
const steps = await Promise.all(
|
||||
[
|
||||
"./steps/validate-new-version.js",
|
||||
"./steps/check-git-status.js",
|
||||
!params["skip-dependencies-install"] && "./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 = outdentString(error.message.trim());
|
||||
const stack = error.stack.replace(message, "");
|
||||
console.error(`${chalk.red("error")} ${message}\n${stack}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
exec(
|
||||
[
|
||||
"git fetch --tags", // Fetch git tags to get the previous version number (i.e. the latest tag)
|
||||
"yarn install", // Install script's dependencies before any require
|
||||
].join(" && "),
|
||||
{ cwd: path.dirname(fileURLToPath(import.meta.url)) },
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
);
|
||||
// Fetch git tags to get the previous version number (i.e. the latest tag)
|
||||
await runCommand("git fetch --tags");
|
||||
|
||||
// Install script's dependencies before any require
|
||||
await runCommand("yarn");
|
||||
|
||||
const { default: run } = await import("./run.js");
|
||||
await run();
|
||||
@@ -0,0 +1,61 @@
|
||||
import chalk from "chalk";
|
||||
import semver from "semver";
|
||||
import { outdent } from "outdent";
|
||||
import { runGit, readJson } from "./utils.js";
|
||||
import parseArguments from "./parse-arguments.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 {
|
||||
params.previousVersion = previousVersion;
|
||||
params.previousVersionOnDefaultBranch = (
|
||||
await readJson("package.json")
|
||||
).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;
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
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);
|
||||
const isPatch = semverDiff === "patch";
|
||||
@@ -26,8 +27,12 @@ export function getReleaseUrl(version, previousVersion) {
|
||||
body: `🔗 [Release note](https://prettier.io/${blogPostInfo.path})`,
|
||||
});
|
||||
}
|
||||
body = encodeURIComponent(body);
|
||||
return `https://github.com/prettier/prettier/releases/new?tag=${version}&title=${version}&body=${body}`;
|
||||
const parameters = new URLSearchParams({
|
||||
tag: version,
|
||||
title: version,
|
||||
body,
|
||||
});
|
||||
return `${RELEASE_URL_BASE}${parameters}`;
|
||||
}
|
||||
|
||||
export default async function showInstructionsAfterNpmPublish({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import chalk from "chalk";
|
||||
import outdent from "outdent";
|
||||
import fetch from "node-fetch";
|
||||
import { waitForEnter, logPromise } from "../utils.js";
|
||||
|
||||
const outdentString = outdent.string;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { URLSearchParams } from "node:url";
|
||||
import { describe, it } from "node:test";
|
||||
import { getReleaseUrl } from "../steps/show-instructions-after-npm-publish.js";
|
||||
|
||||
const RELEASE_URL_BASE = "https://github.com/prettier/prettier/releases/new?";
|
||||
const getExpectedReleaseUrl = (parameters) => {
|
||||
parameters = new URLSearchParams(parameters);
|
||||
return `${RELEASE_URL_BASE}${parameters}`;
|
||||
};
|
||||
|
||||
const getDateParts = () => [
|
||||
new Date().getFullYear(),
|
||||
String(new Date().getMonth() + 1).padStart(2, "0"),
|
||||
String(new Date().getDate()).padStart(2, "0"),
|
||||
];
|
||||
|
||||
describe("publish-to-npm", () => {
|
||||
describe("getReleaseUrl", () => {
|
||||
it("returns URL for patch releasing", () => {
|
||||
const result = getReleaseUrl("2.3.1", "2.3.0");
|
||||
assert.equal(
|
||||
result,
|
||||
getExpectedReleaseUrl({
|
||||
tag: "2.3.1",
|
||||
title: "2.3.1",
|
||||
body: "🔗 [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md#231)",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("returns URL for minor releasing", () => {
|
||||
const result = getReleaseUrl("2.4.0", "2.3.0");
|
||||
assert.equal(
|
||||
result,
|
||||
getExpectedReleaseUrl({
|
||||
tag: "2.4.0",
|
||||
title: "2.4.0",
|
||||
body: [
|
||||
"[diff](https://github.com/prettier/prettier/compare/2.3.0...2.4.0)",
|
||||
`🔗 [Release note](https://prettier.io/blog/${getDateParts().join(
|
||||
"/"
|
||||
)}/2.4.0.html)`,
|
||||
].join("\n\n"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("returns URL for major releasing", () => {
|
||||
const result = getReleaseUrl("2.3.0", "2.2.0");
|
||||
assert.equal(
|
||||
result,
|
||||
getExpectedReleaseUrl({
|
||||
tag: "2.3.0",
|
||||
title: "2.3.0",
|
||||
body: [
|
||||
"[diff](https://github.com/prettier/prettier/compare/2.2.0...2.3.0)",
|
||||
`🔗 [Release note](https://prettier.io/blog/${getDateParts().join(
|
||||
"/"
|
||||
)}/2.3.0.html)`,
|
||||
].join("\n\n"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import chalk from "chalk";
|
||||
import validateNewVersion from "../steps/validate-new-version.js";
|
||||
|
||||
describe("validate-new-version", () => {
|
||||
it("throws error for invalid semver", () => {
|
||||
assert.throws(
|
||||
() => {
|
||||
validateNewVersion({ version: "foo" });
|
||||
},
|
||||
{ message: "Invalid version specified" }
|
||||
);
|
||||
});
|
||||
it("throws error when version isn't greater than prev version", () => {
|
||||
assert.throws(
|
||||
() => {
|
||||
validateNewVersion({ version: "0.0.1", previousVersion: "0.0.2" });
|
||||
},
|
||||
{ message: `Version ${chalk.yellow("0.0.1")} has already been published` }
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { isVersionReleased } from "../steps/wait-for-bot-release.js";
|
||||
|
||||
describe("isVersionReleased", () => {
|
||||
it("returns true for existing version", async () => {
|
||||
assert.ok(await isVersionReleased("1.0.0"));
|
||||
});
|
||||
it("rejects for non-existing version", async () => {
|
||||
await assert.rejects(() => isVersionReleased("999.0.0"), {
|
||||
message: "prettier@999.0.0 doesn't exit.",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import readline from "node:readline";
|
||||
import chalk from "chalk";
|
||||
import { execa } from "execa";
|
||||
import stringWidth from "string-width";
|
||||
import fetch from "node-fetch";
|
||||
import outdent from "outdent";
|
||||
import getFormattedDate from "./get-formatted-date.js";
|
||||
|
||||
|
||||
+8
-3318
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user