@hugojosefson/cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +1 -1
  2. package/esm/deno.js +16 -8
  3. package/esm/hj.js +19 -0
  4. package/esm/node_modules/@conventional-changelog/template/LICENSE.md +21 -0
  5. package/esm/node_modules/@conventional-changelog/template/README.md +66 -0
  6. package/esm/node_modules/@conventional-changelog/template/dist/elements.d.ts +78 -0
  7. package/esm/node_modules/@conventional-changelog/template/dist/elements.d.ts.map +1 -0
  8. package/esm/node_modules/@conventional-changelog/template/dist/elements.js +132 -0
  9. package/esm/node_modules/@conventional-changelog/template/dist/index.d.ts +6 -0
  10. package/esm/node_modules/@conventional-changelog/template/dist/index.d.ts.map +1 -0
  11. package/esm/node_modules/@conventional-changelog/template/dist/index.js +5 -0
  12. package/esm/node_modules/@conventional-changelog/template/dist/legacy.d.ts +20 -0
  13. package/esm/node_modules/@conventional-changelog/template/dist/legacy.d.ts.map +1 -0
  14. package/esm/node_modules/@conventional-changelog/template/dist/legacy.js +22 -0
  15. package/esm/node_modules/@conventional-changelog/template/dist/references.d.ts +43 -0
  16. package/esm/node_modules/@conventional-changelog/template/dist/references.d.ts.map +1 -0
  17. package/esm/node_modules/@conventional-changelog/template/dist/references.js +82 -0
  18. package/esm/node_modules/@conventional-changelog/template/dist/templates.d.ts +74 -0
  19. package/esm/node_modules/@conventional-changelog/template/dist/templates.d.ts.map +1 -0
  20. package/esm/node_modules/@conventional-changelog/template/dist/templates.js +125 -0
  21. package/esm/node_modules/@conventional-changelog/template/dist/types/commit.d.ts +37 -0
  22. package/esm/node_modules/@conventional-changelog/template/dist/types/commit.d.ts.map +1 -0
  23. package/esm/node_modules/@conventional-changelog/template/dist/types/commit.js +2 -0
  24. package/esm/node_modules/@conventional-changelog/template/dist/types/context.d.ts +113 -0
  25. package/esm/node_modules/@conventional-changelog/template/dist/types/context.d.ts.map +1 -0
  26. package/esm/node_modules/@conventional-changelog/template/dist/types/context.js +2 -0
  27. package/esm/node_modules/@conventional-changelog/template/dist/types/index.d.ts +4 -0
  28. package/esm/node_modules/@conventional-changelog/template/dist/types/index.d.ts.map +1 -0
  29. package/esm/node_modules/@conventional-changelog/template/dist/types/index.js +2 -0
  30. package/esm/node_modules/@conventional-changelog/template/dist/types/utils.d.ts +2 -0
  31. package/esm/node_modules/@conventional-changelog/template/dist/types/utils.d.ts.map +1 -0
  32. package/esm/node_modules/@conventional-changelog/template/dist/types/utils.js +2 -0
  33. package/esm/node_modules/@conventional-changelog/template/package.json +38 -0
  34. package/esm/node_modules/@simple-libs/stream-utils/LICENSE +21 -0
  35. package/esm/node_modules/@simple-libs/stream-utils/README.md +79 -0
  36. package/esm/node_modules/@simple-libs/stream-utils/dist/index.d.ts +51 -0
  37. package/esm/node_modules/@simple-libs/stream-utils/dist/index.d.ts.map +1 -0
  38. package/esm/node_modules/@simple-libs/stream-utils/dist/index.js +116 -0
  39. package/esm/node_modules/@simple-libs/stream-utils/package.json +38 -0
  40. package/esm/node_modules/argue-cli/LICENSE +21 -0
  41. package/esm/node_modules/argue-cli/README.md +239 -0
  42. package/esm/node_modules/argue-cli/dist/args.d.ts +30 -0
  43. package/esm/node_modules/argue-cli/dist/args.d.ts.map +1 -0
  44. package/esm/node_modules/argue-cli/dist/argv.d.ts +14 -0
  45. package/esm/node_modules/argue-cli/dist/argv.d.ts.map +1 -0
  46. package/esm/node_modules/argue-cli/dist/core.d.ts +24 -0
  47. package/esm/node_modules/argue-cli/dist/core.d.ts.map +1 -0
  48. package/esm/node_modules/argue-cli/dist/index.d.ts +6 -0
  49. package/esm/node_modules/argue-cli/dist/index.d.ts.map +1 -0
  50. package/esm/node_modules/argue-cli/dist/index.js +254 -0
  51. package/esm/node_modules/argue-cli/dist/index.js.map +1 -0
  52. package/esm/node_modules/argue-cli/dist/options.d.ts +9 -0
  53. package/esm/node_modules/argue-cli/dist/options.d.ts.map +1 -0
  54. package/esm/node_modules/argue-cli/dist/types.d.ts +16 -0
  55. package/esm/node_modules/argue-cli/dist/types.d.ts.map +1 -0
  56. package/esm/node_modules/argue-cli/dist/utils.d.ts +24 -0
  57. package/esm/node_modules/argue-cli/dist/utils.d.ts.map +1 -0
  58. package/esm/node_modules/argue-cli/package.json +34 -0
  59. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/LICENSE.md +21 -0
  60. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/README.md +369 -0
  61. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/package.json +53 -0
  62. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/LICENSE +21 -0
  63. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/README.md +184 -0
  64. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/package.json +31 -0
  65. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/LICENSE.md +21 -0
  66. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/README.md +383 -0
  67. package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/package.json +47 -0
  68. package/esm/node_modules/conventional-changelog-writer/README.md +49 -342
  69. package/esm/node_modules/conventional-changelog-writer/dist/cli/index.d.ts +3 -0
  70. package/esm/node_modules/conventional-changelog-writer/dist/cli/index.d.ts.map +1 -0
  71. package/esm/node_modules/conventional-changelog-writer/dist/cli/index.js +68 -0
  72. package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.d.ts +4 -0
  73. package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.d.ts.map +1 -0
  74. package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.js +39 -0
  75. package/esm/node_modules/conventional-changelog-writer/dist/commit.d.ts +10 -0
  76. package/esm/node_modules/conventional-changelog-writer/dist/commit.d.ts.map +1 -0
  77. package/esm/node_modules/conventional-changelog-writer/dist/commit.js +43 -0
  78. package/esm/node_modules/conventional-changelog-writer/dist/context.d.ts +27 -0
  79. package/esm/node_modules/conventional-changelog-writer/dist/context.d.ts.map +1 -0
  80. package/esm/node_modules/conventional-changelog-writer/dist/context.js +134 -0
  81. package/esm/node_modules/conventional-changelog-writer/dist/index.d.ts +7 -0
  82. package/esm/node_modules/conventional-changelog-writer/dist/index.d.ts.map +1 -0
  83. package/esm/node_modules/conventional-changelog-writer/dist/index.js +6 -0
  84. package/esm/node_modules/conventional-changelog-writer/dist/options.d.ts +25 -0
  85. package/esm/node_modules/conventional-changelog-writer/dist/options.d.ts.map +1 -0
  86. package/esm/node_modules/conventional-changelog-writer/dist/options.js +79 -0
  87. package/esm/node_modules/conventional-changelog-writer/dist/template.d.ts +10 -0
  88. package/esm/node_modules/conventional-changelog-writer/dist/template.d.ts.map +1 -0
  89. package/esm/node_modules/conventional-changelog-writer/dist/template.js +18 -0
  90. package/esm/node_modules/conventional-changelog-writer/dist/types/index.d.ts +8 -0
  91. package/esm/node_modules/conventional-changelog-writer/dist/types/index.d.ts.map +1 -0
  92. package/esm/node_modules/conventional-changelog-writer/dist/types/index.js +2 -0
  93. package/esm/node_modules/conventional-changelog-writer/dist/types/options.d.ts +122 -0
  94. package/esm/node_modules/conventional-changelog-writer/dist/types/options.d.ts.map +1 -0
  95. package/esm/node_modules/conventional-changelog-writer/dist/types/options.js +2 -0
  96. package/esm/node_modules/conventional-changelog-writer/dist/types/utils.d.ts +9 -0
  97. package/esm/node_modules/conventional-changelog-writer/dist/types/utils.d.ts.map +1 -0
  98. package/esm/node_modules/conventional-changelog-writer/dist/types/utils.js +2 -0
  99. package/esm/node_modules/conventional-changelog-writer/dist/utils.d.ts +20 -0
  100. package/esm/node_modules/conventional-changelog-writer/dist/utils.d.ts.map +1 -0
  101. package/esm/node_modules/conventional-changelog-writer/dist/utils.js +70 -0
  102. package/esm/node_modules/conventional-changelog-writer/dist/writers.d.ts +29 -0
  103. package/esm/node_modules/conventional-changelog-writer/dist/writers.d.ts.map +1 -0
  104. package/esm/node_modules/conventional-changelog-writer/dist/writers.js +94 -0
  105. package/esm/node_modules/conventional-changelog-writer/package.json +24 -26
  106. package/esm/node_modules/conventional-commits-filter/README.md +51 -170
  107. package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.d.ts +17 -0
  108. package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.d.ts.map +1 -0
  109. package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.js +46 -0
  110. package/esm/node_modules/conventional-commits-filter/dist/filters.d.ts +19 -0
  111. package/esm/node_modules/conventional-commits-filter/dist/filters.d.ts.map +1 -0
  112. package/esm/node_modules/conventional-commits-filter/dist/filters.js +34 -0
  113. package/esm/node_modules/conventional-commits-filter/dist/index.d.ts +4 -0
  114. package/esm/node_modules/conventional-commits-filter/dist/index.d.ts.map +1 -0
  115. package/esm/node_modules/conventional-commits-filter/dist/index.js +3 -0
  116. package/esm/node_modules/conventional-commits-filter/dist/types.d.ts +6 -0
  117. package/esm/node_modules/conventional-commits-filter/dist/types.d.ts.map +1 -0
  118. package/esm/node_modules/conventional-commits-filter/dist/types.js +2 -0
  119. package/esm/node_modules/conventional-commits-filter/dist/utils.d.ts +16 -0
  120. package/esm/node_modules/conventional-commits-filter/dist/utils.d.ts.map +1 -0
  121. package/esm/node_modules/conventional-commits-filter/dist/utils.js +43 -0
  122. package/esm/node_modules/conventional-commits-filter/package.json +22 -16
  123. package/esm/node_modules/conventional-commits-parser/README.md +49 -353
  124. package/esm/node_modules/conventional-commits-parser/dist/CommitParser.d.ts +40 -0
  125. package/esm/node_modules/conventional-commits-parser/dist/CommitParser.d.ts.map +1 -0
  126. package/esm/node_modules/conventional-commits-parser/dist/CommitParser.js +319 -0
  127. package/esm/node_modules/conventional-commits-parser/dist/cli/index.d.ts +3 -0
  128. package/esm/node_modules/conventional-commits-parser/dist/cli/index.d.ts.map +1 -0
  129. package/esm/node_modules/conventional-commits-parser/dist/cli/index.js +67 -0
  130. package/esm/node_modules/conventional-commits-parser/dist/cli/options.d.ts +3 -0
  131. package/esm/node_modules/conventional-commits-parser/dist/cli/options.d.ts.map +1 -0
  132. package/esm/node_modules/conventional-commits-parser/dist/cli/options.js +40 -0
  133. package/esm/node_modules/conventional-commits-parser/dist/cli/utils.d.ts +5 -0
  134. package/esm/node_modules/conventional-commits-parser/dist/cli/utils.d.ts.map +1 -0
  135. package/esm/node_modules/conventional-commits-parser/dist/cli/utils.js +49 -0
  136. package/esm/node_modules/conventional-commits-parser/dist/index.d.ts +4 -0
  137. package/esm/node_modules/conventional-commits-parser/dist/index.d.ts.map +1 -0
  138. package/esm/node_modules/conventional-commits-parser/dist/index.js +3 -0
  139. package/esm/node_modules/conventional-commits-parser/dist/options.d.ts +3 -0
  140. package/esm/node_modules/conventional-commits-parser/dist/options.d.ts.map +1 -0
  141. package/esm/node_modules/conventional-commits-parser/dist/options.js +27 -0
  142. package/esm/node_modules/conventional-commits-parser/dist/regex.d.ts +8 -0
  143. package/esm/node_modules/conventional-commits-parser/dist/regex.d.ts.map +1 -0
  144. package/esm/node_modules/conventional-commits-parser/dist/regex.js +63 -0
  145. package/esm/node_modules/conventional-commits-parser/dist/stream.d.ts +14 -0
  146. package/esm/node_modules/conventional-commits-parser/dist/stream.d.ts.map +1 -0
  147. package/esm/node_modules/conventional-commits-parser/dist/stream.js +38 -0
  148. package/esm/node_modules/conventional-commits-parser/dist/types.d.ts +100 -0
  149. package/esm/node_modules/conventional-commits-parser/dist/types.d.ts.map +1 -0
  150. package/esm/node_modules/conventional-commits-parser/dist/types.js +2 -0
  151. package/esm/node_modules/conventional-commits-parser/dist/utils.d.ts +41 -0
  152. package/esm/node_modules/conventional-commits-parser/dist/utils.d.ts.map +1 -0
  153. package/esm/node_modules/conventional-commits-parser/dist/utils.js +75 -0
  154. package/esm/node_modules/conventional-commits-parser/package.json +22 -23
  155. package/esm/src/cli/command-help.js +15 -0
  156. package/esm/src/cli/repair-configuration-description.js +98 -0
  157. package/esm/src/cli/run-changelog.js +51 -0
  158. package/esm/src/cli/run-cli.js +8 -0
  159. package/esm/src/features/built-in-feature-registry.js +2 -0
  160. package/esm/src/features/changelog-feature.js +155 -0
  161. package/esm/src/features/github-ci-artifacts.js +17 -3
  162. package/esm/src/features/github-ci-runtime-matrix.js +50 -0
  163. package/esm/src/features/github-release-publish-artifacts.js +5 -4
  164. package/esm/src/features/github-release-publish-feature.js +7 -2
  165. package/esm/src/features/github-workflow-cache.js +18 -0
  166. package/esm/src/features/npm-readme-badge.js +163 -0
  167. package/esm/src/features/readme-contribution-plans.js +18 -1
  168. package/esm/src/features/workflow-deno.js +1 -1
  169. package/esm/src/package/npm-name.js +5 -0
  170. package/esm/src/readme/contribution-blocks.js +33 -6
  171. package/esm/src/release/changelog-markdown.js +41 -0
  172. package/esm/src/release/changelog.js +155 -7
  173. package/esm/src/release/format-changelog.js +15 -0
  174. package/esm/src/release/migrate-changelog.js +92 -0
  175. package/esm/src/release/publish-github.js +7 -3
  176. package/esm/src/release/publish-npm.js +2 -2
  177. package/esm/src/release/publish-tag-prepare.js +63 -78
  178. package/esm/src/release/release-history.js +53 -0
  179. package/npm-dependencies.lock.json +115 -48
  180. package/package.json +7 -5
  181. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/dependencies.js +0 -11036
  182. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/index.d.ts +0 -1416
  183. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/index.js +0 -100
  184. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/licenses.md +0 -1767
  185. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/options.js +0 -92
  186. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/parser.js +0 -84
  187. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/utils.js +0 -7
  188. package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/validate.js +0 -122
  189. package/esm/node_modules/conventional-commits-parser/node_modules/meow/license +0 -9
  190. package/esm/node_modules/conventional-commits-parser/node_modules/meow/package.json +0 -107
  191. package/esm/node_modules/conventional-commits-parser/node_modules/meow/readme.md +0 -314
  192. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/cli.mjs +0 -0
  193. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/index.js +0 -0
  194. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/lib/util.js +0 -0
  195. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/commit.hbs +0 -0
  196. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/footer.hbs +0 -0
  197. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/header.hbs +0 -0
  198. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/template.hbs +0 -0
  199. /package/esm/node_modules/{conventional-commits-filter → conventional-changelog-core/node_modules/conventional-commits-filter}/index.js +0 -0
  200. /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/cli.mjs +0 -0
  201. /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/index.js +0 -0
  202. /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/lib/parser.js +0 -0
  203. /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/lib/regex.js +0 -0
  204. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/dependencies.js +0 -0
  205. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/index.d.ts +0 -0
  206. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/index.js +0 -0
  207. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/licenses.md +0 -0
  208. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/options.js +0 -0
  209. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/parser.js +0 -0
  210. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/utils.js +0 -0
  211. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/validate.js +0 -0
  212. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/license +0 -0
  213. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/package.json +0 -0
  214. /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/readme.md +0 -0
