diff --git a/scripts/release/__tests__/steps/publish-to-npm.spec.js b/scripts/release/__tests__/steps/publish-to-npm.spec.js index d663e0e7e..ced6df5ef 100644 --- a/scripts/release/__tests__/steps/publish-to-npm.spec.js +++ b/scripts/release/__tests__/steps/publish-to-npm.spec.js @@ -1,4 +1,4 @@ -import { getReleaseUrl } from "../../steps/publish-to-npm.js"; +import { getReleaseUrl } from "../../steps/show-instructions-after-npm-publish.js"; describe("publish-to-npm", () => { describe("getReleaseUrl", () => { diff --git a/scripts/release/__tests__/steps/version-check.spec.js b/scripts/release/__tests__/steps/version-check.spec.js new file mode 100644 index 000000000..478f3a532 --- /dev/null +++ b/scripts/release/__tests__/steps/version-check.spec.js @@ -0,0 +1,15 @@ +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."' + ); + }); +}); diff --git a/scripts/release/release.js b/scripts/release/release.js index 56bec2599..9f78aeda8 100644 --- a/scripts/release/release.js +++ b/scripts/release/release.js @@ -15,8 +15,9 @@ async function run() { const params = minimist(process.argv.slice(2), { string: ["version"], - boolean: ["dry"], + boolean: ["dry", "manual"], alias: { v: "version" }, + default: { manual: false }, }); const { stdout: previousVersion } = await runGit([ @@ -39,16 +40,21 @@ async function run() { "./steps/validate-new-version.js", "./steps/check-git-status.js", "./steps/install-dependencies.js", - "./steps/run-tests.js", + params.manual && "./steps/run-tests.js", "./steps/update-version.js", - "./steps/generate-bundles.js", + params.manual && "./steps/generate-bundles.js", "./steps/update-changelog.js", "./steps/push-to-git.js", - "./steps/publish-to-npm.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", - ].map((step) => importDefault(step)) + ] + .filter(Boolean) + .map((step) => importDefault(step)) ); try { diff --git a/scripts/release/steps/publish-to-npm.js b/scripts/release/steps/publish-to-npm.js index 59fbd90d2..775c0e439 100644 --- a/scripts/release/steps/publish-to-npm.js +++ b/scripts/release/steps/publish-to-npm.js @@ -1,16 +1,6 @@ -import chalk from "chalk"; -import outdent from "outdent"; import { execa } from "execa"; -import semver from "semver"; import enquirer from "enquirer"; -import { - getBlogPostInfo, - getChangelogContent, - logPromise, - waitForEnter, -} from "../utils.js"; - -const outdentString = outdent.string; +import { logPromise } from "../utils.js"; /** * Retry "npm publish" when to enter OTP is failed. @@ -37,53 +27,10 @@ async function retryNpmPublish() { } } -export function getReleaseUrl(version, previousVersion) { - const semverDiff = semver.diff(version, previousVersion); - const isPatch = semverDiff === "patch"; - let body; - if (isPatch) { - const urlToChangelog = - "https://github.com/prettier/prettier/blob/main/CHANGELOG.md#" + - version.split(".").join(""); - body = `🔗 [Changelog](${urlToChangelog})`; - } else { - const blogPostInfo = getBlogPostInfo(version); - body = getChangelogContent({ - 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}`; -} - -export default async function publishToNpm({ dry, version, previousVersion }) { +export default async function publishToNpm({ dry }) { if (dry) { return; } await logPromise("Publishing to npm", retryNpmPublish()); - - const releaseUrl = getReleaseUrl(version, previousVersion); - - console.log( - outdentString(chalk` - {green.bold Prettier ${version} published!} - - {yellow.bold Some manual steps are necessary.} - - {bold.underline Create a GitHub Release} - - Go to {cyan.underline ${releaseUrl}} - - Press {bgGreen.black Publish release } - - {bold.underline Test the new release} - - In a new session, run {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(); } diff --git a/scripts/release/steps/show-instructions-after-npm-publish.js b/scripts/release/steps/show-instructions-after-npm-publish.js new file mode 100644 index 000000000..5a015ca5f --- /dev/null +++ b/scripts/release/steps/show-instructions-after-npm-publish.js @@ -0,0 +1,58 @@ +import chalk from "chalk"; +import outdent from "outdent"; +import semver from "semver"; +import { + getBlogPostInfo, + getChangelogContent, + waitForEnter, +} from "../utils.js"; + +const outdentString = outdent.string; + +export function getReleaseUrl(version, previousVersion) { + const semverDiff = semver.diff(version, previousVersion); + const isPatch = semverDiff === "patch"; + let body; + if (isPatch) { + const urlToChangelog = + "https://github.com/prettier/prettier/blob/main/CHANGELOG.md#" + + version.split(".").join(""); + body = `🔗 [Changelog](${urlToChangelog})`; + } else { + const blogPostInfo = getBlogPostInfo(version); + body = getChangelogContent({ + 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}`; +} + +export default async function showInstructionsAfterNpmPublish({ + version, + previousVersion, +}) { + const releaseUrl = getReleaseUrl(version, previousVersion); + + console.log( + outdentString(chalk` + {green.bold Prettier ${version} published!} + + {yellow.bold Some manual steps are necessary.} + + {bold.underline Create a GitHub Release} + - Go to {cyan.underline ${releaseUrl}} + - Press {bgGreen.black Publish release } + + {bold.underline Test the new release} + - In a new session, run {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(); +} diff --git a/scripts/release/steps/wait-for-bot-release.js b/scripts/release/steps/wait-for-bot-release.js new file mode 100644 index 000000000..c17a97338 --- /dev/null +++ b/scripts/release/steps/wait-for-bot-release.js @@ -0,0 +1,78 @@ +import chalk from "chalk"; +import outdent from "outdent"; +import fetch from "node-fetch"; +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(); + const versionExists = version in result.time; + + if (!versionExists) { + throw new Error(`prettier@${version} doesn't exit.`); + } + + return versionExists; +} + +async function checkBotPermission() { + const response = await fetch("https://registry.npmjs.org/prettier/"); + const { maintainers } = await response.json(); + + return maintainers.some(({ name }) => name === "prettier-bot"); +} + +checkBotPermission(); + +const sleep = () => + new Promise((resolve) => { + setTimeout(resolve, 30_000); + }); + +export default async function waitForBotRelease({ dry, version }) { + if (dry) { + return; + } + + 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. + `) + ); + + 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. + `) + ); + + await waitForEnter(); + + let released = false; + while (!released) { + try { + released = await logPromise( + "Checking release status", + isVersionReleased(version) + ); + } catch { + // No op + } + + if (!released) { + await logPromise("Wait for 30s", sleep()); + } + } +}