From 15b112b9752d107db5477b3b629cd8919cde4cb7 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Sat, 12 Jun 2021 16:10:26 +0900 Subject: [PATCH] Add changelog-for-patch script - Add ./scripts/changelog-for-patch.mjs - Extract common functions to ./scripts/utils/changelog - Use changelog-for-patch.mjs from release script --- scripts/changelog-for-patch.mjs | 44 ++++++++++ scripts/draft-blog-post.mjs | 97 +++++------------------ scripts/release/steps/update-changelog.js | 40 ++++++---- scripts/utils/changelog.mjs | 91 +++++++++++++++++++++ 4 files changed, 181 insertions(+), 91 deletions(-) create mode 100644 scripts/changelog-for-patch.mjs create mode 100644 scripts/utils/changelog.mjs diff --git a/scripts/changelog-for-patch.mjs b/scripts/changelog-for-patch.mjs new file mode 100644 index 000000000000..8b59409906ab --- /dev/null +++ b/scripts/changelog-for-patch.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +import path from "node:path"; +import minimist from "minimist"; +import semver from "semver"; +import { + changelogUnreleasedDirPath, + changelogUnreleasedDirs, + getEntries, + printEntries, + replaceVersions, +} from "./utils/changelog.mjs"; + +const { previousVersion, newVersion } = parseArgv(); + +const entries = changelogUnreleasedDirs.flatMap((dir) => { + const dirPath = path.join(changelogUnreleasedDirPath, dir.name); + return getEntries(dirPath); +}); + +console.log( + replaceVersions( + printEntries(entries).join("\n\n"), + previousVersion, + newVersion, + /** isPatch */ true + ) +); + +function parseArgv() { + const argv = minimist(process.argv.slice(2)); + const previousVersion = argv["prev-version"]; + const newVersion = argv["new-version"]; + if ( + !previousVersion || + !newVersion || + semver.compare(previousVersion, newVersion) !== -1 + ) { + throw new Error( + `Invalid argv, prev-version: ${previousVersion}, new-version: ${newVersion}` + ); + } + return { previousVersion, newVersion }; +} diff --git a/scripts/draft-blog-post.mjs b/scripts/draft-blog-post.mjs index 2bc1a389c916..b945210c8703 100644 --- a/scripts/draft-blog-post.mjs +++ b/scripts/draft-blog-post.mjs @@ -3,17 +3,22 @@ import fs from "node:fs"; import path from "node:path"; import rimraf from "rimraf"; -import semver from "semver"; import createEsmUtils from "esm-utils"; +import { + getEntries, + replaceVersions, + changelogUnreleasedDirPath, + changelogUnreleasedDirs, + printEntries, +} from "./utils/changelog.mjs"; const { __dirname, require } = createEsmUtils(import.meta); -const changelogUnreleasedDir = path.join(__dirname, "../changelog_unreleased"); const blogDir = path.join(__dirname, "../website/blog"); const introTemplateFile = path.join( - changelogUnreleasedDir, + changelogUnreleasedDirPath, "BLOG_POST_INTRO_TEMPLATE.md" ); -const introFile = path.join(changelogUnreleasedDir, "blog-post-intro.md"); +const introFile = path.join(changelogUnreleasedDirPath, "blog-post-intro.md"); if (!fs.existsSync(introFile)) { fs.copyFileSync(introTemplateFile, introFile); } @@ -50,46 +55,15 @@ const categoriesByDir = new Map( categories.map((category) => [category.dir, category]) ); -const dirs = fs - .readdirSync(changelogUnreleasedDir, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()); - -for (const dir of dirs) { - const dirPath = path.join(changelogUnreleasedDir, dir.name); +for (const dir of changelogUnreleasedDirs) { + const dirPath = path.join(changelogUnreleasedDirPath, dir.name); const category = categoriesByDir.get(dir.name); if (!category) { throw new Error("Unknown category: " + dir.name); } - category.entries = fs - .readdirSync(dirPath) - .filter((fileName) => /^\d+\.md$/.test(fileName)) - .map((fileName) => { - const [title, ...rest] = fs - .readFileSync(path.join(dirPath, fileName), "utf8") - .trim() - .split("\n"); - - const improvement = title.match(/\[IMPROVEMENT(:(\d+))?]/); - - const section = title.includes("[HIGHLIGHT]") - ? "highlight" - : title.includes("[BREAKING]") - ? "breaking" - : improvement - ? "improvement" - : undefined; - - const order = - section === "improvement" && improvement[2] !== undefined - ? Number(improvement[2]) - : undefined; - - const content = [processTitle(title), ...rest].join("\n"); - - return { fileName, section, order, content }; - }); + category.entries = getEntries(dirPath); } rimraf.sync(postGlob); @@ -100,54 +74,35 @@ fs.writeFileSync( [ fs.readFileSync(introFile, "utf8").trim(), "", - ...printEntries({ + ...printEntriesWithTitle({ title: "Highlights", filter: (entry) => entry.section === "highlight", }), - ...printEntries({ + ...printEntriesWithTitle({ title: "Breaking Changes", filter: (entry) => entry.section === "breaking", }), - ...printEntries({ + ...printEntriesWithTitle({ title: "Formatting Improvements", filter: (entry) => entry.section === "improvement", }), - ...printEntries({ + ...printEntriesWithTitle({ title: "Other Changes", filter: (entry) => !entry.section, }), - ].join("\n\n") + "\n" + ].join("\n\n") + "\n", + previousVersion, + version ) ); -function processTitle(title) { - return title - .replace(/\[(BREAKING|HIGHLIGHT|IMPROVEMENT(:\d+)?)]/g, "") - .replace(/\s+/g, " ") - .replace(/^#{4} [a-z]/, (s) => s.toUpperCase()) - .replace(/(? 0) { - filteredEntries.sort((a, b) => { - if (a.order !== undefined) { - return b.order === undefined ? 1 : a.order - b.order; - } - return a.fileName.localeCompare(b.fileName, "en", { numeric: true }); - }); - result.push( - "### " + title, - ...filteredEntries.map((entry) => entry.content) - ); + result.push("###" + title, ...printEntries(filteredEntries)); } } @@ -157,13 +112,3 @@ function printEntries({ title, filter }) { return result; } - -function formatVersion(version) { - return `${semver.major(version)}.${semver.minor(version)}`; -} - -function replaceVersions(data) { - return data - .replace(/prettier stable/gi, `Prettier ${formatVersion(previousVersion)}`) - .replace(/prettier main/gi, `Prettier ${formatVersion(version)}`); -} diff --git a/scripts/release/steps/update-changelog.js b/scripts/release/steps/update-changelog.js index 3f8adf19524f..fa9ba1849edd 100644 --- a/scripts/release/steps/update-changelog.js +++ b/scripts/release/steps/update-changelog.js @@ -1,6 +1,7 @@ "use strict"; const fs = require("fs"); +const execa = require("execa"); const chalk = require("chalk"); const { outdent, string: outdentString } = require("outdent"); const semver = require("semver"); @@ -18,18 +19,29 @@ function getBlogPostInfo(version) { }; } -function writeChangelog({ version, previousVersion, releaseNotes }) { +function writeChangelog({ version, previousVersion, body }) { const changelog = fs.readFileSync("CHANGELOG.md", "utf-8"); const newEntry = outdent` # ${version} [diff](https://github.com/prettier/prettier/compare/${previousVersion}...${version}) - ${releaseNotes} + ${body} `; fs.writeFileSync("CHANGELOG.md", newEntry + "\n\n" + changelog); } +async function getChangelogForPatch({ version, previousVersion }) { + const { stdout: changelog } = await execa("node", [ + "scripts/changelog-for-patch.mjs", + "--prev-version", + previousVersion, + "--new-version", + version, + ]); + return changelog; +} + module.exports = async function ({ version, previousVersion }) { const semverDiff = semver.diff(version, previousVersion); @@ -38,7 +50,7 @@ module.exports = async function ({ version, previousVersion }) { writeChangelog({ version, previousVersion, - releaseNotes: `🔗 [Release Notes](https://prettier.io/${blogPost.path})`, + body: `🔗 [Release Notes](https://prettier.io/${blogPost.path})`, }); if (fs.existsSync(blogPost.file)) { // Everything is fine, this step is finished @@ -52,18 +64,16 @@ module.exports = async function ({ version, previousVersion }) { `) ); } else { - console.log( - outdentString(chalk` - {yellow.bold A manual step is necessary.} - - You can copy the entries from {bold changelog_unreleased/*/*.md} to {bold CHANGELOG.md} - and update it accordingly. - - You don't need to commit the file, the script will take care of that. - - When you're finished, press ENTER to continue. - `) - ); + const body = await getChangelogForPatch({ + version, + previousVersion, + }); + writeChangelog({ + version, + previousVersion, + body, + }); + console.log("Press ENTER to continue."); } await waitForEnter(); diff --git a/scripts/utils/changelog.mjs b/scripts/utils/changelog.mjs new file mode 100644 index 000000000000..92fdcb87a700 --- /dev/null +++ b/scripts/utils/changelog.mjs @@ -0,0 +1,91 @@ +import fs from "node:fs"; +import path from "node:path"; +import createEsmUtils from "esm-utils"; +import semver from "semver"; + +const { __dirname } = createEsmUtils(import.meta); + +export const changelogUnreleasedDirPath = path.join( + __dirname, + "../../changelog_unreleased" +); + +export const changelogUnreleasedDirs = fs + .readdirSync(changelogUnreleasedDirPath, { + withFileTypes: true, + }) + .filter((entry) => entry.isDirectory()); + +export function getEntries(dirPath) { + const fileNames = fs + .readdirSync(dirPath) + .filter((fileName) => path.extname(fileName) === ".md"); + const entries = fileNames.map((fileName) => { + const [title, ...rest] = fs + .readFileSync(path.join(dirPath, fileName), "utf8") + .trim() + .split("\n"); + + const improvement = title.match(/\[IMPROVEMENT(:(\d+))?]/); + + const section = title.includes("[HIGHLIGHT]") + ? "highlight" + : title.includes("[BREAKING]") + ? "breaking" + : improvement + ? "improvement" + : undefined; + + const order = + section === "improvement" && improvement[2] !== undefined + ? Number(improvement[2]) + : undefined; + + const content = [processTitle(title), ...rest].join("\n"); + + return { fileName, section, order, content }; + }); + return entries; +} + +export function printEntries(entries) { + const result = []; + if (entries.length > 0) { + entries.sort((a, b) => { + if (a.order !== undefined) { + return b.order === undefined ? 1 : a.order - b.order; + } + return a.fileName.localeCompare(b.fileName, "en", { numeric: true }); + }); + result.push(...entries.map((entry) => entry.content)); + } + return result; +} + +export function replaceVersions(data, prevVer, newVer, isPatch = false) { + return data + .replace( + /prettier stable/gi, + `Prettier ${isPatch ? prevVer : formatVersion(prevVer)}` + ) + .replace( + /prettier main/gi, + `Prettier ${isPatch ? newVer : formatVersion(newVer)}` + ); +} + +function formatVersion(version) { + return `${semver.major(version)}.${semver.minor(version)}`; +} + +function processTitle(title) { + return title + .replace(/\[(BREAKING|HIGHLIGHT|IMPROVEMENT(:\d+)?)]/g, "") + .replace(/\s+/g, " ") + .replace(/^#{4} [a-z]/, (s) => s.toUpperCase()) + .replace(/(?