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.
@@ -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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"}