@gjsify/cli 0.51.1 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/cli.gjs.mjs +539 -493
  2. package/lib/actions/build.d.ts +22 -0
  3. package/lib/actions/build.js +37 -9
  4. package/lib/bundler-pick.d.ts +37 -1
  5. package/lib/bundler-pick.js +108 -17
  6. package/lib/cli-app.js +7 -2
  7. package/lib/commands/affected-classify.d.ts +7 -1
  8. package/lib/commands/affected-classify.js +66 -9
  9. package/lib/commands/affected-script-refs.d.ts +20 -0
  10. package/lib/commands/affected-script-refs.js +218 -0
  11. package/lib/commands/affected.js +24 -2
  12. package/lib/commands/env.d.ts +31 -0
  13. package/lib/commands/env.js +114 -0
  14. package/lib/commands/exec.d.ts +10 -0
  15. package/lib/commands/exec.js +208 -0
  16. package/lib/commands/flatpak/diff.js +8 -2
  17. package/lib/commands/flatpak/init.js +4 -1
  18. package/lib/commands/flatpak/sources.js +4 -3
  19. package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
  20. package/lib/commands/flatpak/sync-flathub.js +149 -7
  21. package/lib/commands/flatpak/utils.d.ts +10 -0
  22. package/lib/commands/flatpak/utils.js +10 -0
  23. package/lib/commands/foreach.js +5 -97
  24. package/lib/commands/format.js +26 -6
  25. package/lib/commands/index.d.ts +4 -0
  26. package/lib/commands/index.js +4 -0
  27. package/lib/commands/install.d.ts +2 -2
  28. package/lib/commands/install.js +81 -5
  29. package/lib/commands/link.d.ts +13 -0
  30. package/lib/commands/link.js +243 -0
  31. package/lib/commands/onboard.js +18 -2
  32. package/lib/commands/publish.d.ts +34 -0
  33. package/lib/commands/publish.js +153 -8
  34. package/lib/commands/self-update.js +2 -1
  35. package/lib/commands/ship.js +19 -1
  36. package/lib/commands/test.d.ts +18 -9
  37. package/lib/commands/test.js +56 -11
  38. package/lib/commands/tsc.js +4 -4
  39. package/lib/commands/upgrade.d.ts +15 -0
  40. package/lib/commands/upgrade.js +76 -2
  41. package/lib/commands/webext/index.d.ts +2 -0
  42. package/lib/commands/webext/index.js +15 -0
  43. package/lib/commands/webext/subcommands.d.ts +16 -0
  44. package/lib/commands/webext/subcommands.js +204 -0
  45. package/lib/types/config-data.d.ts +2 -2
  46. package/lib/utils/bin-shim.d.ts +8 -2
  47. package/lib/utils/bin-shim.js +6 -5
  48. package/lib/utils/bundle-inputs.d.ts +84 -0
  49. package/lib/utils/bundle-inputs.js +268 -0
  50. package/lib/utils/check-system-deps.d.ts +5 -19
  51. package/lib/utils/check-system-deps.js +31 -94
  52. package/lib/utils/dev-link.d.ts +225 -0
  53. package/lib/utils/dev-link.js +684 -0
  54. package/lib/utils/exec-bin.d.ts +137 -0
  55. package/lib/utils/exec-bin.js +321 -0
  56. package/lib/utils/gi-runtime-paths.js +3 -2
  57. package/lib/utils/gitignore.d.ts +15 -0
  58. package/lib/utils/gitignore.js +178 -0
  59. package/lib/utils/gjsify-shim.d.ts +47 -0
  60. package/lib/utils/gjsify-shim.js +97 -8
  61. package/lib/utils/hang-watchdog.d.ts +95 -0
  62. package/lib/utils/hang-watchdog.js +194 -0
  63. package/lib/utils/install-backend-native.d.ts +33 -1
  64. package/lib/utils/install-backend-native.js +184 -20
  65. package/lib/utils/install-extraneous.d.ts +90 -0
  66. package/lib/utils/install-extraneous.js +120 -0
  67. package/lib/utils/install-lock.js +4 -25
  68. package/lib/utils/install-tarball-cache.d.ts +37 -9
  69. package/lib/utils/install-tarball-cache.js +59 -14
  70. package/lib/utils/native-skip-scan.d.ts +20 -0
  71. package/lib/utils/native-skip-scan.js +343 -0
  72. package/lib/utils/node-script.js +1 -1
  73. package/lib/utils/normalize-bundler-options.js +11 -0
  74. package/lib/utils/oxc-resolve.js +42 -3
  75. package/lib/utils/process-table.d.ts +22 -0
  76. package/lib/utils/process-table.js +188 -0
  77. package/lib/utils/prune-prefix.js +1 -1
  78. package/lib/utils/publish-pins.d.ts +38 -0
  79. package/lib/utils/publish-pins.js +47 -0
  80. package/lib/utils/publish-readback.d.ts +103 -17
  81. package/lib/utils/publish-readback.js +326 -47
  82. package/lib/utils/run-gjs.d.ts +15 -1
  83. package/lib/utils/run-gjs.js +98 -13
  84. package/lib/utils/run-node.d.ts +2 -0
  85. package/lib/utils/run-node.js +2 -1
  86. package/lib/utils/runtimes.js +14 -13
  87. package/lib/utils/ship/gzip.js +1 -1
  88. package/lib/utils/ship/icns.d.ts +14 -0
  89. package/lib/utils/ship/icns.js +83 -0
  90. package/lib/utils/ship/ico.d.ts +25 -0
  91. package/lib/utils/ship/ico.js +120 -0
  92. package/lib/utils/ship/icons.d.ts +197 -0
  93. package/lib/utils/ship/icons.js +399 -0
  94. package/lib/utils/ship/launcher.js +16 -1
  95. package/lib/utils/ship/layout.d.ts +32 -0
  96. package/lib/utils/ship/layout.js +112 -38
  97. package/lib/utils/ship/msi.d.ts +1 -1
  98. package/lib/utils/ship/msi.js +30 -5
  99. package/lib/utils/ship/pe-launcher.d.ts +18 -0
  100. package/lib/utils/ship/pe-launcher.js +125 -4
  101. package/lib/utils/ship/plan.d.ts +1 -34
  102. package/lib/utils/ship/plan.js +7 -56
  103. package/lib/utils/ship/plist.d.ts +20 -10
  104. package/lib/utils/ship/plist.js +29 -12
  105. package/lib/utils/ship/settings.js +3 -2
  106. package/lib/utils/spawn.d.ts +13 -0
  107. package/lib/utils/spawn.js +10 -5
  108. package/lib/utils/watch-loop.d.ts +15 -1
  109. package/lib/utils/watch-loop.js +9 -5
  110. package/lib/utils/webext/build.d.ts +42 -0
  111. package/lib/utils/webext/build.js +120 -0
  112. package/lib/utils/webext/config.d.ts +72 -0
  113. package/lib/utils/webext/config.js +234 -0
  114. package/lib/utils/webext/icons.d.ts +24 -0
  115. package/lib/utils/webext/icons.js +83 -0
  116. package/lib/utils/webext/launch.d.ts +32 -0
  117. package/lib/utils/webext/launch.js +77 -0
  118. package/lib/utils/webext/manifest.d.ts +53 -0
  119. package/lib/utils/webext/manifest.js +219 -0
  120. package/lib/utils/webext/pages.d.ts +26 -0
  121. package/lib/utils/webext/pages.js +61 -0
  122. package/lib/utils/webext/targets.d.ts +13 -0
  123. package/lib/utils/webext/targets.js +36 -0
  124. package/lib/utils/win32-command.d.ts +17 -0
  125. package/lib/utils/win32-command.js +21 -6
  126. package/package.json +20 -20
