|
1 | 1 | /** |
2 | 2 | * This script is only used to help write release announcements. |
3 | 3 | */ |
4 | | -// @ts-expect-error Could not find a declaration file for module |
5 | | -import { generateNotes } from "@semantic-release/release-notes-generator"; |
6 | 4 | import { spawn } from "node:child_process"; |
7 | 5 | import * as path from "node:path"; |
8 | | -import { URL, fileURLToPath } from "node:url"; |
9 | | -import { readJSONFile } from "../helpers.js"; |
10 | | -import type { Manifest } from "../types.js"; |
11 | | - |
12 | | -function reformat( |
13 | | - output: string, |
14 | | - lastRelease: string, |
15 | | - nextRelease: string |
16 | | -): string { |
17 | | - const replacements: [RegExp, string][] = [ |
18 | | - [/^# .*/m, `📣 react-native-test-app ${nextRelease}`], |
19 | | - [/^### .*/m, `Other fixes since ${lastRelease}:`], |
20 | | - [/^\* \*\*android:\*\*/gm, "* **Android:**"], |
21 | | - [/^\* \*\*apple:\*\*/gm, "* **Apple:**"], |
22 | | - [/^\* \*\*ios:\*\*/gm, "* **iOS:**"], |
23 | | - [/^\* \*\*macos:\*\*/gm, "* **macOS:**"], |
24 | | - [/^\* \*\*visionos:\*\*/gm, "* **visionOS:**"], |
25 | | - [/^\* \*\*windows:\*\*/gm, "* **Windows:**"], |
26 | | - [/\s*\(\[#\d+\]\(https:\/\/github.com.*/gm, ""], |
27 | | - ]; |
28 | | - return replacements |
29 | | - .reduce( |
30 | | - (output, [search, replace]) => output.replace(search, replace), |
31 | | - output |
32 | | - ) |
33 | | - .trim(); |
| 6 | +import { fileURLToPath } from "node:url"; |
| 7 | + |
| 8 | +type Group = |
| 9 | + | "general" |
| 10 | + | "android" |
| 11 | + | "apple" |
| 12 | + | "ios" |
| 13 | + | "macos" |
| 14 | + | "visionos" |
| 15 | + | "windows"; |
| 16 | + |
| 17 | +type Changes = Record<string, Record<Group, string[]>>; |
| 18 | + |
| 19 | +function assertCategory(category: string): asserts category is "feat" | "fix" { |
| 20 | + if (category !== "feat" && category !== "fix") { |
| 21 | + throw new Error(`Unknown category: ${category}`); |
| 22 | + } |
34 | 23 | } |
35 | 24 |
|
36 | | -function repositoryUrl() { |
37 | | - const p = fileURLToPath(new URL("../../package.json", import.meta.url)); |
38 | | - const manifest = readJSONFile<Manifest>(p); |
39 | | - return manifest.repository?.url; |
| 25 | +function capitalize(s: string): string { |
| 26 | + return String(s[0]).toUpperCase() + s.substring(1); |
| 27 | +} |
| 28 | + |
| 29 | +function sanitizeGroup(group: string): Group { |
| 30 | + switch (group) { |
| 31 | + case "android": |
| 32 | + case "apple": |
| 33 | + case "ios": |
| 34 | + case "macos": |
| 35 | + case "visionos": |
| 36 | + case "windows": |
| 37 | + return group; |
| 38 | + default: |
| 39 | + return "general"; |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +function parseCommits(commits: { message: string }[]): Changes { |
| 44 | + const changes: Changes = { |
| 45 | + feat: { |
| 46 | + general: [], |
| 47 | + android: [], |
| 48 | + apple: [], |
| 49 | + ios: [], |
| 50 | + macos: [], |
| 51 | + visionos: [], |
| 52 | + windows: [], |
| 53 | + }, |
| 54 | + fix: { |
| 55 | + general: [], |
| 56 | + android: [], |
| 57 | + apple: [], |
| 58 | + ios: [], |
| 59 | + macos: [], |
| 60 | + visionos: [], |
| 61 | + windows: [], |
| 62 | + }, |
| 63 | + }; |
| 64 | + |
| 65 | + for (const { message } of commits) { |
| 66 | + const m = message.match(/^(feat|fix)(?:\((.*?)\))?: (.*)$/); |
| 67 | + if (m) { |
| 68 | + const [, cat, group, message] = m; |
| 69 | + assertCategory(cat); |
| 70 | + changes[cat][sanitizeGroup(group)].push(message); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + return changes; |
| 75 | +} |
| 76 | + |
| 77 | +function renderGroup(group: string): string { |
| 78 | + switch (group) { |
| 79 | + case "android": |
| 80 | + return "**Android:** "; |
| 81 | + case "apple": |
| 82 | + return "**Apple:** "; |
| 83 | + case "ios": |
| 84 | + return "**iOS:**"; |
| 85 | + case "macos": |
| 86 | + return "**macOS:**"; |
| 87 | + case "visionos": |
| 88 | + return "**visionOS:**"; |
| 89 | + case "windows": |
| 90 | + return "**Windows:**"; |
| 91 | + default: |
| 92 | + return ""; |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +function renderCategory( |
| 97 | + header: string, |
| 98 | + changes: Changes[string], |
| 99 | + output: string[] |
| 100 | +): string[] { |
| 101 | + const groups = Object.entries(changes); |
| 102 | + if (groups.length > 0) { |
| 103 | + output.push("", header, ""); |
| 104 | + for (const [group, entries] of groups) { |
| 105 | + for (const entry of entries) { |
| 106 | + output.push(`- ${renderGroup(group)}${capitalize(entry)}`); |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + return output; |
40 | 111 | } |
41 | 112 |
|
42 | | -/** |
43 | | - * @param {string} lastRelease |
44 | | - * @param {string} nextRelease |
45 | | - */ |
46 | 113 | function main(lastRelease: string, nextRelease: string): void { |
47 | 114 | const args = [ |
48 | 115 | "log", |
@@ -73,20 +140,13 @@ function main(lastRelease: string, nextRelease: string): void { |
73 | 140 | return; |
74 | 141 | } |
75 | 142 |
|
76 | | - const context = { |
77 | | - commits, |
78 | | - lastRelease: { gitTag: lastRelease }, |
79 | | - nextRelease: { gitTag: nextRelease }, |
80 | | - options: { |
81 | | - repositoryUrl: repositoryUrl(), |
82 | | - }, |
83 | | - cwd: process.cwd(), |
84 | | - }; |
85 | | - |
86 | | - const releaseNotes: Promise<string> = generateNotes({}, context); |
87 | | - releaseNotes |
88 | | - .then((output) => reformat(output, lastRelease, nextRelease)) |
89 | | - .then((output) => console.log(output)); |
| 143 | + const { feat, fix } = parseCommits(commits); |
| 144 | + |
| 145 | + const lines = [`📣 react-native-test-app ${nextRelease}`]; |
| 146 | + renderCategory("New features:", feat, lines); |
| 147 | + renderCategory(`Fixes since ${lastRelease}:`, fix, lines); |
| 148 | + |
| 149 | + console.log(lines.join("\n")); |
90 | 150 | }); |
91 | 151 | } |
92 | 152 |
|
|
0 commit comments