@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.
- package/dist/cli.gjs.mjs +539 -493
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +37 -1
- package/lib/bundler-pick.js +108 -17
- package/lib/cli-app.js +7 -2
- package/lib/commands/affected-classify.d.ts +7 -1
- package/lib/commands/affected-classify.js +66 -9
- package/lib/commands/affected-script-refs.d.ts +20 -0
- package/lib/commands/affected-script-refs.js +218 -0
- package/lib/commands/affected.js +24 -2
- package/lib/commands/env.d.ts +31 -0
- package/lib/commands/env.js +114 -0
- package/lib/commands/exec.d.ts +10 -0
- package/lib/commands/exec.js +208 -0
- package/lib/commands/flatpak/diff.js +8 -2
- package/lib/commands/flatpak/init.js +4 -1
- package/lib/commands/flatpak/sources.js +4 -3
- package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
- package/lib/commands/flatpak/sync-flathub.js +149 -7
- package/lib/commands/flatpak/utils.d.ts +10 -0
- package/lib/commands/flatpak/utils.js +10 -0
- package/lib/commands/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +81 -5
- package/lib/commands/link.d.ts +13 -0
- package/lib/commands/link.js +243 -0
- package/lib/commands/onboard.js +18 -2
- package/lib/commands/publish.d.ts +34 -0
- package/lib/commands/publish.js +153 -8
- package/lib/commands/self-update.js +2 -1
- package/lib/commands/ship.js +19 -1
- package/lib/commands/test.d.ts +18 -9
- package/lib/commands/test.js +56 -11
- package/lib/commands/tsc.js +4 -4
- package/lib/commands/upgrade.d.ts +15 -0
- package/lib/commands/upgrade.js +76 -2
- package/lib/commands/webext/index.d.ts +2 -0
- package/lib/commands/webext/index.js +15 -0
- package/lib/commands/webext/subcommands.d.ts +16 -0
- package/lib/commands/webext/subcommands.js +204 -0
- package/lib/types/config-data.d.ts +2 -2
- package/lib/utils/bin-shim.d.ts +8 -2
- package/lib/utils/bin-shim.js +6 -5
- package/lib/utils/bundle-inputs.d.ts +84 -0
- package/lib/utils/bundle-inputs.js +268 -0
- package/lib/utils/check-system-deps.d.ts +5 -19
- package/lib/utils/check-system-deps.js +31 -94
- package/lib/utils/dev-link.d.ts +225 -0
- package/lib/utils/dev-link.js +684 -0
- package/lib/utils/exec-bin.d.ts +137 -0
- package/lib/utils/exec-bin.js +321 -0
- package/lib/utils/gi-runtime-paths.js +3 -2
- package/lib/utils/gitignore.d.ts +15 -0
- package/lib/utils/gitignore.js +178 -0
- package/lib/utils/gjsify-shim.d.ts +47 -0
- package/lib/utils/gjsify-shim.js +97 -8
- package/lib/utils/hang-watchdog.d.ts +95 -0
- package/lib/utils/hang-watchdog.js +194 -0
- package/lib/utils/install-backend-native.d.ts +33 -1
- package/lib/utils/install-backend-native.js +184 -20
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- package/lib/utils/native-skip-scan.d.ts +20 -0
- package/lib/utils/native-skip-scan.js +343 -0
- package/lib/utils/node-script.js +1 -1
- package/lib/utils/normalize-bundler-options.js +11 -0
- package/lib/utils/oxc-resolve.js +42 -3
- package/lib/utils/process-table.d.ts +22 -0
- package/lib/utils/process-table.js +188 -0
- package/lib/utils/prune-prefix.js +1 -1
- package/lib/utils/publish-pins.d.ts +38 -0
- package/lib/utils/publish-pins.js +47 -0
- package/lib/utils/publish-readback.d.ts +103 -17
- package/lib/utils/publish-readback.js +326 -47
- package/lib/utils/run-gjs.d.ts +15 -1
- package/lib/utils/run-gjs.js +98 -13
- package/lib/utils/run-node.d.ts +2 -0
- package/lib/utils/run-node.js +2 -1
- package/lib/utils/runtimes.js +14 -13
- package/lib/utils/ship/gzip.js +1 -1
- package/lib/utils/ship/icns.d.ts +14 -0
- package/lib/utils/ship/icns.js +83 -0
- package/lib/utils/ship/ico.d.ts +25 -0
- package/lib/utils/ship/ico.js +120 -0
- package/lib/utils/ship/icons.d.ts +197 -0
- package/lib/utils/ship/icons.js +399 -0
- package/lib/utils/ship/launcher.js +16 -1
- package/lib/utils/ship/layout.d.ts +32 -0
- package/lib/utils/ship/layout.js +112 -38
- package/lib/utils/ship/msi.d.ts +1 -1
- package/lib/utils/ship/msi.js +30 -5
- package/lib/utils/ship/pe-launcher.d.ts +18 -0
- package/lib/utils/ship/pe-launcher.js +125 -4
- package/lib/utils/ship/plan.d.ts +1 -34
- package/lib/utils/ship/plan.js +7 -56
- package/lib/utils/ship/plist.d.ts +20 -10
- package/lib/utils/ship/plist.js +29 -12
- package/lib/utils/ship/settings.js +3 -2
- package/lib/utils/spawn.d.ts +13 -0
- package/lib/utils/spawn.js +10 -5
- package/lib/utils/watch-loop.d.ts +15 -1
- package/lib/utils/watch-loop.js +9 -5
- package/lib/utils/webext/build.d.ts +42 -0
- package/lib/utils/webext/build.js +120 -0
- package/lib/utils/webext/config.d.ts +72 -0
- package/lib/utils/webext/config.js +234 -0
- package/lib/utils/webext/icons.d.ts +24 -0
- package/lib/utils/webext/icons.js +83 -0
- package/lib/utils/webext/launch.d.ts +32 -0
- package/lib/utils/webext/launch.js +77 -0
- package/lib/utils/webext/manifest.d.ts +53 -0
- package/lib/utils/webext/manifest.js +219 -0
- package/lib/utils/webext/pages.d.ts +26 -0
- package/lib/utils/webext/pages.js +61 -0
- package/lib/utils/webext/targets.d.ts +13 -0
- package/lib/utils/webext/targets.js +36 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- 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:
|
|
116
|
+
* Default polling budget: 600 s.
|
|
84
117
|
*
|
|
85
|
-
* The
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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
|
-
*
|
|
93
|
-
*
|
|
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
|
|
97
|
-
*
|
|
98
|
-
* remediation is verified rather than assumed
|
|
99
|
-
* the 409 path, and that path READS BACK too (see
|
|
100
|
-
* `commands/publish.ts`), so a window that turns out
|
|
101
|
-
* re-run that confirms —
|
|
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 =
|
|
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>;
|