@@ -0,0 +1,188 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // What the CLI asks the OS about OTHER processes — is this pid alive, who are
3
+ // its descendants — answered in ONE place, for Node and GJS hosts alike.
4
+ //
5
+ // Both questions used to be answered only by reading `/proc`, in two files
6
+ // (`install-lock.ts`, `commands/foreach.ts`). procfs is a LINUX filesystem, and
7
+ // on macOS neither reader failed — each returned a plausible, wrong answer:
8
+ //
9
+ // - `foreach`'s fail-fast tree kill found NO descendants, so it signalled only
10
+ // the direct `npm run` child and left its shell → gjs/node build grandchildren
11
+ // running (the orphaned-bundler-at-100%-CPU shape the tree kill exists for).
12
+ // - `install-lock`'s liveness probe fell back to `process.kill(pid, 0)`, whose
13
+ // GJS implementation returned `true` for every pid, so a crashed install's
14
+ // lock was never stolen as dead-owner — only after the 35-min budget.
15
+ //
16
+ // So every reader here is chosen by what the host HAS (procfs readable?), never
17
+ // by platform name — a Linux container can lack procfs too. Only the no-procfs
18
+ // fallback's COMMAND depends on the platform, because Windows has no `ps`.
19
+ import { spawnSync } from 'node:child_process';
20
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
21
+ /**
22
+ * Is `pid` a live process on this machine?
23
+ *
24
+ * `/proc/<pid>` where procfs exists (one stat, no subprocess, identical under
25
+ * Node and GJS); elsewhere Node's signal-0 probe — `ESRCH` dead, `EPERM`
26
+ * alive-but-foreign. Any OTHER outcome counts as ALIVE: callers use this to
27
+ * decide whether something may be taken away from its owner, and "cannot tell"
28
+ * must never read as "gone".
29
+ */
30
+ export function isPidAlive(pid) {
31
+ // `existsSync` never throws by contract: Node returns false on any error, and
32
+ // the GJS shim is a typeof guard + Gio `query_exists`.
33
+ if (existsSync('/proc/self'))
34
+ return existsSync(`/proc/${pid}`);
35
+ try {
36
+ process.kill(pid, 0);
37
+ return true;
38
+ }
39
+ catch (err) {
40
+ return err.code !== 'ESRCH';
41
+ }
42
+ }
43
+ /**
44
+ * Parse `ps -A -o pid=,ppid=` output (two integer columns, blank headers) into
45
+ * ppid → children. Exported for the unit test; the format is POSIX, so procps
46
+ * on Linux and the BSD `ps` on macOS print the same shape.
47
+ */
48
+ export function parsePsPidPpid(output) {
49
+ const map = new Map();
50
+ for (const line of output.split('\n')) {
51
+ const m = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
52
+ if (!m)
53
+ continue;
54
+ const pid = Number(m[1]);
55
+ const ppid = Number(m[2]);
56
+ if (pid <= 0 || ppid <= 0 || pid === ppid)
57
+ continue;
58
+ const siblings = map.get(ppid);
59
+ if (siblings)
60
+ siblings.push(pid);
61
+ else
62
+ map.set(ppid, [pid]);
63
+ }
64
+ return map;
65
+ }
66
+ // Direct children of `pid` from /proc/<pid>/task/*/children (Linux,
67
+ // CONFIG_PROC_CHILDREN — standard on every mainstream kernel). `null` when the
68
+ // files are unreadable, which sends the caller to the full-table fallback.
69
+ function readProcChildren(pid) {
70
+ let taskIds;
71
+ try {
72
+ taskIds = readdirSync(`/proc/${pid}/task`);
73
+ }
74
+ catch {
75
+ // Process already gone (common) or no procfs.
76
+ return null;
77
+ }
78
+ const kids = [];
79
+ let readAny = false;
80
+ for (const tid of taskIds) {
81
+ try {
82
+ const data = readFileSync(`/proc/${pid}/task/${tid}/children`, 'utf-8');
83
+ readAny = true;
84
+ for (const tok of data.trim().split(/\s+/)) {
85
+ const n = Number(tok);
86
+ if (Number.isInteger(n) && n > 0)
87
+ kids.push(n);
88
+ }
89
+ }
90
+ catch {
91
+ // Thread vanished mid-walk, or kernel lacks CONFIG_PROC_CHILDREN.
92
+ }
93
+ }
94
+ return readAny ? kids : null;
95
+ }
96
+ // ppid → children from one /proc/*/stat scan. Empty when there is no procfs.
97
+ function procPpidMap() {
98
+ const map = new Map();
99
+ let entries = [];
100
+ try {
101
+ entries = readdirSync('/proc');
102
+ }
103
+ catch {
104
+ return map;
105
+ }
106
+ for (const entry of entries) {
107
+ const pid = Number(entry);
108
+ if (!Number.isInteger(pid) || pid <= 0)
109
+ continue;
110
+ try {
111
+ const stat = readFileSync(`/proc/${pid}/stat`, 'utf-8');
112
+ // comm (field 2) may contain spaces/parens — parse after last ')'.
113
+ const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
114
+ const ppid = Number(rest[1]);
115
+ if (!Number.isInteger(ppid) || ppid <= 0)
116
+ continue;
117
+ const siblings = map.get(ppid);
118
+ if (siblings)
119
+ siblings.push(pid);
120
+ else
121
+ map.set(ppid, [pid]);
122
+ }
123
+ catch {
124
+ // Process exited between readdir and read — fine.
125
+ }
126
+ }
127
+ return map;
128
+ }
129
+ // ppid → children from the OS's process list where there is no procfs:
130
+ // `ps(1)` on macOS/BSD, CIM's Win32_Process on Windows (no `ps` there — Git's
131
+ // MSYS `ps` rejects `-o` and sees only MSYS processes). Both print "pid ppid"
132
+ // lines, so one parser serves. Empty when neither can be run, which degrades to
133
+ // "signal the direct child only".
134
+ function osPpidMap() {
135
+ const [cmd, args] = process.platform === 'win32'
136
+ ? [
137
+ 'powershell.exe',
138
+ [
139
+ '-NoProfile',
140
+ '-NonInteractive',
141
+ '-Command',
142
+ 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId)" }',
143
+ ],
144
+ ]
145
+ : ['ps', ['-A', '-o', 'pid=,ppid=']];
146
+ try {
147
+ const res = spawnSync(cmd, args, { encoding: 'utf-8' });
148
+ if (res.status !== 0 || typeof res.stdout !== 'string')
149
+ return new Map();
150
+ return parsePsPidPpid(res.stdout);
151
+ }
152
+ catch {
153
+ return new Map();
154
+ }
155
+ }
156
+ /**
157
+ * Every transitive descendant of `pid`, breadth-first (reverse the list for
158
+ * deepest-first signalling). A snapshot — processes spawned after it are not in
159
+ * it, which is why `foreach` re-walks before escalating to SIGKILL.
160
+ */
161
+ export function collectDescendants(pid) {
162
+ const result = [];
163
+ const seen = new Set([pid]);
164
+ const queue = [pid];
165
+ let table = null;
166
+ while (queue.length > 0) {
167
+ const cur = queue.shift();
168
+ let kids = table ? null : readProcChildren(cur);
169
+ if (kids === null) {
170
+ // Per-task children files unreadable: one full table for this and
171
+ // every later level. procfs first, the OS process list where there is none.
172
+ if (!table) {
173
+ table = procPpidMap();
174
+ if (table.size === 0)
175
+ table = osPpidMap();
176
+ }
177
+ kids = table.get(cur) ?? [];
178
+ }
179
+ for (const kid of kids) {
180
+ if (seen.has(kid))
181
+ continue;
182
+ seen.add(kid);
183
+ result.push(kid);
184
+ queue.push(kid);
185
+ }
186
+ }
187
+ return result;
188
+ }
@@ -25,7 +25,7 @@
25
25
  // Reachability ("nothing installed points at this any more") would catch that one
