@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,155 @@
|
|
|
1
|
+
/** Optional changelog lifecycle that preserves existing release history. */
|
|
2
|
+
import { parseSemver } from "../release/semver.js";
|
|
3
|
+
import { changelogHeadings, changelogLines, } from "../release/changelog-markdown.js";
|
|
4
|
+
const id = "changelog";
|
|
5
|
+
const path = "CHANGELOG.md";
|
|
6
|
+
const subject = { kind: "file", identifier: path };
|
|
7
|
+
export const changelogStarter = "# Changelog\n";
|
|
8
|
+
const resolution = "Automatic repair is unsupported. Make CHANGELOG.md a readable regular file, preserving its history, then inspect it again.";
|
|
9
|
+
async function inspect(context) {
|
|
10
|
+
const file = await context.files.observe(path);
|
|
11
|
+
if (file.kind !== "file" && file.kind !== "absent") {
|
|
12
|
+
throw new Error("CHANGELOG.md is not a readable regular file.");
|
|
13
|
+
}
|
|
14
|
+
return file;
|
|
15
|
+
}
|
|
16
|
+
async function detect(context) {
|
|
17
|
+
try {
|
|
18
|
+
const file = await inspect(context);
|
|
19
|
+
let observation = "CHANGELOG.md is absent. Enable --changelog to create it with # Changelog.";
|
|
20
|
+
if (file.kind === "file") {
|
|
21
|
+
const headings = changelogHeadings(file.content).filter((heading) => heading.level === 2);
|
|
22
|
+
const releaseText = headings.flatMap((heading, index) => parseSemver(heading.title)
|
|
23
|
+
? [file.content.slice(heading.offset, headings[index + 1]?.offset)]
|
|
24
|
+
: []).join("\n");
|
|
25
|
+
const content = changelogLines(releaseText).map((line) => line.text).join("\n");
|
|
26
|
+
const flat = /^[-*] [a-z][a-z0-9-]*(?:\([\w./@ -]+\))?!?: .+/m.test(content);
|
|
27
|
+
const grouped = /^### (?:BREAKING CHANGE|Features|Fixes|Other)\r?$/m.test(content);
|
|
28
|
+
observation = flat
|
|
29
|
+
? `CHANGELOG.md contains ${grouped ? "mixed flat and grouped" : "flat"} entries. Existing history is preserved. Optional migration preview: hj changelog migrate.`
|
|
30
|
+
: grouped
|
|
31
|
+
? "CHANGELOG.md contains grouped release sections. Existing history is preserved."
|
|
32
|
+
: file.content === changelogStarter
|
|
33
|
+
? "CHANGELOG.md has an empty starter heading, ready for release entries."
|
|
34
|
+
: "CHANGELOG.md exists with custom content. Existing history is preserved.";
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
state: file.kind === "file" ? "enabled" : "disabled",
|
|
38
|
+
evidence: [{
|
|
39
|
+
code: "changelog-observed",
|
|
40
|
+
kind: id,
|
|
41
|
+
subject,
|
|
42
|
+
observation,
|
|
43
|
+
}],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
const issue = {
|
|
48
|
+
code: "changelog-unreadable",
|
|
49
|
+
kind: id,
|
|
50
|
+
subject,
|
|
51
|
+
observation: "CHANGELOG.md could not be read as a regular file.",
|
|
52
|
+
resolution,
|
|
53
|
+
};
|
|
54
|
+
return { state: "ambiguous", evidence: [issue], issues: [issue] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function check(context, enabled) {
|
|
58
|
+
try {
|
|
59
|
+
const file = await inspect(context);
|
|
60
|
+
if ((enabled && file.kind === "file") || (!enabled && file.kind === "absent")) {
|
|
61
|
+
return {
|
|
62
|
+
result: "no-op",
|
|
63
|
+
warnings: [],
|
|
64
|
+
reason: enabled
|
|
65
|
+
? "Existing CHANGELOG.md is preserved."
|
|
66
|
+
: "CHANGELOG.md is absent.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!enabled && file.kind === "file" && file.content !== changelogStarter) {
|
|
70
|
+
return {
|
|
71
|
+
result: "blocked",
|
|
72
|
+
warnings: [],
|
|
73
|
+
blockers: [{
|
|
74
|
+
code: "changelog-history",
|
|
75
|
+
subjects: [subject],
|
|
76
|
+
message: "CHANGELOG.md contains history or custom content; automatic deletion is unsupported.",
|
|
77
|
+
resolution: "Keep the changelog enabled, or archive its contents and remove CHANGELOG.md manually after disabling dependent release publication.",
|
|
78
|
+
}],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
result: "allowed",
|
|
83
|
+
warnings: [],
|
|
84
|
+
preconditions: [{
|
|
85
|
+
kind: "file-digest",
|
|
86
|
+
path,
|
|
87
|
+
digest: file.kind === "file" ? file.digest : undefined,
|
|
88
|
+
}],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return {
|
|
93
|
+
result: "blocked",
|
|
94
|
+
warnings: [],
|
|
95
|
+
blockers: [{
|
|
96
|
+
code: "changelog-unreadable",
|
|
97
|
+
subjects: [subject],
|
|
98
|
+
message: "CHANGELOG.md could not be read as a regular file.",
|
|
99
|
+
resolution,
|
|
100
|
+
}],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function plan(context, enabled, allowed) {
|
|
105
|
+
const file = await inspect(context);
|
|
106
|
+
const checked = await check(context, enabled);
|
|
107
|
+
if (checked.result !== "allowed") {
|
|
108
|
+
throw new Error("Changelog operation no longer matches its checked state.");
|
|
109
|
+
}
|
|
110
|
+
let changes;
|
|
111
|
+
if (enabled) {
|
|
112
|
+
changes = [{
|
|
113
|
+
kind: "write-file",
|
|
114
|
+
path,
|
|
115
|
+
content: changelogStarter,
|
|
116
|
+
expectedDigest: undefined,
|
|
117
|
+
}];
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (file.kind !== "file") {
|
|
121
|
+
throw new Error("CHANGELOG.md disappeared during planning.");
|
|
122
|
+
}
|
|
123
|
+
changes = [{ kind: "remove-file", path, expectedDigest: file.digest }];
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
featureId: id,
|
|
127
|
+
action: enabled ? "enable" : "disable",
|
|
128
|
+
summary: enabled
|
|
129
|
+
? "Create CHANGELOG.md with # Changelog."
|
|
130
|
+
: "Remove the empty CHANGELOG.md starter.",
|
|
131
|
+
warnings: allowed.warnings,
|
|
132
|
+
preconditions: allowed.preconditions,
|
|
133
|
+
changes,
|
|
134
|
+
validations: [{
|
|
135
|
+
kind: "feature-redetection",
|
|
136
|
+
featureId: id,
|
|
137
|
+
expected: enabled ? "enabled" : "disabled",
|
|
138
|
+
}],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Presence, rather than a required style, defines a valid changelog. */
|
|
142
|
+
export const changelogFeature = {
|
|
143
|
+
metadata: {
|
|
144
|
+
id,
|
|
145
|
+
name: "Changelog",
|
|
146
|
+
summary: "Keep release history, with optional migration from flat entries to grouped sections.",
|
|
147
|
+
},
|
|
148
|
+
dependencies: { requires: [] },
|
|
149
|
+
capabilities: { provides: [], requires: [] },
|
|
150
|
+
detect,
|
|
151
|
+
checkEnable: (context) => check(context, true),
|
|
152
|
+
checkDisable: (context) => check(context, false),
|
|
153
|
+
planEnable: (context, allowed) => plan(context, true, allowed),
|
|
154
|
+
planDisable: (context, allowed) => plan(context, false, allowed),
|
|
155
|
+
};
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { legacyCiCheckCompatibility } from "./github-ci-legacy.js";
|
|
3
3
|
import { workflowDenoVersion } from "./workflow-toolchain.js";
|
|
4
4
|
import { hjPackageReference } from "./hj-package.js";
|
|
5
|
+
import { githubCiRuntimeMatrix, githubCiRuntimeMatrixMarker, } from "./github-ci-runtime-matrix.js";
|
|
6
|
+
import { denoCacheInputs } from "./github-workflow-cache.js";
|
|
5
7
|
import { inspectArtifact } from "../artifacts/inspect-artifact.js";
|
|
6
8
|
import { workflowCliArtifact } from "./workflow-cli.js";
|
|
7
9
|
export const githubCiFeatureId = "github-ci";
|
|
@@ -18,6 +20,10 @@ on:
|
|
|
18
20
|
permissions:
|
|
19
21
|
contents: read
|
|
20
22
|
|
|
23
|
+
concurrency:
|
|
24
|
+
group: hj-ci-\${{ github.workflow }}-pr-\${{ github.event.pull_request.number }}
|
|
25
|
+
cancel-in-progress: true
|
|
26
|
+
|
|
21
27
|
jobs:
|
|
22
28
|
check:
|
|
23
29
|
runs-on: ubuntu-latest
|
|
@@ -28,7 +34,7 @@ jobs:
|
|
|
28
34
|
- uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
|
|
29
35
|
with:
|
|
30
36
|
deno-version: ${workflowDenoVersion}
|
|
31
|
-
- run: deno task all
|
|
37
|
+
${denoCacheInputs} - run: deno task all
|
|
32
38
|
hj-release-commit-validation:
|
|
33
39
|
runs-on: ubuntu-latest
|
|
34
40
|
permissions:
|
|
@@ -41,7 +47,7 @@ jobs:
|
|
|
41
47
|
- uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
|
|
42
48
|
with:
|
|
43
49
|
deno-version: ${workflowDenoVersion}
|
|
44
|
-
- name: Validate release commits
|
|
50
|
+
${denoCacheInputs} - name: Validate release commits
|
|
45
51
|
env:
|
|
46
52
|
HJ_RELEASE_ROUTE: source-validation
|
|
47
53
|
HJ_SOURCE_BASE_SHA: \${{ github.event.pull_request.base.sha }}
|
|
@@ -82,7 +88,7 @@ jobs:
|
|
|
82
88
|
- uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
|
|
83
89
|
with:
|
|
84
90
|
deno-version: ${workflowDenoVersion}
|
|
85
|
-
- name: Update dependencies
|
|
91
|
+
${denoCacheInputs} - name: Update dependencies
|
|
86
92
|
env:
|
|
87
93
|
GH_TOKEN: \${{ github.token }}
|
|
88
94
|
run: |
|
|
@@ -117,6 +123,14 @@ jobs:
|
|
|
117
123
|
}];
|
|
118
124
|
/** Render the same exact CI variant for local adoption and remote protection. */
|
|
119
125
|
export function renderGithubCiArtifact(artifact, context, observation) {
|
|
126
|
+
if (artifact.path === githubCiArtifacts[0].path &&
|
|
127
|
+
observation.kind === "file" &&
|
|
128
|
+
observation.content.split("\n").includes(githubCiRuntimeMatrixMarker.trimEnd())) {
|
|
129
|
+
artifact = {
|
|
130
|
+
...artifact,
|
|
131
|
+
content: githubCiRuntimeMatrix(artifact.content),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
120
134
|
const migrated = artifact.path === githubCiArtifacts[0].path &&
|
|
121
135
|
observation.kind === "file" &&
|
|
122
136
|
observation.content.includes("# Retains the git-hj-init required test check.");
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @module Explicit managed CI variant for hj's shared runtime suite. */
|
|
2
|
+
import { nativeNpmCacheStep } from "./github-workflow-cache.js";
|
|
3
|
+
export const githubCiRuntimeMatrixMarker = "# hj-ci-runtime-matrix: deno-node-bun-v1\n";
|
|
4
|
+
/** Retain shared setup and release validation while replacing the check job. */
|
|
5
|
+
export function githubCiRuntimeMatrix(content) {
|
|
6
|
+
const start = content.indexOf(" check:\n");
|
|
7
|
+
const end = content.indexOf(" hj-release-commit-validation:\n", start);
|
|
8
|
+
const setup = content.slice(start, end).split(" steps:\n")[1]
|
|
9
|
+
.split(" - run: deno task all\n")[0];
|
|
10
|
+
const upload = (label) => ` - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
11
|
+
with:
|
|
12
|
+
name: runtime-report-${label}
|
|
13
|
+
path: .hj/test-results/${label}.json
|
|
14
|
+
include-hidden-files: true
|
|
15
|
+
if-no-files-found: error
|
|
16
|
+
retention-days: 1
|
|
17
|
+
overwrite: true
|
|
18
|
+
`;
|
|
19
|
+
const jobs = ` deno:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
${setup} - run: deno task ci-deno
|
|
23
|
+
${upload("deno")} native:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
runtime: [node24, node26, bun]
|
|
29
|
+
steps:
|
|
30
|
+
${setup}${nativeNpmCacheStep} - run: deno run --allow-all scripts/run-test-matrix.ts --runtime \${{ matrix.runtime }}
|
|
31
|
+
${upload("\${{ matrix.runtime }}")} check:
|
|
32
|
+
if: \${{ always() }}
|
|
33
|
+
needs: [deno, native]
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- name: Require successful checks and every runtime
|
|
37
|
+
env:
|
|
38
|
+
DENO_RESULT: \${{ needs.deno.result }}
|
|
39
|
+
NATIVE_RESULT: \${{ needs.native.result }}
|
|
40
|
+
run: test "$DENO_RESULT" = success && test "$NATIVE_RESULT" = success
|
|
41
|
+
${setup} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
42
|
+
with:
|
|
43
|
+
pattern: runtime-report-*
|
|
44
|
+
path: .hj/test-results
|
|
45
|
+
merge-multiple: true
|
|
46
|
+
- name: Compare complete executed inventories
|
|
47
|
+
run: deno run --allow-read scripts/run-test-matrix.ts --compare-only
|
|
48
|
+
`;
|
|
49
|
+
return (content.slice(0, start) + jobs + content.slice(end)).replace("name: CI\n", githubCiRuntimeMatrixMarker + "name: CI\n");
|
|
50
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @module Exact workflows owned by release-publication features. */
|
|
2
2
|
import { workflowDenoVersion } from "./workflow-toolchain.js";
|
|
3
3
|
import { hjPackageReference } from "./hj-package.js";
|
|
4
|
+
import { denoCacheInputs, nativeNpmCacheStep, } from "./github-workflow-cache.js";
|
|
4
5
|
import { inspectArtifact } from "../artifacts/inspect-artifact.js";
|
|
5
6
|
import { workflowCliArtifact } from "./workflow-cli.js";
|
|
6
7
|
import { publishGithubWorkflow, publishJsrWorkflow, publishNpmWorkflow, publishTagWorkflow, } from "../release/names.js";
|
|
@@ -48,7 +49,7 @@ jobs:
|
|
|
48
49
|
- uses: ${deno}
|
|
49
50
|
with:
|
|
50
51
|
deno-version: ${workflowDenoVersion}
|
|
51
|
-
- name: Configure Git authentication
|
|
52
|
+
${denoCacheInputs}${nativeNpmCacheStep} - name: Configure Git authentication
|
|
52
53
|
env:
|
|
53
54
|
GH_TOKEN: \${{ github.token }}
|
|
54
55
|
run: gh auth setup-git
|
|
@@ -59,7 +60,7 @@ jobs:
|
|
|
59
60
|
HJ_RELEASE_TAG: \${{ inputs.tag }}
|
|
60
61
|
run: >-
|
|
61
62
|
deno run --no-lock
|
|
62
|
-
--allow-env=PATH,GITHUB_OUTPUT,GITHUB_STEP_SUMMARY,HJ_RELEASE_ROUTE,HJ_RELEASE_TAG,ESBUILD_BINARY_PATH,ESBUILD_WORKER_THREADS
|
|
63
|
+
--allow-env=PATH,GITHUB_REPOSITORY,GITHUB_OUTPUT,GITHUB_STEP_SUMMARY,HJ_RELEASE_ROUTE,HJ_RELEASE_TAG,ESBUILD_BINARY_PATH,ESBUILD_WORKER_THREADS
|
|
63
64
|
--allow-read=.,/tmp/opencode
|
|
64
65
|
--allow-write=deno.json,deno.jsonc,CHANGELOG.md,/tmp/opencode,"\${GITHUB_OUTPUT}","\${GITHUB_STEP_SUMMARY}"
|
|
65
66
|
--allow-run=deno,git
|
|
@@ -83,7 +84,7 @@ jobs:
|
|
|
83
84
|
- uses: ${deno}
|
|
84
85
|
with:
|
|
85
86
|
deno-version: ${workflowDenoVersion}
|
|
86
|
-
- name: Configure Git authentication
|
|
87
|
+
${denoCacheInputs} - name: Configure Git authentication
|
|
87
88
|
env:
|
|
88
89
|
GH_TOKEN: \${{ github.token }}
|
|
89
90
|
run: gh auth setup-git
|
|
@@ -140,7 +141,7 @@ ${job === "jsr"
|
|
|
140
141
|
- uses: ${deno}
|
|
141
142
|
with:
|
|
142
143
|
deno-version: ${workflowDenoVersion}
|
|
143
|
-
${job === "npm"
|
|
144
|
+
${denoCacheInputs}${job === "npm"
|
|
144
145
|
? ` - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
|
145
146
|
with:
|
|
146
147
|
node-version: "24"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @module Lifecycle declarations for generated release publication workflows. */
|
|
2
|
+
import { withNpmReadmeBadge } from "./npm-readme-badge.js";
|
|
2
3
|
import { inspectLegacyRelease } from "./github-release-legacy.js";
|
|
3
4
|
import { checkLegacyReleaseMigration, legacyReleaseCompanionPreconditions, legacyReleaseMigrationChanges, legacyReleaseMigrationSummary, } from "./github-release-legacy-migration.js";
|
|
4
5
|
import { jsrPublishCheckArgs } from "./jsr-package-config.js";
|
|
@@ -37,6 +38,10 @@ function releaseFeature(id, name, artifact, requires, options = {}) {
|
|
|
37
38
|
}
|
|
38
39
|
export const githubReleasePublishTagFeature = releaseFeature(githubReleasePublishTagFeatureId, "GitHub release tag publication", publishTagArtifact, [
|
|
39
40
|
{ featureId: "git", reason: "Release publication requires Git." },
|
|
41
|
+
{
|
|
42
|
+
featureId: "changelog",
|
|
43
|
+
reason: "Release publication records changes in CHANGELOG.md.",
|
|
44
|
+
},
|
|
40
45
|
{
|
|
41
46
|
featureId: "github-repo",
|
|
42
47
|
reason: "Release publication requires GitHub.",
|
|
@@ -75,10 +80,10 @@ export const githubReleasePublishGithubFeature = releaseFeature(githubReleasePub
|
|
|
75
80
|
featureId: githubReleasePublishTagFeatureId,
|
|
76
81
|
reason: "GitHub Releases start after a release tag.",
|
|
77
82
|
}]);
|
|
78
|
-
export const githubReleasePublishNpmFeature = releaseFeature(githubReleasePublishNpmFeatureId, "GitHub npm release publication", publishNpmArtifact, [{
|
|
83
|
+
export const githubReleasePublishNpmFeature = withNpmReadmeBadge(releaseFeature(githubReleasePublishNpmFeatureId, "GitHub npm release publication", publishNpmArtifact, [{
|
|
79
84
|
featureId: githubReleasePublishTagFeatureId,
|
|
80
85
|
reason: "npm publication starts after a release tag.",
|
|
81
|
-
}]);
|
|
86
|
+
}]));
|
|
82
87
|
export const githubReleasePublisherFeatures = [
|
|
83
88
|
githubReleasePublishJsrFeature,
|
|
84
89
|
githubReleasePublishGithubFeature,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @module Download caches shared by managed CI and release workflows. */
|
|
2
|
+
import { workflowDenoVersion } from "./workflow-toolchain.js";
|
|
3
|
+
// setup-deno adds OS, architecture and job; its fallback also shares across jobs.
|
|
4
|
+
// Package version metadata is deliberately excluded from dependency cache keys.
|
|
5
|
+
export const denoCacheInputs = ` cache: true
|
|
6
|
+
cache-hash: deno-${workflowDenoVersion}-\${{ hashFiles('**/deno.lock', 'toolchain.json') }}
|
|
7
|
+
`;
|
|
8
|
+
// Frozen npm ci still validates the locks and installs with scripts disabled.
|
|
9
|
+
// Do not cache the build output or node_modules: test preparation clears them.
|
|
10
|
+
export const nativeNpmCacheStep = ` - name: Cache native test npm downloads
|
|
11
|
+
if: hashFiles('scripts/test-build/dependencies/package-lock.json') != '' && hashFiles('scripts/test-build/tools/package-lock.json') != ''
|
|
12
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
13
|
+
with:
|
|
14
|
+
path: ~/.npm
|
|
15
|
+
key: native-test-npm-\${{ runner.os }}-\${{ runner.arch }}-\${{ hashFiles('scripts/test-build/dependencies/package-lock.json', 'scripts/test-build/tools/package-lock.json', 'toolchain.json') }}
|
|
16
|
+
restore-keys: |
|
|
17
|
+
native-test-npm-\${{ runner.os }}-\${{ runner.arch }}-
|
|
18
|
+
`;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { validNpmPublishName } from "../package/npm-name.js";
|
|
2
|
+
import { inspectContribution, npmBadgeImages, } from "../readme/contribution-blocks.js";
|
|
3
|
+
import { inspectDenoConfig } from "./deno-config.js";
|
|
4
|
+
import { isObject, isReadmeTask } from "./deno-tasks.js";
|
|
5
|
+
export const npmBadgeOwner = "github-release-publish-npm";
|
|
6
|
+
export function npmBadge(name) {
|
|
7
|
+
return {
|
|
8
|
+
id: `${npmBadgeOwner}:badge`,
|
|
9
|
+
position: "badges",
|
|
10
|
+
content: `[](https://www.npmjs.com/package/${name})`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Use the same explicit Deno-config name that publication verifies in its archive. */
|
|
14
|
+
export async function npmBadgeIdentity(context) {
|
|
15
|
+
const config = await inspectDenoConfig(context);
|
|
16
|
+
return config.kind === "config" && validNpmPublishName(config.value.name)
|
|
17
|
+
? config.value.name
|
|
18
|
+
: undefined;
|
|
19
|
+
}
|
|
20
|
+
async function inspect(context) {
|
|
21
|
+
const config = await inspectDenoConfig(context);
|
|
22
|
+
const tasks = config.kind === "config" && isObject(config.value.tasks)
|
|
23
|
+
? config.value.tasks
|
|
24
|
+
: {};
|
|
25
|
+
const build = isReadmeTask(tasks.readme);
|
|
26
|
+
const path = build ? "readme/README.md" : "README.md";
|
|
27
|
+
const entry = await context.files.observe(path);
|
|
28
|
+
const name = await npmBadgeIdentity(context);
|
|
29
|
+
const text = entry.kind === "file" ? entry.content : "";
|
|
30
|
+
const desired = npmBadge(name ?? "@unknown/package");
|
|
31
|
+
const owned = await inspectContribution(text, desired);
|
|
32
|
+
const npmImages = npmBadgeImages(text);
|
|
33
|
+
const custom = owned === "absent" && npmImages.length > 0;
|
|
34
|
+
const compatibleCustom = custom && npmImages.length === 1 &&
|
|
35
|
+
text.includes(desired.content);
|
|
36
|
+
const kind = entry.kind !== "file" && entry.kind !== "absent"
|
|
37
|
+
? "custom"
|
|
38
|
+
: owned === "duplicate" || npmImages.length > 1
|
|
39
|
+
? "duplicate"
|
|
40
|
+
: custom && !compatibleCustom
|
|
41
|
+
? "custom"
|
|
42
|
+
: compatibleCustom
|
|
43
|
+
? "exact"
|
|
44
|
+
: owned;
|
|
45
|
+
return {
|
|
46
|
+
path,
|
|
47
|
+
name,
|
|
48
|
+
text,
|
|
49
|
+
kind,
|
|
50
|
+
owned,
|
|
51
|
+
compatibleCustom,
|
|
52
|
+
missingReadme: entry.kind === "absent",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function finding(state, path, observation, resolution) {
|
|
56
|
+
return {
|
|
57
|
+
state,
|
|
58
|
+
evidence: [],
|
|
59
|
+
issues: [{
|
|
60
|
+
code: "npm-readme-badge",
|
|
61
|
+
kind: "readme",
|
|
62
|
+
subject: { kind: "repository-path", identifier: path },
|
|
63
|
+
observation,
|
|
64
|
+
resolution,
|
|
65
|
+
}],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Keep publication's existing workflow checks and add badge lifecycle ownership. */
|
|
69
|
+
export function withNpmReadmeBadge(workflow) {
|
|
70
|
+
return {
|
|
71
|
+
...workflow,
|
|
72
|
+
capabilities: {
|
|
73
|
+
provides: [],
|
|
74
|
+
requires: [{
|
|
75
|
+
capabilityId: "readme",
|
|
76
|
+
reason: "npm publication includes a README version badge.",
|
|
77
|
+
}],
|
|
78
|
+
},
|
|
79
|
+
detect: async (context) => {
|
|
80
|
+
const current = await workflow.detect(context);
|
|
81
|
+
if (current.state === "ambiguous")
|
|
82
|
+
return current;
|
|
83
|
+
const badge = await inspect(context);
|
|
84
|
+
if (current.state === "disabled") {
|
|
85
|
+
if (badge.owned === "exact" || badge.owned === "stale") {
|
|
86
|
+
return finding("drifted", badge.path, `The npm publication workflow is absent but ${badge.path} retains its owned npm badge.`, `Disable --github-release-publish-npm to remove its owned badge, or enable it with --repair to restore publication.`);
|
|
87
|
+
}
|
|
88
|
+
return current;
|
|
89
|
+
}
|
|
90
|
+
if (!badge.name) {
|
|
91
|
+
return finding("ambiguous", "deno.json|deno.jsonc", "Cannot resolve the npm badge target from one explicit scoped Deno package name.", "Set name in exactly one valid deno.json or deno.jsonc to the npm package published by npm-build. The built archive must use the same name.");
|
|
92
|
+
}
|
|
93
|
+
if (badge.kind === "custom" || badge.kind === "duplicate") {
|
|
94
|
+
return finding("ambiguous", badge.path, `${badge.path} contains ${badge.kind === "duplicate" ? "duplicate" : "custom or conflicting"} npm badge content.`, `Preserve custom text, then replace the conflicting badge with ${npmBadge(badge.name).content}, or remove it before enabling npm publication.`);
|
|
95
|
+
}
|
|
96
|
+
if (current.state === "drifted")
|
|
97
|
+
return current;
|
|
98
|
+
if (badge.kind !== "exact") {
|
|
99
|
+
return finding("drifted", badge.path, `The npm version badge for ${badge.name} is ${badge.kind === "absent" ? "missing" : "outdated"} in ${badge.path}.`, `Use --repair to ${badge.kind === "absent" ? "add" : "replace"} the owned npm badge in ${badge.path} with ${npmBadge(badge.name).content}${badge.path.startsWith("readme/") ? " and rebuild README.md." : "."}`);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
state: "enabled",
|
|
103
|
+
evidence: [...current.evidence, {
|
|
104
|
+
code: "npm-readme-badge",
|
|
105
|
+
kind: "readme",
|
|
106
|
+
subject: { kind: "repository-path", identifier: badge.path },
|
|
107
|
+
observation: `${badge.path} links its ${badge.compatibleCustom ? "custom" : "owned"} npm version badge to https://www.npmjs.com/package/${badge.name}.`,
|
|
108
|
+
}],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
checkEnable: async (context) => {
|
|
112
|
+
const check = await workflow.checkEnable(context);
|
|
113
|
+
if (check.result === "blocked")
|
|
114
|
+
return check;
|
|
115
|
+
const badge = await inspect(context);
|
|
116
|
+
if (!badge.name) {
|
|
117
|
+
return blocked("Set an explicit scoped npm package name in exactly one deno.json or deno.jsonc before adding its badge.");
|
|
118
|
+
}
|
|
119
|
+
if (badge.kind === "custom" || badge.kind === "duplicate") {
|
|
120
|
+
return blocked(`Resolve custom or duplicate npm badges in ${badge.path}. Keep custom text and use exactly ${npmBadge(badge.name).content}, or remove the conflicting badge. Custom content will not be overwritten.`);
|
|
121
|
+
}
|
|
122
|
+
if (badge.missingReadme &&
|
|
123
|
+
!context.resolvedChanges.some((change) => change.enabled &&
|
|
124
|
+
["readme-static", "readme-build"].includes(change.featureId))) {
|
|
125
|
+
return blocked(`Select a README provider to create ${badge.path} for the npm badge.`);
|
|
126
|
+
}
|
|
127
|
+
if (badge.kind !== "exact" &&
|
|
128
|
+
(await workflow.detect(context)).state === "enabled" &&
|
|
129
|
+
!repairing(context)) {
|
|
130
|
+
return blocked(`Use --repair to ${badge.kind === "absent" ? "add" : "replace"} the npm badge for ${badge.name} in ${badge.path}.`);
|
|
131
|
+
}
|
|
132
|
+
return check.result === "no-op" && badge.kind !== "exact"
|
|
133
|
+
? { result: "allowed", warnings: [], preconditions: [] }
|
|
134
|
+
: check;
|
|
135
|
+
},
|
|
136
|
+
checkDisable: async (context) => {
|
|
137
|
+
const check = await workflow.checkDisable(context);
|
|
138
|
+
if (check.result !== "no-op")
|
|
139
|
+
return check;
|
|
140
|
+
const badge = await inspect(context);
|
|
141
|
+
return badge.owned === "exact" || badge.owned === "stale"
|
|
142
|
+
? { result: "allowed", warnings: [], preconditions: [] }
|
|
143
|
+
: check;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function repairing(context) {
|
|
148
|
+
return context.repair?.kind === "all-drifted" ||
|
|
149
|
+
context.repair?.kind === "features" &&
|
|
150
|
+
context.repair.featureIds.includes(npmBadgeOwner);
|
|
151
|
+
}
|
|
152
|
+
function blocked(message) {
|
|
153
|
+
return {
|
|
154
|
+
result: "blocked",
|
|
155
|
+
warnings: [],
|
|
156
|
+
blockers: [{
|
|
157
|
+
code: "npm-readme-badge",
|
|
158
|
+
message,
|
|
159
|
+
subjects: [],
|
|
160
|
+
resolution: "Resolve the stated README badge or package identity before continuing.",
|
|
161
|
+
}],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @module Compose feature-owned README guides against the final feature plans. */
|
|
2
|
+
import { npmBadge, npmBadgeIdentity, npmBadgeOwner, } from "./npm-readme-badge.js";
|
|
2
3
|
import { fileAccess } from "../repository/file-access.js";
|
|
3
4
|
import { fromFileUrl } from "@jsr/std__path";
|
|
4
5
|
import { packageImports, rewritePackageImport, } from "../readme/package-import.js";
|
|
@@ -132,9 +133,23 @@ export async function reconcileReadmePlans(context, plans) {
|
|
|
132
133
|
github,
|
|
133
134
|
cliPermissions,
|
|
134
135
|
});
|
|
136
|
+
const npmName = await npmBadgeIdentity(projected);
|
|
137
|
+
if (active(npmBadgeOwner) && npmName)
|
|
138
|
+
contributions.push(npmBadge(npmName));
|
|
139
|
+
const preserveNpmBadge = context.resolvedChanges.find((change) => change.featureId === npmBadgeOwner)
|
|
140
|
+
?.enabled !== false &&
|
|
141
|
+
(!npmName || context.detections.get(npmBadgeOwner)?.state === "ambiguous" ||
|
|
142
|
+
context.detections.get(npmBadgeOwner) === undefined);
|
|
135
143
|
const provider = build ? "readme-build" : "readme-static";
|
|
136
144
|
// Remove unchanged legacy JSR API blocks before the library adds its block.
|
|
137
|
-
const owners = [
|
|
145
|
+
const owners = [
|
|
146
|
+
provider,
|
|
147
|
+
"jsr-package",
|
|
148
|
+
"deno-cli",
|
|
149
|
+
"deno-lib",
|
|
150
|
+
npmBadgeOwner,
|
|
151
|
+
"github-ci",
|
|
152
|
+
];
|
|
138
153
|
const appended = [];
|
|
139
154
|
// File/export ownership belongs to JSR publication. README blocks stay with
|
|
140
155
|
// the feature named in their marker so per-feature commits remain possible.
|
|
@@ -144,6 +159,8 @@ export async function reconcileReadmePlans(context, plans) {
|
|
|
144
159
|
for (const owner of owners) {
|
|
145
160
|
if (owner === "github-ci" && preserveCiBadge)
|
|
146
161
|
continue;
|
|
162
|
+
if (owner === npmBadgeOwner && preserveNpmBadge)
|
|
163
|
+
continue;
|
|
147
164
|
const before = files.changes.length;
|
|
148
165
|
const current = await files.read(sourcePath);
|
|
149
166
|
if (current === undefined)
|
|
@@ -22,6 +22,6 @@ export function workflowDenoArtifact(artifact, context, observation) {
|
|
|
22
22
|
const version = validateDenoVersion(selected);
|
|
23
23
|
return {
|
|
24
24
|
...artifact,
|
|
25
|
-
content: artifact.content.replaceAll(`deno-version: ${workflowDenoVersion}\n`, `deno-version: ${version}\n`),
|
|
25
|
+
content: artifact.content.replaceAll(`deno-version: ${workflowDenoVersion}\n`, `deno-version: ${version}\n`).replaceAll(`cache-hash: deno-${workflowDenoVersion}-`, `cache-hash: deno-${version}-`),
|
|
26
26
|
};
|
|
27
27
|
}
|
|
@@ -70,18 +70,24 @@ export async function reconcileBlocks(text, desired, owner) {
|
|
|
70
70
|
if (item.position === "badges" &&
|
|
71
71
|
(item.id.startsWith("jsr-package")
|
|
72
72
|
? text.includes("[![JSR")
|
|
73
|
-
:
|
|
73
|
+
: item.id === "github-ci:badge"
|
|
74
|
+
? text.includes("[![CI]")
|
|
75
|
+
: npmBadgeImages(text).length > 0))
|
|
74
76
|
continue;
|
|
75
77
|
const block = await wrap(item);
|
|
76
78
|
if (item.position === "badges") {
|
|
77
79
|
const section = text.search(/^## /m);
|
|
78
80
|
const marker = text.indexOf("<!-- hj:readme ");
|
|
79
81
|
let at = Math.min(section < 0 ? text.length : section, marker < 0 ? text.length : marker);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const order = [
|
|
83
|
+
"jsr-package:badges",
|
|
84
|
+
"github-release-publish-npm:badge",
|
|
85
|
+
"github-ci:badge",
|
|
86
|
+
];
|
|
87
|
+
const earlier = [...text.matchAll(pattern)].filter((match) => order.indexOf(match[1]) >= 0 &&
|
|
88
|
+
order.indexOf(match[1]) < order.indexOf(item.id)).at(-1);
|
|
89
|
+
if (earlier)
|
|
90
|
+
at = earlier.index + earlier[0].length;
|
|
85
91
|
text = insert(text, at, block);
|
|
86
92
|
}
|
|
87
93
|
else {
|
|
@@ -108,3 +114,24 @@ function insert(text, at, block) {
|
|
|
108
114
|
const after = text.slice(at);
|
|
109
115
|
return before + "\n\n" + block + (after ? "\n" + after : "");
|
|
110
116
|
}
|
|
117
|
+
/** Inspect one owned contribution without treating edited bodies as generated. */
|
|
118
|
+
export async function inspectContribution(text, desired) {
|
|
119
|
+
const matches = [...text.matchAll(pattern)].filter((match) => match[1] === desired.id);
|
|
120
|
+
const markers = text.split(`<!-- hj:readme ${desired.id} `).length - 1;
|
|
121
|
+
if (markers > 1)
|
|
122
|
+
return "duplicate";
|
|
123
|
+
const match = matches[0];
|
|
124
|
+
if (!match) {
|
|
125
|
+
return text.includes(`<!-- hj:readme ${desired.id} `) ? "custom" : "absent";
|
|
126
|
+
}
|
|
127
|
+
if (await contributionHash(match[3]) !== match[2])
|
|
128
|
+
return "custom";
|
|
129
|
+
return await contributionHash(desired.content) === match[2]
|
|
130
|
+
? "exact"
|
|
131
|
+
: "stale";
|
|
132
|
+
}
|
|
133
|
+
/** Recognize npm badge images without claiming ownership of custom Markdown. */
|
|
134
|
+
export function npmBadgeImages(text) {
|
|
135
|
+
return [...text.matchAll(/!\[([^\]]*)\]\(([^)\n]+)\)/g)].filter((match) => /^npm(?: version)?$/i.test(match[1]) ||
|
|
136
|
+
match[2].startsWith("https://img.shields.io/npm/v/")).map((match) => match[0]);
|
|
137
|
+
}
|