pm-ops 2026.10.6 → 2026.10.13
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/CHANGELOG.md +14 -0
- package/README.md +48 -2
- package/dist/attestation.d.ts +2 -0
- package/dist/attestation.d.ts.map +1 -1
- package/dist/attestation.js +145 -10
- package/dist/attestation.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/merge-driver-prepare.d.ts +3 -2
- package/dist/merge-driver-prepare.d.ts.map +1 -1
- package/dist/merge-driver-prepare.js +9 -3
- package/dist/merge-driver-prepare.js.map +1 -1
- package/dist/release-completeness.d.ts +209 -0
- package/dist/release-completeness.d.ts.map +1 -0
- package/dist/release-completeness.js +402 -0
- package/dist/release-completeness.js.map +1 -0
- package/dist/release-repair.d.ts +77 -0
- package/dist/release-repair.d.ts.map +1 -0
- package/dist/release-repair.js +223 -0
- package/dist/release-repair.js.map +1 -0
- package/dist/shell-scan.d.ts +58 -1
- package/dist/shell-scan.d.ts.map +1 -1
- package/dist/shell-scan.js +207 -33
- package/dist/shell-scan.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +11 -6
- package/templates/prepare-merge-driver.ts +50 -41
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { VerifierResult } from "./shell-scan.ts";
|
|
2
|
+
/** A release tag matches a calendar version, optionally with a `-N` suffix. */
|
|
3
|
+
export declare const RELEASE_TAG_PATTERN: RegExp;
|
|
4
|
+
/**
|
|
5
|
+
* Decide whether a tag is a release tag rather than an arbitrary ref.
|
|
6
|
+
*
|
|
7
|
+
* Only release tags are anchored: a non-release tag is not part of the release
|
|
8
|
+
* triple contract and is ignored, so an unrelated tag cannot fail this gate.
|
|
9
|
+
*
|
|
10
|
+
* @param tag - A git tag name.
|
|
11
|
+
* @returns True when the tag spells a calendar release version.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isReleaseTag(tag: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Convert a release tag to the npm version string it corresponds to.
|
|
16
|
+
*
|
|
17
|
+
* A tag zero-pads the month and day (`v2026.08.31`) while npm stores them
|
|
18
|
+
* without padding (`2026.8.31`), and both may carry a `-N` suffix
|
|
19
|
+
* (`v2026.07.14-1` -> `2026.7.14-1`). The components are parsed as numbers so
|
|
20
|
+
* the leading zeros drop, which is also what makes the two spellings compare
|
|
21
|
+
* equal.
|
|
22
|
+
*
|
|
23
|
+
* @param tag - A release tag name, with or without a leading `v`.
|
|
24
|
+
* @returns The npm version string for that tag.
|
|
25
|
+
*/
|
|
26
|
+
export declare function tagToNpmVersion(tag: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Convert a published npm version to the release tag it corresponds to.
|
|
29
|
+
*
|
|
30
|
+
* This is the inverse of {@link tagToNpmVersion}: it re-pads the month and day
|
|
31
|
+
* to two digits and restores the leading `v`, so `2026.7.14-1` becomes
|
|
32
|
+
* `v2026.07.14-1`. The year is already four digits, so padding to two leaves it
|
|
33
|
+
* unchanged.
|
|
34
|
+
*
|
|
35
|
+
* @param version - A published npm version string.
|
|
36
|
+
* @returns The release tag name for that version.
|
|
37
|
+
*/
|
|
38
|
+
export declare function npmVersionToTag(version: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Extract the repository URL from a `package.json` `repository` field.
|
|
41
|
+
*
|
|
42
|
+
* The field may be a shorthand string or an object with a `url` key, and both
|
|
43
|
+
* forms occur in this fleet, so the two are reconciled here rather than at
|
|
44
|
+
* every caller.
|
|
45
|
+
*
|
|
46
|
+
* @param repo - The raw `repository` value from a manifest.
|
|
47
|
+
* @returns The URL string, or an empty string when none is declared.
|
|
48
|
+
*/
|
|
49
|
+
export declare function repositoryUrl(repo: string | {
|
|
50
|
+
url?: string;
|
|
51
|
+
} | undefined): string;
|
|
52
|
+
/**
|
|
53
|
+
* Derive the `owner/name` slug from a repository URL.
|
|
54
|
+
*
|
|
55
|
+
* Accepts every spelling npm and git record: `git+https://github.com/o/n.git`,
|
|
56
|
+
* `https://github.com/o/n`, `git@github.com:o/n.git`, and the `github:o/n`
|
|
57
|
+
* shorthand. A URL that names no GitHub host yields an empty string so the
|
|
58
|
+
* caller can fail closed rather than querying the wrong registry.
|
|
59
|
+
*
|
|
60
|
+
* The host check requires `hostname === "github.com"` exactly rather than a
|
|
61
|
+
* substring test. A regex that merely looks for `github` anywhere in the URL
|
|
62
|
+
* accepts `https://notgithub.com/owner/name` and
|
|
63
|
+
* `https://evil.example/github:owner/name`, directing the verifier at the
|
|
64
|
+
* wrong repository. The HTTPS form is parsed with `new URL` so the hostname is
|
|
65
|
+
* compared precisely; the SSH (`git@github.com:owner/name`) and shorthand
|
|
66
|
+
* (`github:owner/name`) forms are not valid URLs and are matched with anchored
|
|
67
|
+
* patterns that require the `github.com` host literally.
|
|
68
|
+
*
|
|
69
|
+
* @param url - The repository URL to parse.
|
|
70
|
+
* @returns The `owner/name` slug, or an empty string when no GitHub host is named.
|
|
71
|
+
*/
|
|
72
|
+
export declare function repoSlugFromUrl(url: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Read the package name declared in a manifest.
|
|
75
|
+
*
|
|
76
|
+
* @param root - Repository root containing `package.json`.
|
|
77
|
+
* @returns The `name` field, or an empty string when the manifest lacks one.
|
|
78
|
+
*/
|
|
79
|
+
export declare function packageNameFromManifest(root: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Split command output into non-empty, trimmed lines.
|
|
82
|
+
*
|
|
83
|
+
* Used for `git tag` and `gh api ... releases` output, both of which are one entry
|
|
84
|
+
* per line. A trailing newline produces no empty entry.
|
|
85
|
+
*
|
|
86
|
+
* @param output - Raw command output.
|
|
87
|
+
* @returns The non-empty trimmed lines.
|
|
88
|
+
*/
|
|
89
|
+
export declare function parseLines(output: string): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Parse the complete JSON array emitted by `npm view <pkg> versions --json`.
|
|
92
|
+
*
|
|
93
|
+
* Reject malformed JSON and unreadable entries instead of dropping them. The
|
|
94
|
+
* caller must report an unavailable inventory separately from an empty one;
|
|
95
|
+
* otherwise a registry failure falsely describes existing releases as absent.
|
|
96
|
+
*
|
|
97
|
+
* @param output - Raw `npm view ... --json` output.
|
|
98
|
+
* @returns Every version string from a successfully decoded inventory.
|
|
99
|
+
* @throws When the response is not JSON or not an array of non-empty strings.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parseNpmVersions(output: string): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Audit three release lists for completeness.
|
|
104
|
+
*
|
|
105
|
+
* A release tag is the anchor. For each release tag the check requires both a
|
|
106
|
+
* matching published npm version and a matching GitHub Release, and it refuses
|
|
107
|
+
* orphan npm versions and orphan GitHub Releases whose corresponding tag is
|
|
108
|
+
* missing, so a triple is incomplete in any direction. Non-release tags are
|
|
109
|
+
* ignored: they are not part of the release contract.
|
|
110
|
+
*
|
|
111
|
+
* An empty release-tag set is a failure rather than a pass: a scan that finds
|
|
112
|
+
* no release tags is either pointed at the wrong repository or has outlived
|
|
113
|
+
* the releases it guards, and both look identical to a clean result unless
|
|
114
|
+
* said out loud.
|
|
115
|
+
*
|
|
116
|
+
* @param tags - Git tag names.
|
|
117
|
+
* @param npmVersions - Published npm version strings.
|
|
118
|
+
* @param githubReleases - GitHub Release tag names.
|
|
119
|
+
* @returns Failures and per-list notes.
|
|
120
|
+
*/
|
|
121
|
+
export declare function auditReleaseCompleteness(tags: readonly string[], npmVersions: readonly string[], githubReleases: readonly string[]): VerifierResult;
|
|
122
|
+
/** A source of the three release lists, injectable so the suite is hermetic. */
|
|
123
|
+
export interface CompletenessFetcher {
|
|
124
|
+
/** Git tag names in the repository at `root`. */
|
|
125
|
+
tags(root: string): string[];
|
|
126
|
+
/** The `origin` remote URL of the repository at `root`. */
|
|
127
|
+
remoteUrl(root: string): string;
|
|
128
|
+
/** Published npm version strings for `pkgName`. */
|
|
129
|
+
npmVersions(pkgName: string): string[];
|
|
130
|
+
/** GitHub Release tag names for the `owner/name` slug. */
|
|
131
|
+
githubReleases(slug: string): string[];
|
|
132
|
+
/**
|
|
133
|
+
* Whether the GitHub Release of one tag exists, read by its tag. The list
|
|
134
|
+
* endpoint can lag a release created seconds earlier; this read confirms a
|
|
135
|
+
* tag the list lacks before the audit calls it missing.
|
|
136
|
+
*/
|
|
137
|
+
githubReleaseForTag(slug: string, tag: string): boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Run a child process and return its stdout as a string.
|
|
141
|
+
*
|
|
142
|
+
* The default executor used by {@link makeFetcher}; exported so the suite can
|
|
143
|
+
* cover the real {@link execFileSync} call with a local command rather than the
|
|
144
|
+
* networked `npm`/`gh` invocations the fetcher builds.
|
|
145
|
+
*
|
|
146
|
+
* @param command - The program to run.
|
|
147
|
+
* @param args - Arguments to pass.
|
|
148
|
+
* @param options - Options such as `cwd`.
|
|
149
|
+
* @returns The process stdout, decoded as UTF-8.
|
|
150
|
+
*/
|
|
151
|
+
export declare function defaultExec(command: string, args: string[], options?: {
|
|
152
|
+
cwd?: string;
|
|
153
|
+
}): string;
|
|
154
|
+
/**
|
|
155
|
+
* Fetch all GitHub Release tag names for a slug, paginating through the API.
|
|
156
|
+
*
|
|
157
|
+
* `gh release list -L N` caps at N entries, so a repository with more than N
|
|
158
|
+
* releases silently omits the older ones. This function pages through the
|
|
159
|
+
* GitHub releases API 100 at a time until a page returns fewer than a full
|
|
160
|
+
* page, collecting every release so the completeness audit sees the full
|
|
161
|
+
* history rather than a truncated prefix. Raising a fixed cap reproduces the
|
|
162
|
+
* same defect later; paginating until the API reports no further pages does
|
|
163
|
+
* not.
|
|
164
|
+
*
|
|
165
|
+
* @param exec - The executor to run `gh` commands through.
|
|
166
|
+
* @param slug - The `owner/name` slug to query.
|
|
167
|
+
* @returns All GitHub Release tag names, in API order.
|
|
168
|
+
*/
|
|
169
|
+
export declare function fetchGithubReleasesPaginated(exec: (command: string, args: string[], options: {
|
|
170
|
+
cwd?: string;
|
|
171
|
+
}) => string, slug: string): string[];
|
|
172
|
+
/**
|
|
173
|
+
* Build a fetcher from an executor.
|
|
174
|
+
*
|
|
175
|
+
* Separating the executor from the parsing lets the suite cover every command
|
|
176
|
+
* construction and parser with an injected executor, while {@link defaultExec}
|
|
177
|
+
* is covered separately by a local process. The fetcher the release gate uses
|
|
178
|
+
* is built from {@link defaultExec}; the suite builds one from a mock.
|
|
179
|
+
*
|
|
180
|
+
* @param exec - The executor to run commands through.
|
|
181
|
+
* @returns A fetcher whose methods gather one release list each.
|
|
182
|
+
*/
|
|
183
|
+
export declare function makeFetcher(exec: (command: string, args: string[], options: {
|
|
184
|
+
cwd?: string;
|
|
185
|
+
}) => string): CompletenessFetcher;
|
|
186
|
+
/** The fetcher the release gate uses, built from the real executor. */
|
|
187
|
+
export declare const realFetcher: CompletenessFetcher;
|
|
188
|
+
/**
|
|
189
|
+
* Gather the three release lists and audit them only when all reads succeed.
|
|
190
|
+
*
|
|
191
|
+
* A failed read names its source and makes no absence claims from partial data.
|
|
192
|
+
* Missing package or repository identity is rejected before any registry read.
|
|
193
|
+
* A release tag missing from the GitHub Release list is confirmed by a read of
|
|
194
|
+
* that tag's release, because the list lags a release created moments earlier.
|
|
195
|
+
*
|
|
196
|
+
* @param root - Repository root to verify.
|
|
197
|
+
* @param fetcher - The source of the three lists.
|
|
198
|
+
* @returns Failures and notes for the repository.
|
|
199
|
+
*/
|
|
200
|
+
export declare function verify(root: string, fetcher: CompletenessFetcher): VerifierResult;
|
|
201
|
+
/**
|
|
202
|
+
* Print a result and set a failing exit code when it failed.
|
|
203
|
+
*
|
|
204
|
+
* @param result - The audit outcome.
|
|
205
|
+
* @param write - Sink for the report lines.
|
|
206
|
+
* @param exit - Called with the process exit code when there were failures.
|
|
207
|
+
*/
|
|
208
|
+
export declare function report(result: VerifierResult, write: (line: string) => void, exit: (code: number) => void): void;
|
|
209
|
+
//# sourceMappingURL=release-completeness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-completeness.d.ts","sourceRoot":"","sources":["../release-completeness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proves every release tag of this package is a complete release triple: a git
|
|
3
|
+
* tag, a published npm version, and a GitHub Release, all corresponding to one
|
|
4
|
+
* another.
|
|
5
|
+
*
|
|
6
|
+
* The release workflow tags a commit and publishes to npm in the same job, but
|
|
7
|
+
* the GitHub Release is a separate later step. When an earlier step failed -
|
|
8
|
+
* as the bun-install verification did for 14 days from 2026-08-17 onward - the
|
|
9
|
+
* job aborted before `Create GitHub release` ran, so tags and npm versions
|
|
10
|
+
* accumulated with no GitHub Release and nothing reported the gap. The
|
|
11
|
+
* publish-attestation and changelog-date gates both passed: the artifact was
|
|
12
|
+
* attested and the heading was dated correctly, yet the release was only
|
|
13
|
+
* two-thirds complete. This check closes that hole by requiring the third
|
|
14
|
+
* member of every release triple to exist.
|
|
15
|
+
*
|
|
16
|
+
* The analysis is separated from the I/O so the rules are driven by the suite
|
|
17
|
+
* against injected lists rather than only against this repository, which
|
|
18
|
+
* happens to satisfy them. A release tag is the anchor: for each one the
|
|
19
|
+
* check requires both a matching published npm version and a matching GitHub
|
|
20
|
+
* Release, and it also refuses orphan npm versions and orphan GitHub Releases
|
|
21
|
+
* whose corresponding tag is missing, so a triple is incomplete in any
|
|
22
|
+
* direction.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
import { execFileSync } from "node:child_process";
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
import { resolve } from "node:path";
|
|
29
|
+
/** A release tag matches a calendar version, optionally with a `-N` suffix. */
|
|
30
|
+
export const RELEASE_TAG_PATTERN = /^v\d{4}\.\d{2}\.\d{2}(-\d+)?$/;
|
|
31
|
+
/**
|
|
32
|
+
* Decide whether a tag is a release tag rather than an arbitrary ref.
|
|
33
|
+
*
|
|
34
|
+
* Only release tags are anchored: a non-release tag is not part of the release
|
|
35
|
+
* triple contract and is ignored, so an unrelated tag cannot fail this gate.
|
|
36
|
+
*
|
|
37
|
+
* @param tag - A git tag name.
|
|
38
|
+
* @returns True when the tag spells a calendar release version.
|
|
39
|
+
*/
|
|
40
|
+
export function isReleaseTag(tag) {
|
|
41
|
+
return RELEASE_TAG_PATTERN.test(tag);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a release tag to the npm version string it corresponds to.
|
|
45
|
+
*
|
|
46
|
+
* A tag zero-pads the month and day (`v2026.08.31`) while npm stores them
|
|
47
|
+
* without padding (`2026.8.31`), and both may carry a `-N` suffix
|
|
48
|
+
* (`v2026.07.14-1` -> `2026.7.14-1`). The components are parsed as numbers so
|
|
49
|
+
* the leading zeros drop, which is also what makes the two spellings compare
|
|
50
|
+
* equal.
|
|
51
|
+
*
|
|
52
|
+
* @param tag - A release tag name, with or without a leading `v`.
|
|
53
|
+
* @returns The npm version string for that tag.
|
|
54
|
+
*/
|
|
55
|
+
export function tagToNpmVersion(tag) {
|
|
56
|
+
const core = tag.startsWith("v") ? tag.slice(1) : tag;
|
|
57
|
+
const dash = core.indexOf("-");
|
|
58
|
+
const base = dash >= 0 ? core.slice(0, dash) : core;
|
|
59
|
+
const suffix = dash >= 0 ? core.slice(dash) : "";
|
|
60
|
+
const parts = base.split(".").map((part) => String(Number(part)));
|
|
61
|
+
return parts.join(".") + suffix;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convert a published npm version to the release tag it corresponds to.
|
|
65
|
+
*
|
|
66
|
+
* This is the inverse of {@link tagToNpmVersion}: it re-pads the month and day
|
|
67
|
+
* to two digits and restores the leading `v`, so `2026.7.14-1` becomes
|
|
68
|
+
* `v2026.07.14-1`. The year is already four digits, so padding to two leaves it
|
|
69
|
+
* unchanged.
|
|
70
|
+
*
|
|
71
|
+
* @param version - A published npm version string.
|
|
72
|
+
* @returns The release tag name for that version.
|
|
73
|
+
*/
|
|
74
|
+
export function npmVersionToTag(version) {
|
|
75
|
+
const dash = version.indexOf("-");
|
|
76
|
+
const base = dash >= 0 ? version.slice(0, dash) : version;
|
|
77
|
+
const suffix = dash >= 0 ? version.slice(dash) : "";
|
|
78
|
+
const parts = base.split(".").map((part) => part.padStart(2, "0"));
|
|
79
|
+
return `v${parts.join(".")}${suffix}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract the repository URL from a `package.json` `repository` field.
|
|
83
|
+
*
|
|
84
|
+
* The field may be a shorthand string or an object with a `url` key, and both
|
|
85
|
+
* forms occur in this fleet, so the two are reconciled here rather than at
|
|
86
|
+
* every caller.
|
|
87
|
+
*
|
|
88
|
+
* @param repo - The raw `repository` value from a manifest.
|
|
89
|
+
* @returns The URL string, or an empty string when none is declared.
|
|
90
|
+
*/
|
|
91
|
+
export function repositoryUrl(repo) {
|
|
92
|
+
if (typeof repo === "string")
|
|
93
|
+
return repo;
|
|
94
|
+
return repo?.url ?? "";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Derive the `owner/name` slug from a repository URL.
|
|
98
|
+
*
|
|
99
|
+
* Accepts every spelling npm and git record: `git+https://github.com/o/n.git`,
|
|
100
|
+
* `https://github.com/o/n`, `git@github.com:o/n.git`, and the `github:o/n`
|
|
101
|
+
* shorthand. A URL that names no GitHub host yields an empty string so the
|
|
102
|
+
* caller can fail closed rather than querying the wrong registry.
|
|
103
|
+
*
|
|
104
|
+
* The host check requires `hostname === "github.com"` exactly rather than a
|
|
105
|
+
* substring test. A regex that merely looks for `github` anywhere in the URL
|
|
106
|
+
* accepts `https://notgithub.com/owner/name` and
|
|
107
|
+
* `https://evil.example/github:owner/name`, directing the verifier at the
|
|
108
|
+
* wrong repository. The HTTPS form is parsed with `new URL` so the hostname is
|
|
109
|
+
* compared precisely; the SSH (`git@github.com:owner/name`) and shorthand
|
|
110
|
+
* (`github:owner/name`) forms are not valid URLs and are matched with anchored
|
|
111
|
+
* patterns that require the `github.com` host literally.
|
|
112
|
+
*
|
|
113
|
+
* @param url - The repository URL to parse.
|
|
114
|
+
* @returns The `owner/name` slug, or an empty string when no GitHub host is named.
|
|
115
|
+
*/
|
|
116
|
+
export function repoSlugFromUrl(url) {
|
|
117
|
+
// Shorthand form: `github:owner/name`. Checked before URL parsing because
|
|
118
|
+
// `new URL("github:owner/name")` parses `github` as the protocol and an
|
|
119
|
+
// empty hostname, which would fall through to the URL branch and return ""
|
|
120
|
+
// for a valid shorthand.
|
|
121
|
+
const shorthand = /^github:([^/]+\/[^/]+?)(?:\.git)?\/?$/i.exec(url);
|
|
122
|
+
if (shorthand)
|
|
123
|
+
return shorthand[1];
|
|
124
|
+
// SSH form: `git@github.com:owner/name.git`. Not a URL, so it is matched
|
|
125
|
+
// with an anchored pattern that requires `github.com` literally.
|
|
126
|
+
const ssh = /^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?\/?$/i.exec(url);
|
|
127
|
+
if (ssh)
|
|
128
|
+
return ssh[1];
|
|
129
|
+
// HTTPS form: `git+https://github.com/owner/name.git` etc. The `git+`
|
|
130
|
+
// prefix is stripped so `new URL` can parse the remaining `https://` URL.
|
|
131
|
+
// The hostname is compared exactly — a substring test accepts hostile hosts
|
|
132
|
+
// such as `notgithub.com`.
|
|
133
|
+
try {
|
|
134
|
+
const parsed = new URL(url.replace(/^git\+/i, ""));
|
|
135
|
+
if (parsed.hostname === "github.com") {
|
|
136
|
+
const path = /^\/([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(parsed.pathname);
|
|
137
|
+
if (path)
|
|
138
|
+
return path[1];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Not a valid URL — neither the SSH nor shorthand branches matched, so
|
|
143
|
+
// there is no GitHub slug to extract.
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Read the package name declared in a manifest.
|
|
149
|
+
*
|
|
150
|
+
* @param root - Repository root containing `package.json`.
|
|
151
|
+
* @returns The `name` field, or an empty string when the manifest lacks one.
|
|
152
|
+
*/
|
|
153
|
+
export function packageNameFromManifest(root) {
|
|
154
|
+
const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf-8"));
|
|
155
|
+
return pkg.name ?? "";
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Split command output into non-empty, trimmed lines.
|
|
159
|
+
*
|
|
160
|
+
* Used for `git tag` and `gh api ... releases` output, both of which are one entry
|
|
161
|
+
* per line. A trailing newline produces no empty entry.
|
|
162
|
+
*
|
|
163
|
+
* @param output - Raw command output.
|
|
164
|
+
* @returns The non-empty trimmed lines.
|
|
165
|
+
*/
|
|
166
|
+
export function parseLines(output) {
|
|
167
|
+
return output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Parse the complete JSON array emitted by `npm view <pkg> versions --json`.
|
|
171
|
+
*
|
|
172
|
+
* Reject malformed JSON and unreadable entries instead of dropping them. The
|
|
173
|
+
* caller must report an unavailable inventory separately from an empty one;
|
|
174
|
+
* otherwise a registry failure falsely describes existing releases as absent.
|
|
175
|
+
*
|
|
176
|
+
* @param output - Raw `npm view ... --json` output.
|
|
177
|
+
* @returns Every version string from a successfully decoded inventory.
|
|
178
|
+
* @throws When the response is not JSON or not an array of non-empty strings.
|
|
179
|
+
*/
|
|
180
|
+
export function parseNpmVersions(output) {
|
|
181
|
+
const parsed = JSON.parse(output);
|
|
182
|
+
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
183
|
+
throw new Error("expected an array of non-empty version strings");
|
|
184
|
+
}
|
|
185
|
+
return parsed;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Audit three release lists for completeness.
|
|
189
|
+
*
|
|
190
|
+
* A release tag is the anchor. For each release tag the check requires both a
|
|
191
|
+
* matching published npm version and a matching GitHub Release, and it refuses
|
|
192
|
+
* orphan npm versions and orphan GitHub Releases whose corresponding tag is
|
|
193
|
+
* missing, so a triple is incomplete in any direction. Non-release tags are
|
|
194
|
+
* ignored: they are not part of the release contract.
|
|
195
|
+
*
|
|
196
|
+
* An empty release-tag set is a failure rather than a pass: a scan that finds
|
|
197
|
+
* no release tags is either pointed at the wrong repository or has outlived
|
|
198
|
+
* the releases it guards, and both look identical to a clean result unless
|
|
199
|
+
* said out loud.
|
|
200
|
+
*
|
|
201
|
+
* @param tags - Git tag names.
|
|
202
|
+
* @param npmVersions - Published npm version strings.
|
|
203
|
+
* @param githubReleases - GitHub Release tag names.
|
|
204
|
+
* @returns Failures and per-list notes.
|
|
205
|
+
*/
|
|
206
|
+
export function auditReleaseCompleteness(tags, npmVersions, githubReleases) {
|
|
207
|
+
const releaseTags = tags.filter(isReleaseTag);
|
|
208
|
+
const tagSet = new Set(releaseTags);
|
|
209
|
+
const npmSet = new Set(npmVersions);
|
|
210
|
+
const ghSet = new Set(githubReleases);
|
|
211
|
+
const failures = [];
|
|
212
|
+
if (releaseTags.length === 0) {
|
|
213
|
+
failures.push("no release tags found - the scan is looking in the wrong place");
|
|
214
|
+
}
|
|
215
|
+
for (const tag of releaseTags) {
|
|
216
|
+
const version = tagToNpmVersion(tag);
|
|
217
|
+
if (!npmSet.has(version)) {
|
|
218
|
+
failures.push(`release tag ${tag} has no corresponding published npm version (expected ${version})`);
|
|
219
|
+
}
|
|
220
|
+
if (!ghSet.has(tag)) {
|
|
221
|
+
failures.push(`release tag ${tag} has no corresponding GitHub Release`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const version of npmVersions) {
|
|
225
|
+
const tag = npmVersionToTag(version);
|
|
226
|
+
if (!tagSet.has(tag)) {
|
|
227
|
+
failures.push(`published npm version ${version} has no corresponding release tag (expected ${tag})`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const release of githubReleases) {
|
|
231
|
+
if (!tagSet.has(release)) {
|
|
232
|
+
failures.push(`GitHub Release ${release} has no corresponding release tag`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const notes = [];
|
|
236
|
+
if (failures.length === 0) {
|
|
237
|
+
notes.push(`ok - ${releaseTags.length} release tag(s), each with a published npm version and a GitHub Release`);
|
|
238
|
+
}
|
|
239
|
+
return { failures, notes };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Run a child process and return its stdout as a string.
|
|
243
|
+
*
|
|
244
|
+
* The default executor used by {@link makeFetcher}; exported so the suite can
|
|
245
|
+
* cover the real {@link execFileSync} call with a local command rather than the
|
|
246
|
+
* networked `npm`/`gh` invocations the fetcher builds.
|
|
247
|
+
*
|
|
248
|
+
* @param command - The program to run.
|
|
249
|
+
* @param args - Arguments to pass.
|
|
250
|
+
* @param options - Options such as `cwd`.
|
|
251
|
+
* @returns The process stdout, decoded as UTF-8.
|
|
252
|
+
*/
|
|
253
|
+
export function defaultExec(command, args, options = {}) {
|
|
254
|
+
return execFileSync(command, args, { encoding: "utf-8", ...options });
|
|
255
|
+
}
|
|
256
|
+
/** Page size for GitHub Release API pagination. The API caps at 100. */
|
|
257
|
+
const GITHUB_RELEASES_PER_PAGE = 100;
|
|
258
|
+
/**
|
|
259
|
+
* Fetch all GitHub Release tag names for a slug, paginating through the API.
|
|
260
|
+
*
|
|
261
|
+
* `gh release list -L N` caps at N entries, so a repository with more than N
|
|
262
|
+
* releases silently omits the older ones. This function pages through the
|
|
263
|
+
* GitHub releases API 100 at a time until a page returns fewer than a full
|
|
264
|
+
* page, collecting every release so the completeness audit sees the full
|
|
265
|
+
* history rather than a truncated prefix. Raising a fixed cap reproduces the
|
|
266
|
+
* same defect later; paginating until the API reports no further pages does
|
|
267
|
+
* not.
|
|
268
|
+
*
|
|
269
|
+
* @param exec - The executor to run `gh` commands through.
|
|
270
|
+
* @param slug - The `owner/name` slug to query.
|
|
271
|
+
* @returns All GitHub Release tag names, in API order.
|
|
272
|
+
*/
|
|
273
|
+
export function fetchGithubReleasesPaginated(exec, slug) {
|
|
274
|
+
const releases = [];
|
|
275
|
+
let page = 1;
|
|
276
|
+
while (true) {
|
|
277
|
+
// `--method GET` is load-bearing, not decoration: `gh api` switches to POST
|
|
278
|
+
// as soon as any `-f` parameter is present, so without it this "read" posts
|
|
279
|
+
// to the create-a-release endpoint. Verified against the live API — it
|
|
280
|
+
// returns `422 "tag_name" wasn't supplied`, i.e. the request was understood
|
|
281
|
+
// as an attempt to CREATE a release, and the audit fails outright.
|
|
282
|
+
const output = exec("gh", ["api", `repos/${slug}/releases`, "--method", "GET", "-f", `per_page=${GITHUB_RELEASES_PER_PAGE}`, "-f", `page=${page}`, "--jq", ".[].tag_name"], {});
|
|
283
|
+
const pageReleases = parseLines(output);
|
|
284
|
+
if (pageReleases.length === 0)
|
|
285
|
+
break;
|
|
286
|
+
releases.push(...pageReleases);
|
|
287
|
+
if (pageReleases.length < GITHUB_RELEASES_PER_PAGE)
|
|
288
|
+
break;
|
|
289
|
+
page++;
|
|
290
|
+
}
|
|
291
|
+
return releases;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Build a fetcher from an executor.
|
|
295
|
+
*
|
|
296
|
+
* Separating the executor from the parsing lets the suite cover every command
|
|
297
|
+
* construction and parser with an injected executor, while {@link defaultExec}
|
|
298
|
+
* is covered separately by a local process. The fetcher the release gate uses
|
|
299
|
+
* is built from {@link defaultExec}; the suite builds one from a mock.
|
|
300
|
+
*
|
|
301
|
+
* @param exec - The executor to run commands through.
|
|
302
|
+
* @returns A fetcher whose methods gather one release list each.
|
|
303
|
+
*/
|
|
304
|
+
export function makeFetcher(exec) {
|
|
305
|
+
return {
|
|
306
|
+
tags(root) {
|
|
307
|
+
return parseLines(exec("git", ["tag"], { cwd: root }));
|
|
308
|
+
},
|
|
309
|
+
remoteUrl(root) {
|
|
310
|
+
return exec("git", ["remote", "get-url", "origin"], { cwd: root }).trim();
|
|
311
|
+
},
|
|
312
|
+
npmVersions(pkgName) {
|
|
313
|
+
return parseNpmVersions(exec("npm", ["view", pkgName, "versions", "--json"], {}));
|
|
314
|
+
},
|
|
315
|
+
githubReleases(slug) {
|
|
316
|
+
return fetchGithubReleasesPaginated(exec, slug);
|
|
317
|
+
},
|
|
318
|
+
githubReleaseForTag(slug, tag) {
|
|
319
|
+
let answered;
|
|
320
|
+
try {
|
|
321
|
+
answered = exec("gh", ["api", `repos/${slug}/releases/tags/${tag}`, "--method", "GET", "--jq", ".tag_name"], {}).trim();
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
// Only a 404 answers "no such release"; any other failure is an unreadable source.
|
|
325
|
+
if (error instanceof Error && error.message.includes("HTTP 404"))
|
|
326
|
+
return false;
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
// A successful read that names another tag (or none) is unreadable, not absent.
|
|
330
|
+
if (answered !== tag)
|
|
331
|
+
throw new Error(`the release read for ${tag} answered tag "${answered}"`);
|
|
332
|
+
return true;
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** The fetcher the release gate uses, built from the real executor. */
|
|
337
|
+
export const realFetcher = makeFetcher(defaultExec);
|
|
338
|
+
/**
|
|
339
|
+
* Gather the three release lists and audit them only when all reads succeed.
|
|
340
|
+
*
|
|
341
|
+
* A failed read names its source and makes no absence claims from partial data.
|
|
342
|
+
* Missing package or repository identity is rejected before any registry read.
|
|
343
|
+
* A release tag missing from the GitHub Release list is confirmed by a read of
|
|
344
|
+
* that tag's release, because the list lags a release created moments earlier.
|
|
345
|
+
*
|
|
346
|
+
* @param root - Repository root to verify.
|
|
347
|
+
* @param fetcher - The source of the three lists.
|
|
348
|
+
* @returns Failures and notes for the repository.
|
|
349
|
+
*/
|
|
350
|
+
export function verify(root, fetcher) {
|
|
351
|
+
let source = "package manifest";
|
|
352
|
+
try {
|
|
353
|
+
const pkgName = packageNameFromManifest(root);
|
|
354
|
+
if (!pkgName)
|
|
355
|
+
throw new Error("package name is missing");
|
|
356
|
+
source = "origin repository";
|
|
357
|
+
const slug = repoSlugFromUrl(fetcher.remoteUrl(root));
|
|
358
|
+
if (!slug)
|
|
359
|
+
throw new Error("origin does not identify a GitHub repository");
|
|
360
|
+
source = "git tags";
|
|
361
|
+
const tags = fetcher.tags(root);
|
|
362
|
+
source = "npm versions";
|
|
363
|
+
const npmVersions = fetcher.npmVersions(pkgName);
|
|
364
|
+
source = "GitHub Releases";
|
|
365
|
+
const listed = fetcher.githubReleases(slug);
|
|
366
|
+
// The list endpoint lags a release created moments before: it missed
|
|
367
|
+
// pm-ops v2026.09.28, created in the same second this audit ran after it.
|
|
368
|
+
// A release tag the list lacks is read by its tag before it counts as missing.
|
|
369
|
+
const listedSet = new Set(listed);
|
|
370
|
+
const confirmed = tags.filter((tag) => isReleaseTag(tag) && !listedSet.has(tag) && fetcher.githubReleaseForTag(slug, tag));
|
|
371
|
+
return auditReleaseCompleteness(tags, npmVersions, [...listed, ...confirmed]);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
return {
|
|
375
|
+
failures: [`unable to determine release completeness while reading ${source}: ${error instanceof Error ? error.message : String(error)}`],
|
|
376
|
+
notes: [],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Print a result and set a failing exit code when it failed.
|
|
382
|
+
*
|
|
383
|
+
* @param result - The audit outcome.
|
|
384
|
+
* @param write - Sink for the report lines.
|
|
385
|
+
* @param exit - Called with the process exit code when there were failures.
|
|
386
|
+
*/
|
|
387
|
+
export function report(result, write, exit) {
|
|
388
|
+
const lines = [
|
|
389
|
+
...result.notes,
|
|
390
|
+
...result.failures.map((failure) => `FAIL - ${failure}`),
|
|
391
|
+
];
|
|
392
|
+
for (const line of lines)
|
|
393
|
+
write(line);
|
|
394
|
+
const failureCount = result.failures.length;
|
|
395
|
+
if (failureCount === 0) {
|
|
396
|
+
write("verify-release-completeness: every release tag has a published npm version and a GitHub Release.");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
write(`verify-release-completeness: ${failureCount} failure(s).`);
|
|
400
|
+
exit(1);
|
|
401
|
+
}
|
|
402
|
+
//# sourceMappingURL=release-completeness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-completeness.js","sourceRoot":"","sources":["../release-completeness.ts"],"names":[],"mappings":"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"}
|