26
26
  // and more, but it needs a record of what the prefix was ASSEMBLED FROM, which no
27
27
  // prefix carries yet. Uncertain means keep — see ADR 0025 and
28
- // `status/open-todos.md`.
28
+ // `status/open-todos/README.md`.
29
29
  import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync } from 'node:fs';
30
30
  import { join } from 'node:path';
31
31
  import { checkPlatform, declaresPlatform, describePlatformTarget, readPlatformOverrides, } from './platform-check.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The intra-repo pins a publish is about to ship, enumerated from the manifest
3
+ * PAIR — the workspace's own `package.json` and the rewritten one that comes
4
+ * back out of the tarball.
5
+ *
6
+ * Why the pair and not just the rewritten manifest: after
7
+ * `rewriteWorkspaceDeps` (see `commands/pack.ts`) a sibling pin is an ordinary
8
+ * `^0.51.1` and indistinguishable from a third-party dependency. The `workspace:`
9
+ * prefix survives only in the SOURCE manifest, so which edges are this repo's
10
+ * own is a fact that exists in one file and the version they will ship at exists
11
+ * in the other.
12
+ */
13
+ /** The blocks `rewriteWorkspaceDeps` rewrites and a consumer install resolves. */
14
+ export type DependencyBlock = 'dependencies' | 'optionalDependencies' | 'peerDependencies';
15
+ /**
16
+ * The one block whose unresolvable edge fails EVERY consumer install.
17
+ *
18
+ * Measured, npm 11.17.0, against the live registry, on an edge to a name that
19
+ * did not exist: `optionalDependencies` installs clean (exit 0, silently),
20
+ * `dependencies` is `npm error 404`, exit 1. Yarn refuses both (`YN0035`),
21
+ * which is why an optional edge is still reported — just not fatal.
22
+ */
23
+ export declare const REQUIRED_BLOCK: DependencyBlock;
24
+ export interface WorkspacePin {
25
+ name: string;
26
+ /** The concrete range the tarball will carry, after the rewrite. */
27
+ spec: string;
28
+ block: DependencyBlock;
29
+ }
30
+ /**
31
+ * Every edge that was a `workspace:` spec before the rewrite, with the version
32
+ * it will ship at.
33
+ *
34
+ * A name appearing in two blocks yields two pins: `dependencies` and
35
+ * `optionalDependencies` do not cost the same at install time, so collapsing
36
+ * them would throw away the distinction the caller decides on.
37
+ */
38
+ export declare function collectWorkspacePins(source: Record<string, unknown>, rewritten: Record<string, unknown>): WorkspacePin[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The intra-repo pins a publish is about to ship, enumerated from the manifest
3
+ * PAIR — the workspace's own `package.json` and the rewritten one that comes
4
+ * back out of the tarball.
5
+ *
6
+ * Why the pair and not just the rewritten manifest: after
7
+ * `rewriteWorkspaceDeps` (see `commands/pack.ts`) a sibling pin is an ordinary
8
+ * `^0.51.1` and indistinguishable from a third-party dependency. The `workspace:`
9
+ * prefix survives only in the SOURCE manifest, so which edges are this repo's
10
+ * own is a fact that exists in one file and the version they will ship at exists
11
+ * in the other.
12
+ */
13
+ const BLOCKS = ['dependencies', 'optionalDependencies', 'peerDependencies'];
14
+ /**
15
+ * The one block whose unresolvable edge fails EVERY consumer install.
16
+ *
17
+ * Measured, npm 11.17.0, against the live registry, on an edge to a name that
18
+ * did not exist: `optionalDependencies` installs clean (exit 0, silently),
19
+ * `dependencies` is `npm error 404`, exit 1. Yarn refuses both (`YN0035`),
20
+ * which is why an optional edge is still reported — just not fatal.
21
+ */
22
+ export const REQUIRED_BLOCK = 'dependencies';
23
+ const depsOf = (pkg, block) => {
24
+ const value = pkg[block];
25
+ return value && typeof value === 'object' ? value : {};
26
+ };
27
+ /**
28
+ * Every edge that was a `workspace:` spec before the rewrite, with the version
29
+ * it will ship at.
30
+ *
31
+ * A name appearing in two blocks yields two pins: `dependencies` and
32
+ * `optionalDependencies` do not cost the same at install time, so collapsing
33
+ * them would throw away the distinction the caller decides on.
34
+ */
35
+ export function collectWorkspacePins(source, rewritten) {
36
+ const pins = [];
37
+ for (const block of BLOCKS) {
38
+ const before = depsOf(source, block);
39
+ const after = depsOf(rewritten, block);
40
+ for (const [name, spec] of Object.entries(before)) {
41
+ if (typeof spec !== 'string' || !spec.startsWith('workspace:'))
42
+ continue;
43
+ pins.push({ name, spec: after[name] ?? spec, block });
44
+ }
45
+ }
46
+ return pins;
47
+ }
@@ -19,9 +19,42 @@ export type ReadbackProbe = {
19
19
  status?: number;
20
20
  detail: string;
21
21
  };
22
+ /**
23
+ * What the read-back CONCLUDED — the three outcomes a caller may act on, plus
24
+ * the one it may not.
25
+ *
26
+ * `absent` on its own is not a conclusion. The install document not carrying a
27
+ * version has two causes with opposite remedies — the write never landed
28
+ * (re-publish) and the write landed but that document does not serve it yet
29
+ * (wait; a re-publish is answered 409) — and until this type existed both left
30
+ * through one message asserting the first. Observed live on the ts-for-gir
31
+ * v5.1.0 release: `@ts-for-gir/reporter` was recorded by the registry at
32
+ * 21:43:00.983Z and a cache-busted abbreviated read ~40 min later still did not
33
+ * carry 5.1.0, while the full packument's `time` map did; two minutes after
34
+ * that, both agreed.
35
+ */
36
+ export type ReadbackVerdict =
37
+ /** The install document serves `name@version` and advertises a tarball. */
38
+ 'confirmed'
39
+ /** The registry has no record of this version at all — the v0.46.0 shape. */
40
+ | 'not-published'
41
+ /** The registry RECORDS the write; the document npm installs from does not serve it. */
42
+ | 'recorded-not-served'
43
+ /**
44
+ * Nothing was established. A 5xx, a timeout, DNS, an auth wall — or an
45
+ * install document without the version whose registry record could then
46
+ * not be read, which leaves BOTH other verdicts unearned. NEVER "published".
47
+ */
48
+ | 'unknown';
22
49
  export interface ReadbackResult {
23
50
  /** True iff a probe saw the exact version with a tarball on it. */
24
51
  confirmed: boolean;
52
+ /** What the read-back concluded. `confirmed === (verdict === 'confirmed')`. */
53
+ verdict: ReadbackVerdict;
54
+ /** One line naming the evidence the verdict rests on — it goes in the report. */
55
+ verdictDetail: string;
56
+ /** `time[version]` from the registry's own record, when the corroboration read one. */
57
+ recordedAt?: string;
25
58
  /** Probes actually sent (≥ 1). */
26
59
  attempts: number;
27
60
  elapsedMs: number;
@@ -80,29 +113,42 @@ export declare function backoffFor(round: number): number;
80
113
  */
81
114
  export declare function probeTimeoutFor(configuredMs: number, remainingMs: number): number;
82
115
  /**
83
- * Default polling budget: 300 s.
116
+ * Default polling budget: 600 s.
84
117
  *
85
- * The measured maximum lag between a 2xx and the registry recording the version
86
- * was 251.7 s (@gjsify/child_process, v0.46.0), so a budget below ~4.2 minutes
87
- * turns npm's normal queueing into a false red — and a false red at release time
88
- * costs a manual re-run of a workflow that did its job. 300 s clears that
89
- * maximum and is still short enough that a genuinely lost write is reported by
90
- * the job that lost it rather than by a later sweep.
118
+ * The lag between a 2xx and the registry recording the version has been measured
119
+ * on two releases, and the second broke the first's ceiling. v0.46.0 (199
120
+ * packages): 19 late writes, maximum 251.7 s (@gjsify/child_process). ts-for-gir
121
+ * v5.1.0 (12 packages, run 34899018849, PUT lines against `time["5.1.0"]`):
122
+ * four recorded ~0.7 s BEFORE the 2xx, eight after it — +74, +74, +75, +75,
123
+ * +96, +248, +248 and +368 s (@ts-for-gir/reporter) — the last past the 300 s
124
+ * this constant used to be, so "above the measured maximum" had quietly
125
+ * stopped being true of it. A budget below the lag turns npm's normal queueing into a false
126
+ * red, and a false red at release time costs a manual re-run of a workflow that
127
+ * did its job. A budget above it costs nothing on the nine in ten that confirm
128
+ * on the first probe; all it delays is the report of a genuinely LOST write —
129
+ * one in 199, measured — by the difference. 600 s is ~1.6x the larger maximum.
91
130
  *
92
- * THAT MAXIMUM IS A SAMPLE MAXIMUM, and the sample does not look truncated by
93
- * nature. The 19 lags fall in buckets ~20-30 s apart — 56(2), 76(3), 96(3),
131
+ * BOTH MAXIMA ARE SAMPLE MAXIMA, and neither sample looks truncated by nature.
132
+ * The v0.46.0 lags fall in buckets ~20-30 s apart — 56(2), 76(3), 96(3),
94
133
  * 127(4), 157(3), 189(1), 251(3) — and the TOP bucket is as populated as the
95
134
  * middle ones, which is the shape of a distribution cut off by the observation
96
- * rather than one decaying to zero. One release is one sample, so a lag past
97
- * 300 s is not ruled out; what makes 300 s affordable anyway is that the
98
- * remediation is verified rather than assumed. The re-run this reports lands on
99
- * the 409 path, and that path READS BACK too (see the conflict branch in
100
- * `commands/publish.ts`), so a window that turns out to be too short costs one
101
- * re-run that confirms — it does not hand back an unverified success.
135
+ * rather than one decaying to zero; the next release then produced a value past
136
+ * it. So a lag past 600 s is not ruled out either. What makes any finite window
137
+ * affordable is that the remediation is verified rather than assumed: the
138
+ * re-run this reports lands on the 409 path, and that path READS BACK too (see
139
+ * the conflict branch in `commands/publish.ts`), so a window that turns out too
140
+ * short costs one re-run that confirms — never an unverified success.
141
+ *
142
+ * `time[version]` is when the registry RECORDED the write, a lower bound on
143
+ * when the document npm installs from SERVES it: the reporter above was
144
+ * recorded at +368 s and a cache-busted read of its install document still
145
+ * lacked 5.1.0 ~40 min later. No budget waits that out, and none should — that
146
+ * state is what the `recorded-not-served` verdict names.
102
147
  */
103
- export declare const DEFAULT_VERIFY_BUDGET_MS = 300000;
148
+ export declare const DEFAULT_VERIFY_BUDGET_MS = 600000;
104
149
  /**
105
- * Poll the registry until `name@version` resolves, or the budget runs out.
150
+ * Poll the registry until `name@version` resolves, or the budget runs out, then
151
+ * say WHICH of the three states it found (see {@link ReadbackVerdict}).
106
152
  *
107
153
  * No side effects, no exits, no printing beyond the injected `log` — the caller
108
154
  * owns presentation, same contract as {@link import('./publish-diagnose.js')}.
@@ -130,3 +176,43 @@ export declare function formatUnconfirmedPublish(opts: {
130
176
  */
131
177
  claim?: 'accepted' | 'already-published';
132
178
  }): string;
