#!/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.js * ✔ Input your Pull Request number: 10961 * ✔ Input category of your Pull Request: typescript */ import fs from "node:fs/promises"; import enquirer from "enquirer"; import openEditor from "open-editor"; import { CHANGELOG_CATEGORIES } from "./utilities/changelog-categories.js"; 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 content = await createChangelog(title, user, prNumber, category); const file = await addNewChangelog(prNumber, category, content); const relativePath = file.href.slice( new URL("../", import.meta.url).href.length, ); console.log("Generated changelog file: " + relativePath); const shouldOpenChangelog = await new enquirer.Confirm({ message: "Open changelog file?", initial: true, }).run(); if (shouldOpenChangelog) { try { openEditor([file]); } catch (error) { console.log(error); } } /** * @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} content * @returns {Promise} */ async function addNewChangelog(prNumber, category, content) { const file = new URL( `../changelog_unreleased/${category}/${prNumber}.md`, import.meta.url, ); await fs.writeFile(file, content); return file; } /** * @param {string} title * @param {string} user * @param {number} prNumber * @param {string} string * @returns {Promise} */ async function createChangelog(title, user, prNumber, category) { const templateFile = new URL( "../changelog_unreleased/TEMPLATE.md", import.meta.url, ); const changelogTemplate = await fs.readFile(templateFile, "utf8"); 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, generateComment(syntax, "Input") + "\n") .replace( stableCommentPart, generateComment(syntax, "Prettier stable") + "\n", ) .replace(mainCommentPart, generateComment(syntax, "Prettier main") + "\n"); } /** * @param {string} category * @returns {string} */ function getSyntaxFromCategory(category) { switch (category) { case "angular": case "html": case "lwc": case "mjml": 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": case "misc": return "jsx"; case "typescript": return "tsx"; case "css": case "scss": case "less": return "css"; default: return category; } } /** * @param {string} syntax * @param {string} comment * @returns {string} */ function generateComment(syntax, comment) { switch (syntax) { case "md": case "mdx": case "html": return ``; case "sh": case "gql": case "yaml": return `# ${comment}`; case "hbs": return `{{! ${comment} }}`; case "css": return `/* ${comment} */`; default: return `// ${comment}`; } } /** * @param {unknown} * @returns {void} */ function assertCategory(category) { if (!CHANGELOG_CATEGORIES.includes(category)) { throw new Error(`${category} is invalid category`); } }