diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbcfe1d15..ba6b4f392 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,7 +66,7 @@ To debug Prettier locally, you can either debug it in Node (recommended) or the The project uses ESLint for linting and Prettier for formatting. If your editor isn't set up to work with them, you can lint and format all files from the command line using `yarn fix`. -After opening a PR, describe your changes in a file in the `changelog_unreleased` directory following the template [`changelog_unreleased/TEMPLATE.md`](changelog_unreleased/TEMPLATE.md) and commit this file to your PR. +After opening a PR, describe your changes in a file in the `changelog_unreleased` directory following the template [`changelog_unreleased/TEMPLATE.md`](changelog_unreleased/TEMPLATE.md) and commit this file to your PR. You can use `./scripts/generate-changelog.mjs` to create changelog file. Please see comments of the script file for usage. Take a look at [`commands.md`](commands.md) and, if you know Haskell, check out [Wadler's paper](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) to understand how Prettier works. diff --git a/package.json b/package.json index 451bd85d6..669368844 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "core-js": "3.16.0", "cross-env": "7.0.3", "cspell": "5.6.6", + "enquirer": "2.3.6", "eslint": "7.32.0", "eslint-config-prettier": "8.3.0", "eslint-formatter-friendly": "7.0.0", @@ -121,6 +122,7 @@ "jest-snapshot-serializer-raw": "1.2.0", "jest-watch-typeahead": "0.6.4", "node-actionlint": "1.1.0", + "node-fetch": "2.6.1", "npm-run-all": "4.1.5", "path-browserify": "1.0.1", "prettier": "2.3.2", diff --git a/scripts/generate-changelog.mjs b/scripts/generate-changelog.mjs new file mode 100644 index 000000000..62cb66ef7 --- /dev/null +++ b/scripts/generate-changelog.mjs @@ -0,0 +1,191 @@ +#!/usr/bin/env node + +/** + * When you run the script, enter the number and category of the Pull Request at the prompt. + * Get the PR title and author name via the GitHub API and create a file in ./changelog_unreleased + * + * $ node ./scripts/generate-changelog.mjs + * ✔ Input your Pull Request number: · 10961 + * ✔ Input category of your Pull Request: · typescript + */ + +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"; + +const { __dirname } = createEsmUtils(import.meta); + +(async () => { + const prNumberPrompt = new enquirer.NumberPrompt({ + message: "Input your Pull Request number:", + }); + const prNumber = await prNumberPrompt.run(); + + const categoryPrompt = new enquirer.AutoComplete({ + message: "Input category of your Pull Request:", + limit: CHANGELOG_CATEGORIES.length, + // The array passed to `choices` will be broken, so copy it. + choices: [...CHANGELOG_CATEGORIES], + }); + const category = (await categoryPrompt.run()).trim(); + + if (!prNumber || !category) { + throw new Error("Two args are required."); + } + assertCategory(category); + + const { title, user } = await getPr(prNumber); + + const newChangelog = await createChangelog(title, user, prNumber, category); + + await addNewChangelog(prNumber, category, newChangelog); + + console.log("Done"); +})(); + +/** + * @param {number} prNumber + * @returns {Promise<{ title: string; user: string }>} + */ +async function getPr(prNumber) { + // https://docs.github.com/en/rest/reference/pulls#get-a-pull-request + const url = `https://api.github.com/repos/prettier/prettier/pulls/${prNumber}`; + const response = await fetch(url, { + Headers: { + "Content-Type": "application/json", + Accept: "application/vnd.github.v3+json", + }, + }); + if (!response.ok) { + if (response.status === 404) { + throw new Error(`Pull Request #${prNumber} not found.`); + } + throw new Error(response); + } + const { title, user } = await response.json(); + return { + title, + user: user.login, + }; +} + +/** + * @param {number} prNumber + * @param {string} category + * @param {string} newChangelog + * @returns {Promise} + */ +async function addNewChangelog(prNumber, category, newChangelog) { + const newChangelogPath = path.resolve( + __dirname, + `../changelog_unreleased/${category}/${prNumber}.md` + ); + await fs.writeFile(newChangelogPath, newChangelog); +} + +/** + * @param {string} title + * @param {string} user + * @param {number} prNumber + * @param {string} string + * @returns {string} + */ +async function createChangelog(title, user, prNumber, category) { + const changelogTemplatePath = path.resolve( + __dirname, + "../changelog_unreleased/TEMPLATE.md" + ); + const changelogTemplate = await fs.readFile(changelogTemplatePath, "utf-8"); + + const titlePart = "Title"; + const prNumberPart = "#XXXX"; + const userPart = "@user"; + const codeBlockPart = "```jsx\n"; + const inputCommentPart = "// Input\n"; + const stableCommentPart = "// Prettier stable\n"; + const mainCommentPart = "// Prettier main\n"; + + const syntax = getSyntaxFromCategory(category); + + return changelogTemplate + .replace(titlePart, title) + .replace(prNumberPart, `#${prNumber}`) + .replace(userPart, `@${user}`) + .replace(codeBlockPart, `\`\`\`${syntax}\n`) + .replace(inputCommentPart, getCommentForSyntax(syntax, "Input") + "\n") + .replace( + stableCommentPart, + getCommentForSyntax(syntax, "Prettier stable") + "\n" + ) + .replace( + mainCommentPart, + getCommentForSyntax(syntax, "Prettier main") + "\n" + ); +} + +/** + * @param {string} category + * @returns {string} + */ +function getSyntaxFromCategory(category) { + switch (category) { + case "angular": + case "html": + case "lwc": + return "html"; + case "cli": + return "sh"; + case "graphql": + return "gql"; + case "handlebars": + return "hbs"; + case "json": + return "jsonc"; + case "markdown": + return "md"; + case "mdx": + return "mdx"; + case "flow": + case "javascript": + case "api": + return "jsx"; + case "typescript": + return "tsx"; + default: + return category; + } +} + +/** + * @param {string} syntax + * @param {string} comment + * @returns {string} + */ +function getCommentForSyntax(syntax, comment) { + switch (syntax) { + case "md": + case "mdx": + case "html": + return ``; + case "sh": + case "gql": + return `# ${comment}`; + case "hbs": + return `{{! ${comment} }}`; + default: + return `// ${comment}`; + } +} + +/** + * @param {unknown} + * @returns {void} + */ +function assertCategory(category) { + if (!CHANGELOG_CATEGORIES.includes(category)) { + throw new Error(`${category} is invalid category`); + } +} diff --git a/scripts/lint-changelog.mjs b/scripts/lint-changelog.mjs index 71fa017cb..b715e561d 100644 --- a/scripts/lint-changelog.mjs +++ b/scripts/lint-changelog.mjs @@ -4,32 +4,13 @@ import path from "node:path"; import fs from "node:fs"; import { outdent } from "outdent"; import createEsmUtils from "esm-utils"; +import { CHANGELOG_CATEGORIES } from "./utils/changelog-categories.mjs"; const { __dirname } = createEsmUtils(import.meta); const CHANGELOG_DIR = "changelog_unreleased"; const TEMPLATE_FILE = "TEMPLATE.md"; const BLOG_POST_INTRO_TEMPLATE_FILE = "BLOG_POST_INTRO_TEMPLATE.md"; const BLOG_POST_INTRO_FILE = "blog-post-intro.md"; -const CHANGELOG_CATEGORIES = [ - "angular", - "api", - "cli", - "css", - "flow", - "graphql", - "handlebars", - "html", - "javascript", - "json", - "less", - "lwc", - "markdown", - "mdx", - "scss", - "typescript", - "vue", - "yaml", -]; const CHANGELOG_ROOT = path.join(__dirname, `../${CHANGELOG_DIR}`); const showErrorMessage = (message) => { console.error(message); diff --git a/scripts/utils/changelog-categories.mjs b/scripts/utils/changelog-categories.mjs new file mode 100644 index 000000000..6bd6fccc3 --- /dev/null +++ b/scripts/utils/changelog-categories.mjs @@ -0,0 +1,20 @@ +export const CHANGELOG_CATEGORIES = [ + "angular", + "api", + "cli", + "css", + "flow", + "graphql", + "handlebars", + "html", + "javascript", + "json", + "less", + "lwc", + "markdown", + "mdx", + "scss", + "typescript", + "vue", + "yaml", +]; diff --git a/yarn.lock b/yarn.lock index c0db85efa..ddc8797b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2865,7 +2865,7 @@ enhanced-resolve@^5.8.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: +enquirer@2.3.6, enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -4969,6 +4969,11 @@ node-actionlint@1.1.0: chalk "^4.1.1" fast-glob "^3.2.7" +node-fetch@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"