179
+ /** Whether the registry serves a packument for a NAME at all. */
180
+ export type NamePresence = {
181
+ state: 'present';
182
+ status: number;
183
+ versions: number;
184
+ } | {
185
+ state: 'absent';
186
+ status: number;
187
+ detail: string;
188
+ }
189
+ /**
190
+ * We do not KNOW — a 5xx, a timeout, an auth wall. A caller that blocks a
191
+ * publish on this must treat it as "not established", never as "absent":
192
+ * the whole point of the guard is that it cannot manufacture a red.
193
+ */
194
+ | {
195
+ state: 'unknown';
196
+ status?: number;
197
+ detail: string;
198
+ };
199
+ export interface ProbePackageNameInput {
200
+ registry: string;
201
+ name: string;
202
+ probeTimeoutMs?: number;
203
+ authorization?: string;
204
+ fetchImpl?: typeof fetch;
205
+ }
206
+ /**
207
+ * Ask ONE question: does this NAME exist on the registry at all.
208
+ *
209
+ * Deliberately not a version question, and that is what makes it safe to gate a
210
+ * publish on. A version is eventually consistent — measured on v0.46.0, 19 of
211
+ * 199 packages were recorded 56-252 s AFTER their own 2xx — so "is name@version
212
+ * there" answers "not yet" for minutes and a gate built on it would manufacture
213
+ * a red on a publish that worked. Presence of the NAME does not move once the
214
+ * first version lands, which is the same reason
215
+ * `verify-published-closure.mjs --phase pre-release` asks it rather than the
216
+ * version: an answer that does not move when the train moves.
217
+ */
218
+ export declare function probePackageName(input: ProbePackageNameInput): Promise<NamePresence>;