@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.
- package/README.md +1 -1
- package/esm/deno.js +16 -8
- package/esm/hj.js +19 -0
- package/esm/node_modules/@conventional-changelog/template/LICENSE.md +21 -0
- package/esm/node_modules/@conventional-changelog/template/README.md +66 -0
- package/esm/node_modules/@conventional-changelog/template/dist/elements.d.ts +78 -0
- package/esm/node_modules/@conventional-changelog/template/dist/elements.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/elements.js +132 -0
- package/esm/node_modules/@conventional-changelog/template/dist/index.d.ts +6 -0
- package/esm/node_modules/@conventional-changelog/template/dist/index.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/index.js +5 -0
- package/esm/node_modules/@conventional-changelog/template/dist/legacy.d.ts +20 -0
- package/esm/node_modules/@conventional-changelog/template/dist/legacy.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/legacy.js +22 -0
- package/esm/node_modules/@conventional-changelog/template/dist/references.d.ts +43 -0
- package/esm/node_modules/@conventional-changelog/template/dist/references.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/references.js +82 -0
- package/esm/node_modules/@conventional-changelog/template/dist/templates.d.ts +74 -0
- package/esm/node_modules/@conventional-changelog/template/dist/templates.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/templates.js +125 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/commit.d.ts +37 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/commit.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/commit.js +2 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/context.d.ts +113 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/context.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/context.js +2 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/index.d.ts +4 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/index.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/index.js +2 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/utils.d.ts +2 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/utils.d.ts.map +1 -0
- package/esm/node_modules/@conventional-changelog/template/dist/types/utils.js +2 -0
- package/esm/node_modules/@conventional-changelog/template/package.json +38 -0
- package/esm/node_modules/@simple-libs/stream-utils/LICENSE +21 -0
- package/esm/node_modules/@simple-libs/stream-utils/README.md +79 -0
- package/esm/node_modules/@simple-libs/stream-utils/dist/index.d.ts +51 -0
- package/esm/node_modules/@simple-libs/stream-utils/dist/index.d.ts.map +1 -0
- package/esm/node_modules/@simple-libs/stream-utils/dist/index.js +116 -0
- package/esm/node_modules/@simple-libs/stream-utils/package.json +38 -0
- package/esm/node_modules/argue-cli/LICENSE +21 -0
- package/esm/node_modules/argue-cli/README.md +239 -0
- package/esm/node_modules/argue-cli/dist/args.d.ts +30 -0
- package/esm/node_modules/argue-cli/dist/args.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/argv.d.ts +14 -0
- package/esm/node_modules/argue-cli/dist/argv.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/core.d.ts +24 -0
- package/esm/node_modules/argue-cli/dist/core.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/index.d.ts +6 -0
- package/esm/node_modules/argue-cli/dist/index.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/index.js +254 -0
- package/esm/node_modules/argue-cli/dist/index.js.map +1 -0
- package/esm/node_modules/argue-cli/dist/options.d.ts +9 -0
- package/esm/node_modules/argue-cli/dist/options.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/types.d.ts +16 -0
- package/esm/node_modules/argue-cli/dist/types.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/dist/utils.d.ts +24 -0
- package/esm/node_modules/argue-cli/dist/utils.d.ts.map +1 -0
- package/esm/node_modules/argue-cli/package.json +34 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/LICENSE.md +21 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/README.md +369 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-changelog-writer/package.json +53 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/LICENSE +21 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/README.md +184 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-filter/package.json +31 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/LICENSE.md +21 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/README.md +383 -0
- package/esm/node_modules/conventional-changelog-core/node_modules/conventional-commits-parser/package.json +47 -0
- package/esm/node_modules/conventional-changelog-writer/README.md +49 -342
- package/esm/node_modules/conventional-changelog-writer/dist/cli/index.d.ts +3 -0
- package/esm/node_modules/conventional-changelog-writer/dist/cli/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/cli/index.js +68 -0
- package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.d.ts +4 -0
- package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/cli/utils.js +39 -0
- package/esm/node_modules/conventional-changelog-writer/dist/commit.d.ts +10 -0
- package/esm/node_modules/conventional-changelog-writer/dist/commit.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/commit.js +43 -0
- package/esm/node_modules/conventional-changelog-writer/dist/context.d.ts +27 -0
- package/esm/node_modules/conventional-changelog-writer/dist/context.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/context.js +134 -0
- package/esm/node_modules/conventional-changelog-writer/dist/index.d.ts +7 -0
- package/esm/node_modules/conventional-changelog-writer/dist/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/index.js +6 -0
- package/esm/node_modules/conventional-changelog-writer/dist/options.d.ts +25 -0
- package/esm/node_modules/conventional-changelog-writer/dist/options.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/options.js +79 -0
- package/esm/node_modules/conventional-changelog-writer/dist/template.d.ts +10 -0
- package/esm/node_modules/conventional-changelog-writer/dist/template.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/template.js +18 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/index.d.ts +8 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/index.js +2 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/options.d.ts +122 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/options.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/options.js +2 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/utils.d.ts +9 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/types/utils.js +2 -0
- package/esm/node_modules/conventional-changelog-writer/dist/utils.d.ts +20 -0
- package/esm/node_modules/conventional-changelog-writer/dist/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/utils.js +70 -0
- package/esm/node_modules/conventional-changelog-writer/dist/writers.d.ts +29 -0
- package/esm/node_modules/conventional-changelog-writer/dist/writers.d.ts.map +1 -0
- package/esm/node_modules/conventional-changelog-writer/dist/writers.js +94 -0
- package/esm/node_modules/conventional-changelog-writer/package.json +24 -26
- package/esm/node_modules/conventional-commits-filter/README.md +51 -170
- package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.d.ts +17 -0
- package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-filter/dist/RevertedCommitsFilter.js +46 -0
- package/esm/node_modules/conventional-commits-filter/dist/filters.d.ts +19 -0
- package/esm/node_modules/conventional-commits-filter/dist/filters.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-filter/dist/filters.js +34 -0
- package/esm/node_modules/conventional-commits-filter/dist/index.d.ts +4 -0
- package/esm/node_modules/conventional-commits-filter/dist/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-filter/dist/index.js +3 -0
- package/esm/node_modules/conventional-commits-filter/dist/types.d.ts +6 -0
- package/esm/node_modules/conventional-commits-filter/dist/types.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-filter/dist/types.js +2 -0
- package/esm/node_modules/conventional-commits-filter/dist/utils.d.ts +16 -0
- package/esm/node_modules/conventional-commits-filter/dist/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-filter/dist/utils.js +43 -0
- package/esm/node_modules/conventional-commits-filter/package.json +22 -16
- package/esm/node_modules/conventional-commits-parser/README.md +49 -353
- package/esm/node_modules/conventional-commits-parser/dist/CommitParser.d.ts +40 -0
- package/esm/node_modules/conventional-commits-parser/dist/CommitParser.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/CommitParser.js +319 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/index.d.ts +3 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/index.js +67 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/options.d.ts +3 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/options.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/options.js +40 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/utils.d.ts +5 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/cli/utils.js +49 -0
- package/esm/node_modules/conventional-commits-parser/dist/index.d.ts +4 -0
- package/esm/node_modules/conventional-commits-parser/dist/index.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/index.js +3 -0
- package/esm/node_modules/conventional-commits-parser/dist/options.d.ts +3 -0
- package/esm/node_modules/conventional-commits-parser/dist/options.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/options.js +27 -0
- package/esm/node_modules/conventional-commits-parser/dist/regex.d.ts +8 -0
- package/esm/node_modules/conventional-commits-parser/dist/regex.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/regex.js +63 -0
- package/esm/node_modules/conventional-commits-parser/dist/stream.d.ts +14 -0
- package/esm/node_modules/conventional-commits-parser/dist/stream.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/stream.js +38 -0
- package/esm/node_modules/conventional-commits-parser/dist/types.d.ts +100 -0
- package/esm/node_modules/conventional-commits-parser/dist/types.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/types.js +2 -0
- package/esm/node_modules/conventional-commits-parser/dist/utils.d.ts +41 -0
- package/esm/node_modules/conventional-commits-parser/dist/utils.d.ts.map +1 -0
- package/esm/node_modules/conventional-commits-parser/dist/utils.js +75 -0
- package/esm/node_modules/conventional-commits-parser/package.json +22 -23
- package/esm/src/cli/command-help.js +15 -0
- package/esm/src/cli/repair-configuration-description.js +98 -0
- package/esm/src/cli/run-changelog.js +51 -0
- package/esm/src/cli/run-cli.js +8 -0
- package/esm/src/features/built-in-feature-registry.js +2 -0
- package/esm/src/features/changelog-feature.js +155 -0
- package/esm/src/features/github-ci-artifacts.js +17 -3
- package/esm/src/features/github-ci-runtime-matrix.js +50 -0
- package/esm/src/features/github-release-publish-artifacts.js +5 -4
- package/esm/src/features/github-release-publish-feature.js +7 -2
- package/esm/src/features/github-workflow-cache.js +18 -0
- package/esm/src/features/npm-readme-badge.js +163 -0
- package/esm/src/features/readme-contribution-plans.js +18 -1
- package/esm/src/features/workflow-deno.js +1 -1
- package/esm/src/package/npm-name.js +5 -0
- package/esm/src/readme/contribution-blocks.js +33 -6
- package/esm/src/release/changelog-markdown.js +41 -0
- package/esm/src/release/changelog.js +155 -7
- package/esm/src/release/format-changelog.js +15 -0
- package/esm/src/release/migrate-changelog.js +92 -0
- package/esm/src/release/publish-github.js +7 -3
- package/esm/src/release/publish-npm.js +2 -2
- package/esm/src/release/publish-tag-prepare.js +63 -78
- package/esm/src/release/release-history.js +53 -0
- package/npm-dependencies.lock.json +115 -48
- package/package.json +7 -5
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/dependencies.js +0 -11036
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/index.d.ts +0 -1416
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/index.js +0 -100
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/licenses.md +0 -1767
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/options.js +0 -92
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/parser.js +0 -84
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/utils.js +0 -7
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/build/validate.js +0 -122
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/license +0 -9
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/package.json +0 -107
- package/esm/node_modules/conventional-commits-parser/node_modules/meow/readme.md +0 -314
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/cli.mjs +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/index.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/lib/util.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/commit.hbs +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/footer.hbs +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/header.hbs +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core/node_modules/conventional-changelog-writer}/templates/template.hbs +0 -0
- /package/esm/node_modules/{conventional-commits-filter → conventional-changelog-core/node_modules/conventional-commits-filter}/index.js +0 -0
- /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/cli.mjs +0 -0
- /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/index.js +0 -0
- /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/lib/parser.js +0 -0
- /package/esm/node_modules/{conventional-commits-parser → conventional-changelog-core/node_modules/conventional-commits-parser}/lib/regex.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/dependencies.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/index.d.ts +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/index.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/licenses.md +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/options.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/parser.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/utils.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/build/validate.js +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/license +0 -0
- /package/esm/node_modules/{conventional-changelog-writer → conventional-changelog-core}/node_modules/meow/package.json +0 -0
- /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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
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
|
|
36
|
-
const matches =
|
|
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 (
|
|
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,
|
|
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,
|
|
94
|
-
const insertion =
|
|
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
|
|
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
|
|
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
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
+
}
|