@@ -0,0 +1,41 @@
1
+ export function changelogLines(text) {
2
+ const lines = [];
3
+ let offset = 0;
4
+ let comment = false;
5
+ let fence;
6
+ for (const line of text.split(/(?<=\n)/)) {
7
+ const value = line.replace(/\r?\n$/, "");
8
+ const marker = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(value);
9
+ if (fence) {
10
+ if (marker && marker[1][0] === fence.character &&
11
+ marker[1].length >= fence.length && !marker[2].trim())
12
+ fence = undefined;
13
+ }
14
+ else if (comment || value.replace(/(`+).*?\1/g, "").includes("<!--")) {
15
+ // Comments can contain example headings and fences; neither is structure.
16
+ const markers = value.replace(/(`+).*?\1/g, "").match(/<!--|-->/g) ?? [];
17
+ for (const token of markers) {
18
+ if (token === "<!--")
19
+ comment = true;
20
+ else
21
+ comment = false;
22
+ }
23
+ }
24
+ else if (marker) {
25
+ fence = { character: marker[1][0], length: marker[1].length };
26
+ }
27
+ else if (!/^ {4}|^\t/.test(value)) {
28
+ lines.push({ text: value, offset });
29
+ }
30
+ offset += line.length;
31
+ }
32
+ return lines;
33
+ }
34
+ export function changelogHeadings(text) {
35
+ return changelogLines(text).flatMap((line) => {
36
+ const heading = /^ {0,3}(#{1,6})[ \t]+(.+?)(?:[ \t]+#+)?[ \t]*$/.exec(line.text);
37
+ return heading
38
+ ? [{ title: heading[2], level: heading[1].length, offset: line.offset }]
39
+ : [];
40
+ });
41
+ }
@@ -1,7 +1,117 @@
1
1
  /** @module Immutable changelog release-section creation and insertion. */
2
+ import { changelogHeadings } from "./changelog-markdown.js";
3
+ import { CommitParser } from "conventional-commits-parser";
4
+ import { writeChangelogString } from "conventional-changelog-writer";
5
+ import { validateConventionalCommits } from "./conventional-commit.js";
6
+ import { parseSemver } from "./semver.js";
7
+ const sections = ["BREAKING CHANGE", "Features", "Fixes", "Other"];
8
+ /** Validates the target used for commit and issue links. */
9
+ export function changelogRepositoryUrl(repository) {
10
+ const parts = repository.split("/");
11
+ if (parts.length !== 2 ||
12
+ parts.some((part) => !/^[A-Za-z0-9_.-]+$/.test(part) || part === "." || part === ".."))
13
+ throw new TypeError("Changelog requires a GitHub owner/repository.");
14
+ return `https://github.com/${repository}`;
15
+ }
16
+ /** Groups validated full messages with the pinned Conventional Changelog libraries. */
17
+ export async function createReleaseSection(version, commits, repository) {
18
+ const repositoryUrl = changelogRepositoryUrl(repository);
19
+ if (!parseSemver(version) || commits.length === 0 ||
20
+ commits.some((commit) => !/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(commit.hash))) {
21
+ throw new TypeError("Release section requires a version and full commit SHAs.");
22
+ }
23
+ const validated = validateConventionalCommits(commits.map((commit) => commit.message));
24
+ const parser = new CommitParser({
25
+ headerPattern: /^([a-z][a-z0-9-]*)(?:\(([\w./@ -]+)\))?!?: (.+)$/,
26
+ headerCorrespondence: ["type", "scope", "subject"],
27
+ noteKeywords: ["BREAKING CHANGE", "BREAKING-CHANGE"],
28
+ });
29
+ const parsed = commits.map((commit, index) => {
30
+ const data = parser.parse(commit.message);
31
+ const references = [
32
+ ...data.references,
33
+ ...data.notes.flatMap((note) => parser.parse(`fix: note\n\n${note.text}`).references),
34
+ ];
35
+ const refs = new Map();
36
+ for (const reference of references) {
37
+ if (!/^\d+$/.test(reference.issue))
38
+ continue;
39
+ const target = reference.repository
40
+ ? `${reference.owner ?? repository.split("/")[0]}/${reference.repository}`
41
+ : repository;
42
+ const label = `${reference.owner ? `${reference.owner}/` : ""}${reference.repository ?? ""}#${reference.issue}`;
43
+ refs.set(label, `${changelogRepositoryUrl(target)}/issues/${reference.issue}`);
44
+ }
45
+ const used = new Set();
46
+ const subject = linkReferences(data.subject ?? "", refs, used);
47
+ const notes = data.notes.map((note) => ({
48
+ ...note,
49
+ text: linkReferences(note.text, refs, used),
50
+ }));
51
+ const extraLinks = [...refs].flatMap(([label, url]) => {
52
+ if (used.has(url))
53
+ return [];
54
+ used.add(url);
55
+ return [`[${label}](${url})`];
56
+ });
57
+ return {
58
+ ...data,
59
+ hash: commit.hash,
60
+ scope: data.scope ?? "",
61
+ subject,
62
+ notes,
63
+ type: validated[index].breaking
64
+ ? "BREAKING CHANGE"
65
+ : data.type === "feat"
66
+ ? "Features"
67
+ : data.type === "fix"
68
+ ? "Fixes"
69
+ : "Other",
70
+ links: [
71
+ `[${commit.hash.slice(0, 7)}](${repositoryUrl}/commit/${commit.hash})`,
72
+ ...extraLinks,
73
+ ],
74
+ };
75
+ });
76
+ const text = await writeChangelogString(parsed, { version }, {
77
+ // Preserve full hashes, every commit type, and the supplied range order.
78
+ transform: (commit) => commit,
79
+ ignoreReverted: false,
80
+ generateOn: () => false,
81
+ groupBy: "type",
82
+ commitsSort: (a, b) => compare(a.scope, b.scope),
83
+ commitGroupsSort: (a, b) => sections.indexOf(a.title) - sections.indexOf(b.title),
84
+ template: (context) => {
85
+ let output = `## ${version}\n\n`;
86
+ for (const group of context.commitGroups ?? []) {
87
+ output += `### ${group.title}\n\n`;
88
+ let scope;
89
+ for (const commit of group.commits) {
90
+ if (commit.scope !== scope) {
91
+ if (scope !== undefined)
92
+ output += "\n";
93
+ scope = commit.scope;
94
+ if (scope)
95
+ output += `#### ${scope}\n\n`;
96
+ }
97
+ output += `- ${commit.subject} (${commit.links.join(", ")})\n`;
98
+ for (const note of commit.notes) {
99
+ output += `\n ${note.text.replaceAll("\n", "\n ")}\n`;
100
+ }
101
+ }
102
+ output += "\n";
103
+ }
104
+ return output;
105
+ },
106
+ });
107
+ return `${text.trimEnd()}\n\n`;
108
+ }
109
+ function compare(a, b) {
110
+ return a < b ? -1 : a > b ? 1 : 0;
111
+ }
2
112
  const heading = "# Changelog";
3
113
  /** Creates a deterministic release heading and list from validated commit subjects. */
4
- export function createReleaseSection(version, subjects) {
114
+ export function createLegacyReleaseSection(version, subjects) {
5
115
  if (!version || subjects.some((subject) => !subject)) {
6
116
  throw new TypeError("Release section requires a version and non-empty subjects.");
7
117
  }
@@ -15,12 +125,22 @@ export function createChangelogInsertion(oldText, releaseSection) {
15
125
  if (!oldText) {
16
126
  return { offset: 0, text: `${heading}\n\n${releaseSection}` };
17
127
  }
18
- if (!oldText.startsWith(heading) || !/^# Changelog(?:\r?\n|$)/.test(oldText)) {
19
- throw new TypeError("Existing changelog must start with # Changelog.");
20
- }
21
- const section = /\n## [^\n]+(?:\r?\n|$)/.exec(oldText);
22
- const offset = section ? section.index + 1 : oldText.length;
23
- return { offset, text: releaseSection };
128
+ const allHeadings = changelogHeadings(oldText);
129
+ const headings = allHeadings.filter((item) => item.level === 2);
130
+ const section = headings.find((item) => parseSemver(item.title) || /^\[?unreleased\]?$/i.test(item.title)) ?? headings[0];
131
+ // Without release headings, insert just after a leading title. Never enter a code block.
132
+ const titleEnd = allHeadings[0]?.level === 1 && allHeadings[0]?.offset === 0
133
+ ? /^(?:[^\n]*\n)(?:[ \t]*\r?\n)*/.exec(oldText)?.[0].length ??
134
+ oldText.length
135
+ : 0;
136
+ const offset = section?.offset ?? titleEnd;
137
+ const prefix = oldText.slice(0, offset);
138
+ const separator = !prefix || prefix.endsWith("\n\n")
139
+ ? ""
140
+ : prefix.endsWith("\n")
141
+ ? "\n"
142
+ : "\n\n";
143
+ return { offset, text: separator + releaseSection };
24
144
  }
25
145
  /** Applies the recorded insertion and leaves every old byte outside it unchanged. */
26
146
  export function applyChangelogInsertion(oldText, insertion) {
@@ -31,3 +151,31 @@ export function applyChangelogInsertion(oldText, insertion) {
31
151
  return oldText.slice(0, insertion.offset) + insertion.text +
32
152
  oldText.slice(insertion.offset);
33
153
  }
154
+ /** Leave existing Markdown links, code and URLs intact while linking plain references. */
155
+ function linkReferences(text, references, used) {
156
+ return text.replace(/\[[^\]]*\]\([^)]*\)|`[^`]*`|https?:\/\/\S+|(?<![\w/])(?:[\w.-]+(?:\/[\w.-]+)?)?#\d+\b/g, (token) => {
157
+ const url = references.get(token);
158
+ if (!url) {
159
+ for (const target of references.values()) {
160
+ if (token.endsWith(`](${target})`))
161
+ used.add(target);
162
+ }
163
+ return token;
164
+ }
165
+ used.add(url);
166
+ return `[${token}](${url})`;
167
+ });
168
+ }
169
+ /** The original insertion algorithm remains frozen for exact legacy recovery. */
170
+ export function createLegacyChangelogInsertion(oldText, releaseSection) {
171
+ if (!oldText)
172
+ return { offset: 0, text: `${heading}\n\n${releaseSection}` };
173
+ if (!/^# Changelog(?:\r?\n|$)/.test(oldText)) {
174
+ throw new TypeError("Existing changelog must start with # Changelog.");
175
+ }
176
+ const section = /\n## [^\n]+(?:\r?\n|$)/.exec(oldText);
177
+ return {
178
+ offset: section ? section.index + 1 : oldText.length,
179
+ text: releaseSection,
180
+ };
181
+ }
@@ -0,0 +1,15 @@
1
+ /** Format new changelog text without rewriting previous content. */
2
+ import { createChangelogInsertion, } from "./changelog.js";
3
+ import { runOrThrow } from "./release-process.js";
4
+ export async function formatChangelogSection(process, section) {
5
+ return await runOrThrow(process, "deno", ["fmt", "--ext=md", "-"], {
6
+ stdin: section,
7
+ });
8
+ }
9
+ export async function formatChangelogInsertion(process, oldText, section) {
10
+ const formatted = await formatChangelogSection(process, section);
11
+ const insertion = createChangelogInsertion(oldText, `${formatted.trimEnd()}\n\n`);
12
+ return insertion.offset === oldText.length
13
+ ? { ...insertion, text: `${insertion.text.trimEnd()}\n` }
14
+ : insertion;
15
+ }
@@ -0,0 +1,92 @@
1
+ /** Read-only reconstruction of supported release sections from immutable Git ranges. */
2
+ import { changelogHeadings } from "./changelog-markdown.js";
3
+ import { parse } from "jsonc-parser";
4
+ import { createLegacyReleaseSection, createReleaseSection, } from "./changelog.js";
5
+ import { formatChangelogSection } from "./format-changelog.js";
6
+ import { commitMessages, releaseTags } from "./release-history.js";
7
+ import { selectPreviousRelease } from "./previous-release.js";
8
+ import { runOrThrow } from "./release-process.js";
9
+ import { parseSemver } from "./semver.js";
10
+ export async function migrateChangelog(oldText, repository, process) {
11
+ const headings = changelogHeadings(oldText).filter((heading) => heading.level === 2);
12
+ const tags = await releaseTags(process, "HEAD");
13
+ const seen = new Set();
14
+ const replacements = [];
15
+ for (const [index, heading] of headings.entries()) {
16
+ const version = heading.title;
17
+ if (!parseSemver(version))
18
+ continue;
19
+ if (seen.has(version)) {
20
+ throw new Error(`Duplicate changelog release ${version}.`);
21
+ }
22
+ seen.add(version);
23
+ const tag = tags.find((tag) => tag.name === version);
24
+ if (!tag?.lightweight || !tag.targetIsAncestor) {
25
+ throw new Error(`Release ${version} needs its original lightweight ancestor tag; fetch the complete Git history and tags.`);
26
+ }
27
+ const subject = (await runOrThrow(process, "git", [
28
+ "show",
29
+ "-s",
30
+ "--format=%s",
31
+ tag.target,
32
+ ])).trimEnd();
33
+ const parents = (await runOrThrow(process, "git", [
34
+ "rev-list",
35
+ "--parents",
36
+ "-n",
37
+ "1",
38
+ tag.target,
39
+ ])).trim().split(" ");
40
+ if (subject !== `chore(release): ${version}` || parents.length !== 2) {
41
+ throw new Error(`Release ${version} has no recognized single-parent release boundary.`);
42
+ }
43
+ const parent = parents[1];
44
+ const configs = [];
45
+ for (const path of ["deno.json", "deno.jsonc"]) {
46
+ const result = await process.run("git", ["show", `${parent}:${path}`]);
47
+ if (result.success)
48
+ configs.push(new TextDecoder().decode(result.stdout));
49
+ }
50
+ const errors = [];
51
+ const config = configs.length === 1
52
+ ? parse(configs[0], errors, { allowTrailingComma: true })
53
+ : undefined;
54
+ if (!config || errors.length || typeof config.version !== "string") {
55
+ throw new Error(`Release ${version} has no unambiguous previous version config.`);
56
+ }
57
+ const previous = selectPreviousRelease(await releaseTags(process, parent), config.version);
58
+ if (previous.kind === "conflict" || previous.kind === "version-mismatch") {
59
+ throw new Error(`Release ${version} has ambiguous previous release tags.`);
60
+ }
61
+ const range = previous.kind === "tag"
62
+ ? `${previous.tag.target}..${parent}`
63
+ : parent;
64
+ const ids = (await runOrThrow(process, "git", ["rev-list", "--reverse", range]))
65
+ .trim().split("\n");
66
+ const messages = await commitMessages(process, ids);
67
+ const grouped = await formatChangelogSection(process, await createReleaseSection(version, ids.map((hash, i) => ({ hash, message: messages[i] })), repository));
68
+ const legacy = await formatChangelogSection(process, createLegacyReleaseSection(version, messages.map((message) => message.split("\n")[0])));
69
+ const start = heading.offset;
70
+ const end = headings[index + 1]?.offset ?? oldText.length;
71
+ const existing = oldText.slice(start, end);
72
+ if (existing.trimEnd() !== legacy.trimEnd() &&
73
+ existing.trimEnd() !== grouped.trimEnd()) {
74
+ throw new Error(`Release ${version} contains custom or mismatched text. Keep extending this changelog, or reconcile that section with Git history before migration.`);
75
+ }
76
+ // Retain the section's exact separating whitespace and all non-release text.
77
+ replacements.push({
78
+ start,
79
+ end,
80
+ text: grouped.trimEnd() + existing.slice(existing.trimEnd().length),
81
+ });
82
+ }
83
+ if (!seen.size) {
84
+ throw new Error("No supported release sections found. Keep extending this changelog; migration supports ## <SemVer> sections from hj releases.");
85
+ }
86
+ let result = oldText;
87
+ for (const replacement of replacements.reverse()) {
88
+ result = result.slice(0, replacement.start) + replacement.text +
89
+ result.slice(replacement.end);
90
+ }
91
+ return result;
92
+ }
@@ -1,3 +1,5 @@
1
+ /** Idempotent GitHub Release publication. */
2
+ import { changelogHeadings } from "./changelog-markdown.js";
1
3
  import { processText } from "./release-process.js";
2
4
  import { confirmPublication, } from "./confirm-publication.js";
3
5
  import { parseSemver } from "./semver.js";
@@ -32,12 +34,14 @@ export async function publishGithub(input) {
32
34
  throw new Error("GitHub Release creation was not confirmed.");
33
35
  }
34
36
  export function changelogSection(text, version) {
35
- const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36
- const matches = [...text.matchAll(new RegExp(`^## ${escaped}\\r?\\n[\\s\\S]*?(?=^## |(?![\\s\\S]))`, "gm"))];
37
+ const headings = changelogHeadings(text).filter((heading) => heading.level === 2);
38
+ const matches = headings.flatMap((heading, index) => heading.title === version
39
+ ? [{ start: heading.offset, end: headings[index + 1]?.offset }]
40
+ : []);
37
41
  if (matches.length !== 1) {
38
42
  throw new TypeError("Applicable changelog section is missing or ambiguous.");
39
43
  }
40
- return matches[0][0];
44
+ return text.slice(matches[0].start, matches[0].end);
41
45
  }
42
46
  export function githubReleaseApi(process, repository) {
43
47
  if (!/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.test(repository)) {
@@ -1,4 +1,5 @@
1
1
  /** Independent npm publication of a checked, reproducible build artifact. */
2
+ import { validNpmPublishName } from "../package/npm-name.js";
2
3
  import * as fs from "node:fs/promises";
3
4
  import { isDeepStrictEqual } from "node:util";
4
5
  import { confirmPublication, } from "./confirm-publication.js";
@@ -75,8 +76,7 @@ export async function publishNpm(input) {
75
76
  throw new TypeError("npm publication requires a clean release checkout.");
76
77
  }
77
78
  const config = await versionConfig(input.files, release.version);
78
- if (typeof config.name !== "string" ||
79
- !/^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(config.name)) {
79
+ if (!validNpmPublishName(config.name)) {
80
80
  throw new TypeError("npm publication requires a scoped package name in Deno config.");
81
81
  }
82
82
  const tasks = object(config.tasks);
@@ -1,12 +1,14 @@
1
1
  /** @module Read-only-token preparation routes for tag publication. */
2
2
  import * as fs from "node:fs/promises";
3
+ import { commitMessages, releaseTags } from "./release-history.js";
4
+ import { formatChangelogInsertion } from "./format-changelog.js";
3
5
  import { requiredEnvironment } from "./release-environment.js";
4
6
  import { digestBytes } from "../repository/digest-bytes.js";
5
7
  import { LocalFileReader } from "../repository/local-file-reader.js";
6
8
  import { LocalGitReader } from "../repository/local-git-reader.js";
7
9
  import { inspectDenoConfig } from "../features/deno-config.js";
8
10
  import { githubReleasePublisherFeatures } from "../features/github-release-publish-feature.js";
9
- import { applyChangelogInsertion, createChangelogInsertion, createReleaseSection, } from "./changelog.js";
11
+ import { applyChangelogInsertion, changelogRepositoryUrl, createLegacyChangelogInsertion, createLegacyReleaseSection, createReleaseSection, } from "./changelog.js";
10
12
  import { validateConventionalCommits } from "./conventional-commit.js";
11
13
  import { nextVersion } from "./fork-version.js";
12
14
  import { validateGithubOutputSize } from "./github-output.js";
@@ -29,14 +31,14 @@ export async function publishTagPrepare(root, environment, process) {
29
31
  return { output: "Source commits are valid.\n", releaseNeeded: false };
30
32
  }
31
33
  if (route === "recovery") {
32
- return await prepareRecovery(root, environment, process);
34
+ return await prepareRecovery(root, environment, process, releaseRepository(environment));
33
35
  }
34
36
  if (route !== "usual") {
35
37
  throw new Error("HJ_RELEASE_ROUTE must be source-validation, usual, or recovery.");
36
38
  }
37
- return await prepareUsual(root, environment, process);
39
+ return await prepareUsual(root, environment, process, releaseRepository(environment));
38
40
  }
39
- async function prepareUsual(root, environment, process) {
41
+ async function prepareUsual(root, environment, process, repository) {
40
42
  await requireClean(process);
41
43
  await runOrThrow(process, "git", [
42
44
  "fetch",
@@ -55,7 +57,7 @@ async function prepareUsual(root, environment, process) {
55
57
  if (config.kind !== "config" || typeof config.value.version !== "string")
56
58
  throw new Error("One Deno config with a string version is required.");
57
59
  const tags = await releaseTags(process, selectedSha);
58
- await requireNoUntaggedRelease(root, process, selectedSha, tags);
60
+ await requireNoUntaggedRelease(root, process, selectedSha, tags, repository);
59
61
  await runOrThrow(process, "git", ["switch", "--detach", selectedSha]);
60
62
  const previous = selectPreviousRelease(tags, config.value.version);
61
63
  if (previous.kind === "conflict") {
@@ -85,16 +87,15 @@ async function prepareUsual(root, environment, process) {
85
87
  if (!releaseType)
86
88
  throw new Error("The release range is empty.");
87
89
  const version = await nextVersion(previousVersion, releaseType);
88
- await runOrThrow(process, "deno", ["task", "all"]);
89
90
  await requireClean(process);
90
91
  const previousVersionText = config.text;
91
92
  const versionText = updateDenoConfigVersion(config.path, config.text, version);
92
93
  const changelog = await observeOptionalFile(files, changelogPath);
93
- const releaseSection = createReleaseSection(version, conventional.map((commit) => commit.subject));
94
- const insertion = createChangelogInsertion(changelog?.content ?? "", releaseSection);
94
+ const releaseSection = await createReleaseSection(version, commits.map((hash, index) => ({ hash, message: messages[index] })), repository);
95
+ const insertion = await formatChangelogInsertion(process, changelog?.content ?? "", releaseSection);
95
96
  await fs.writeFile(new URL(config.path, root), versionText);
96
97
  await fs.writeFile(new URL(changelogPath, root), applyChangelogInsertion(changelog?.content ?? "", insertion));
97
- await runOrThrow(process, "deno", ["fmt", config.path, changelogPath]);
98
+ await runOrThrow(process, "deno", ["fmt", config.path]);
98
99
  const formattedVersion = await fs.readFile(new URL(config.path, root), "utf8");
99
100
  const formattedChangelog = await fs.readFile(new URL(changelogPath, root), "utf8");
100
101
  const formattedInsertion = retainedInsertion(changelog?.content ?? "", formattedChangelog, insertion.offset);
@@ -171,7 +172,7 @@ async function runReleaseContributions(root, files, process) {
171
172
  }
172
173
  }
173
174
  /** Rebuilds a committed release; it intentionally never creates a ref or tag. */
174
- async function prepareRecovery(root, environment, process) {
175
+ async function prepareRecovery(root, environment, process, repository) {
175
176
  const version = requiredEnvironment(environment, "HJ_RELEASE_TAG");
176
177
  if (!parseSemver(version)) {
177
178
  throw new Error("HJ_RELEASE_TAG must be an exact nonempty SemVer value.");
@@ -205,7 +206,7 @@ async function prepareRecovery(root, environment, process) {
205
206
  }
206
207
  let bundle;
207
208
  try {
208
- bundle = await rebuildRelease(root, process, parents[1], commit);
209
+ bundle = await rebuildRelease(root, process, parents[1], commit, repository);
209
210
  }
210
211
  finally {
211
212
  await restoreRecoveryWorktree(process, main, changelogPath);
@@ -236,7 +237,7 @@ async function prepareRecovery(root, environment, process) {
236
237
  releaseNeeded: true,
237
238
  };
238
239
  }
239
- async function requireNoUntaggedRelease(root, process, selectedSha, tags) {
240
+ async function requireNoUntaggedRelease(root, process, selectedSha, tags, repository) {
240
241
  const commits = lines(await runOrThrow(process, "git", ["rev-list", "--reverse", selectedSha]));
241
242
  const found = [];
242
243
  for (const commit of commits) {
@@ -257,7 +258,7 @@ async function requireNoUntaggedRelease(root, process, selectedSha, tags) {
257
258
  }
258
259
  let bundle;
259
260
  try {
260
- bundle = await rebuildRelease(root, process, parents[1], commit);
261
+ bundle = await rebuildRelease(root, process, parents[1], commit, repository);
261
262
  }
262
263
  finally {
263
264
  await restoreRecoveryWorktree(process, selectedSha, changelogPath);
@@ -279,7 +280,7 @@ async function requireNoUntaggedRelease(root, process, selectedSha, tags) {
279
280
  throw new Error(`Release ${found[0].version} is untagged; start the tag workflow with ${found[0].version}.`);
280
281
  }
281
282
  }
282
- async function rebuildRelease(root, process, parent, release) {
283
+ async function rebuildRelease(root, process, parent, release, repository) {
283
284
  await runOrThrow(process, "git", ["switch", "--detach", parent]);
284
285
  const files = new LocalFileReader(root, false);
285
286
  const config = await inspectDenoConfig({ files });
@@ -299,29 +300,59 @@ async function rebuildRelease(root, process, parent, release) {
299
300
  : previous.version;
300
301
  const range = previousTagTarget ? `${previousTagTarget}..${parent}` : parent;
301
302
  const commitIds = lines(await runOrThrow(process, "git", ["rev-list", "--reverse", range]));
302
- const conventional = validateConventionalCommits(await commitMessages(process, commitIds));
303
+ const messages = await commitMessages(process, commitIds);
304
+ const conventional = validateConventionalCommits(messages);
303
305
  const releaseType = selectReleaseType(conventional);
304
306
  if (!releaseType)
305
307
  throw new Error("Recovery release range is empty.");
306
308
  const version = await nextVersion(previousVersion, releaseType);
307
309
  const oldChangelog = await observeOptionalFile(files, changelogPath);
308
310
  const versionText = updateDenoConfigVersion(config.path, config.text, version);
309
- const insertion = createChangelogInsertion(oldChangelog?.content ?? "", createReleaseSection(version, conventional.map((item) => item.subject)));
310
- await fs.writeFile(new URL(config.path, root), versionText);
311
- await fs.writeFile(new URL(changelogPath, root), applyChangelogInsertion(oldChangelog?.content ?? "", insertion));
312
- await runOrThrow(process, "deno", ["fmt", config.path, changelogPath]);
313
- const formattedVersion = await fs.readFile(new URL(config.path, root), "utf8");
314
- const formattedChangelog = await fs.readFile(new URL(changelogPath, root), "utf8");
315
- const formattedInsertion = retainedInsertion(oldChangelog?.content ?? "", formattedChangelog, insertion.offset);
316
- const paths = await worktreeChangedPaths(process);
311
+ const sections = [
312
+ await createReleaseSection(version, commitIds.map((hash, index) => ({
313
+ hash,
314
+ message: messages[index],
315
+ })), repository),
316
+ createLegacyReleaseSection(version, conventional.map((item) => item.subject)),
317
+ ];
317
318
  const expectedPaths = [config.path, changelogPath];
318
- if (paths.length !== 2 || !expectedPaths.every((path) => paths.includes(path)))
319
- throw new Error("Recovery candidate changed an unexpected path.");
320
- const candidateDigest = await candidateTreeDigest(process, parent, expectedPaths);
321
319
  const releaseDigest = await releaseTreeIndexDigest(process, release);
322
- if (candidateDigest !== releaseDigest) {
320
+ let candidate;
321
+ // Compatibility is exact: neither format may waive the tree or path checks.
322
+ for (const [index, section] of sections.entries()) {
323
+ const insertion = index === 0
324
+ ? await formatChangelogInsertion(process, oldChangelog?.content ?? "", section)
325
+ : createLegacyChangelogInsertion(oldChangelog?.content ?? "", section);
326
+ await fs.writeFile(new URL(config.path, root), versionText);
327
+ await fs.writeFile(new URL(changelogPath, root), applyChangelogInsertion(oldChangelog?.content ?? "", insertion));
328
+ await runOrThrow(process, "deno", [
329
+ "fmt",
330
+ config.path,
331
+ ...(index === 1 ? [changelogPath] : []),
332
+ ]);
333
+ const formattedVersion = await fs.readFile(new URL(config.path, root), "utf8");
334
+ const formattedChangelog = await fs.readFile(new URL(changelogPath, root), "utf8");
335
+ const formattedInsertion = retainedInsertion(oldChangelog?.content ?? "", formattedChangelog, insertion.offset);
336
+ const paths = await worktreeChangedPaths(process);
337
+ if (paths.length !== 2 || !expectedPaths.every((path) => paths.includes(path))) {
338
+ throw new Error("Recovery candidate changed an unexpected path.");
339
+ }
340
+ const candidateDigest = await candidateTreeDigest(process, parent, expectedPaths);
341
+ if (candidateDigest === releaseDigest) {
342
+ candidate = {
343
+ insertion,
344
+ formattedVersion,
345
+ formattedChangelog,
346
+ formattedInsertion,
347
+ candidateDigest,
348
+ };
349
+ break;
350
+ }
351
+ }
352
+ if (!candidate) {
323
353
  throw new Error("Recovery release tree differs from its expected candidate.");
324
354
  }
355
+ const { insertion, formattedVersion, formattedChangelog, formattedInsertion, candidateDigest, } = candidate;
325
356
  const releasePaths = (await runOrThrow(process, "git", [
326
357
  "diff-tree",
327
358
  "--no-commit-id",
@@ -361,57 +392,6 @@ async function rebuildRelease(root, process, parent, release) {
361
392
  treeDigest: candidateDigest,
362
393
  };
363
394
  }
364
- async function releaseTags(process, selectedSha) {
365
- const text = await runOrThrow(process, "git", [
366
- "for-each-ref",
367
- "--format=%(refname:strip=2)%00%(objecttype)%00%(objectname)%00%(*objectname)%00",
368
- "refs/tags",
369
- ]);
370
- const fields = text.split("\0");
371
- const tags = [];
372
- for (let index = 0; index + 3 < fields.length; index += 4) {
373
- const name = fields[index].replace(/^\n+/, "");
374
- const objectType = fields[index + 1];
375
- const objectName = fields[index + 2];
376
- const peeled = fields[index + 3];
377
- if (!name)
378
- continue;
379
- const target = objectType === "commit" ? objectName : peeled;
380
- if (!sha.test(target))
381
- continue;
382
- const ancestor = await process.run("git", [
383
- "merge-base",
384
- "--is-ancestor",
385
- target,
386
- selectedSha,
387
- ]);
388
- if (ancestor.code !== 0 && ancestor.code !== 1) {
389
- throw new Error("Could not determine release-tag ancestry.");
390
- }
391
- tags.push({
392
- name,
393
- target,
394
- lightweight: objectType === "commit",
395
- targetIsAncestor: ancestor.code === 0,
396
- });
397
- }
398
- return tags;
399
- }
400
- async function commitMessages(process, commits) {
401
- const messages = [];
402
- for (const commit of commits) {
403
- if (!sha.test(commit)) {
404
- throw new Error("Git returned an invalid commit SHA.");
405
- }
406
- messages.push((await runOrThrow(process, "git", [
407
- "show",
408
- "--no-patch",
409
- "--format=%B",
410
- commit,
411
- ])).replace(/\n+$/, ""));
412
- }
413
- return messages;
414
- }
415
395
  async function requireClean(process) {
416
396
  const status = await runOrThrow(process, "git", [
417
397
  "status",
@@ -497,3 +477,8 @@ function lines(value) {
497
477
  async function digestText(text) {
498
478
  return await digestBytes(new TextEncoder().encode(text));
499
479
  }
480
+ function releaseRepository(environment) {
481
+ const repository = requiredEnvironment(environment, "GITHUB_REPOSITORY");
482
+ changelogRepositoryUrl(repository);
483
+ return repository;
484
+ }