|
8 | 8 | readVersionFromPackageJson, readVersionFromPyprojectToml, |
9 | 9 | removeIgnoredFiles, getGithubTokens, getGiteaTokens, |
10 | 10 | getRepoInfo, writeResult, createForgeRelease, |
| 11 | + readChangelogEntry, updateChangelogHeadingDate, |
11 | 12 | type RepoInfo, |
12 | 13 | } from "./index.ts"; |
13 | 14 | import {exec, tomlGetString, SubprocessError} from "./utils.ts"; |
@@ -374,6 +375,71 @@ snapshots: |
374 | 375 | expect(await readFile(join(tmpDir, "pnpm-lock.yaml"), "utf8")).toEqual(lockContent); |
375 | 376 | })); |
376 | 377 |
|
| 378 | +test("readChangelogEntry", () => { |
| 379 | + const md = `# Changelog |
| 380 | +
|
| 381 | +## [Unreleased] |
| 382 | +
|
| 383 | +## [1.2.3] - 2024-01-15 |
| 384 | +### Added |
| 385 | +- new thing |
| 386 | +
|
| 387 | +### Fixed |
| 388 | +- broken thing |
| 389 | +
|
| 390 | +## 1.2.2 (2024-01-01) |
| 391 | +- prior |
| 392 | +
|
| 393 | +## v1.2.1 |
| 394 | +old |
| 395 | +`; |
| 396 | + expect(readChangelogEntry(md, "1.2.3")).toEqual("### Added\n- new thing\n\n### Fixed\n- broken thing"); |
| 397 | + expect(readChangelogEntry(md, "1.2.2")).toEqual("- prior"); |
| 398 | + expect(readChangelogEntry(md, "1.2.1")).toEqual("old"); |
| 399 | + expect(readChangelogEntry(md, "v1.2.3")).toEqual("### Added\n- new thing\n\n### Fixed\n- broken thing"); |
| 400 | + expect(readChangelogEntry(md, "9.9.9")).toBeNull(); |
| 401 | + |
| 402 | + expect(readChangelogEntry("# 1.0.0\n\nbody\n", "1.0.0")).toEqual("body"); |
| 403 | + expect(readChangelogEntry("# 1.0.0\n\nbody\n", "1.0.10")).toBeNull(); |
| 404 | + expect(readChangelogEntry("## 1.0.0\n## 1.0.1\nb\n", "1.0.0")).toBeNull(); |
| 405 | + expect(readChangelogEntry("## 1.0.0-rc.1\n\nrc\n## 1.0.0\n\nrelease\n", "1.0.0")).toEqual("release"); |
| 406 | + expect(readChangelogEntry("## 1.0.0\n\nrelease\n## 1.0.0-rc.1\n\nrc\n", "1.0.0-rc.1")).toEqual("rc"); |
| 407 | + expect(readChangelogEntry("# Changelog\n\n## 1.0.0\nbody\n", "1.0.0")).toEqual("body"); |
| 408 | + expect(readChangelogEntry("", "1.0.0")).toBeNull(); |
| 409 | +}); |
| 410 | + |
| 411 | +test("updateChangelogHeadingDate", () => { |
| 412 | + const today = "2026-04-30"; |
| 413 | + |
| 414 | + expect(updateChangelogHeadingDate("## 1.2.3\n\nbody\n", "1.2.3", today)) |
| 415 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 416 | + |
| 417 | + expect(updateChangelogHeadingDate("## [1.2.3]\n\nbody\n", "1.2.3", today)) |
| 418 | + .toEqual("## [1.2.3] - 2026-04-30\n\nbody\n"); |
| 419 | + |
| 420 | + expect(updateChangelogHeadingDate("## 1.2.3 - YYYY-MM-DD\n\nbody\n", "1.2.3", today)) |
| 421 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 422 | + |
| 423 | + expect(updateChangelogHeadingDate("## [1.2.3] (yyyy-mm-dd)\n\nbody\n", "1.2.3", today)) |
| 424 | + .toEqual("## [1.2.3] (2026-04-30)\n\nbody\n"); |
| 425 | + |
| 426 | + expect(updateChangelogHeadingDate("## 1.2.3 - xxxx-xx-xx\n\nbody\n", "1.2.3", today)) |
| 427 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 428 | + |
| 429 | + expect(updateChangelogHeadingDate("## 1.2.3 - XXXX-XX-XX\n\nbody\n", "1.2.3", today)) |
| 430 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 431 | + |
| 432 | + expect(updateChangelogHeadingDate("## 1.2.3 - DD-MM-YYYY\n\nbody\n", "1.2.3", today)) |
| 433 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 434 | + |
| 435 | + expect(updateChangelogHeadingDate("## 1.2.3 - ????-??-??\n\nbody\n", "1.2.3", today)) |
| 436 | + .toEqual("## 1.2.3 - 2026-04-30\n\nbody\n"); |
| 437 | + |
| 438 | + expect(updateChangelogHeadingDate("## [1.2.3] - 2024-01-15\n\nbody\n", "1.2.3", today)).toBeNull(); |
| 439 | + |
| 440 | + expect(updateChangelogHeadingDate("## 1.0.0\nbody\n", "9.9.9", today)).toBeNull(); |
| 441 | +}); |
| 442 | + |
377 | 443 | test("createForgeRelease github success", async () => { |
378 | 444 | const mock = vi.fn(() => Promise.resolve(Response.json({html_url: "https://github.com/o/r/releases/tag/1.0.1"}, {status: 201}))); |
379 | 445 | stubGlobal("fetch", mock); |
@@ -1042,6 +1108,62 @@ test("incrementSemver unknown level throws", () => { |
1042 | 1108 | expect(() => incrementSemver("1.0.0", "unknown")).toThrow("Invalid semver level"); |
1043 | 1109 | }); |
1044 | 1110 |
|
| 1111 | +test("CHANGELOG.md drives commit body and gets dated heading", () => withTmpDir(async (tmpDir) => { |
| 1112 | + await writeFile(join(tmpDir, "package.json"), JSON.stringify({name: "test-pkg", version: "1.0.0"}, null, 2)); |
| 1113 | + await writeFile(join(tmpDir, "CHANGELOG.md"), `# Changelog\n\n## [1.0.1]\n- Fixed thing X\n- Added thing Y\n\n## 1.0.0\nold stuff\n`); |
| 1114 | + |
| 1115 | + const {env} = await setupReleaseRepo(tmpDir); |
| 1116 | + const opts = {cwd: tmpDir, env: {...process.env, ...env}}; |
| 1117 | + |
| 1118 | + await exec("node", [distPath, "--no-push", "patch", "package.json"], opts); |
| 1119 | + |
| 1120 | + const today = new Date().toISOString().substring(0, 10); |
| 1121 | + const changelogAfter = await readFile(join(tmpDir, "CHANGELOG.md"), "utf8"); |
| 1122 | + expect(changelogAfter).toContain(`## [1.0.1] - ${today}`); |
| 1123 | + |
| 1124 | + const {stdout: msg} = await exec("git", ["log", "-1", "--pretty=%B"], opts); |
| 1125 | + expect(msg).toContain("- Fixed thing X"); |
| 1126 | + expect(msg).toContain("- Added thing Y"); |
| 1127 | + // git log fallback (commit subjects) must not leak in |
| 1128 | + expect(msg).not.toContain("Initial commit"); |
| 1129 | +})); |
| 1130 | + |
| 1131 | +test("CHANGELOG.md without entry falls back to git log", () => withTmpDir(async (tmpDir) => { |
| 1132 | + await writeFile(join(tmpDir, "package.json"), JSON.stringify({name: "test-pkg", version: "1.0.0"}, null, 2)); |
| 1133 | + await writeFile(join(tmpDir, "CHANGELOG.md"), `# Changelog\n\n## 1.0.0\nold\n`); |
| 1134 | + |
| 1135 | + const {env} = await setupReleaseRepo(tmpDir); |
| 1136 | + const opts = {cwd: tmpDir, env: {...process.env, ...env}}; |
| 1137 | + |
| 1138 | + // commit between the tag and HEAD so the git log fallback has something to report |
| 1139 | + await writeFile(join(tmpDir, "package.json"), JSON.stringify({name: "test-pkg", version: "1.0.0", changed: true}, null, 2)); |
| 1140 | + await exec("git", ["commit", "-am", "tweak something"], opts); |
| 1141 | + |
| 1142 | + await exec("node", [distPath, "--no-push", "patch", "package.json"], opts); |
| 1143 | + |
| 1144 | + // unchanged because no entry for 1.0.1 |
| 1145 | + expect(await readFile(join(tmpDir, "CHANGELOG.md"), "utf8")).toEqual(`# Changelog\n\n## 1.0.0\nold\n`); |
| 1146 | + |
| 1147 | + const {stdout: msg} = await exec("git", ["log", "-1", "--pretty=%B"], opts); |
| 1148 | + expect(msg).toContain("tweak something"); |
| 1149 | +})); |
| 1150 | + |
| 1151 | +test("CHANGELOG.md with existing date is left alone", () => withTmpDir(async (tmpDir) => { |
| 1152 | + await writeFile(join(tmpDir, "package.json"), JSON.stringify({name: "test-pkg", version: "1.0.0"}, null, 2)); |
| 1153 | + const original = `# Changelog\n\n## [1.0.1] - 2024-01-15\n- existing entry\n`; |
| 1154 | + await writeFile(join(tmpDir, "CHANGELOG.md"), original); |
| 1155 | + |
| 1156 | + const {env} = await setupReleaseRepo(tmpDir); |
| 1157 | + const opts = {cwd: tmpDir, env: {...process.env, ...env}}; |
| 1158 | + |
| 1159 | + await exec("node", [distPath, "--no-push", "patch", "package.json"], opts); |
| 1160 | + |
| 1161 | + expect(await readFile(join(tmpDir, "CHANGELOG.md"), "utf8")).toEqual(original); |
| 1162 | + |
| 1163 | + const {stdout: msg} = await exec("git", ["log", "-1", "--pretty=%B"], opts); |
| 1164 | + expect(msg).toContain("- existing entry"); |
| 1165 | +})); |
| 1166 | + |
1045 | 1167 | test("readVersionFromPackageJson", () => withTmpDir(async (tmpDir) => { |
1046 | 1168 | await writeFile(join(tmpDir, "package.json"), JSON.stringify({name: "test", version: "3.2.1"}, null, 2)); |
1047 | 1169 | expect(readVersionFromPackageJson(tmpDir)).toEqual("3.2.1"); |
|
0 commit comments