@gjsify/cli 0.46.0 → 0.48.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 (35) hide show
  1. package/dist/cli.gjs.mjs +87 -83
  2. package/lib/commands/affected-classify.js +30 -1
  3. package/lib/commands/dlx.js +7 -3
  4. package/lib/commands/foreach.js +9 -2
  5. package/lib/commands/onboard.js +11 -0
  6. package/lib/commands/publish.d.ts +41 -0
  7. package/lib/commands/publish.js +223 -10
  8. package/lib/commands/run.js +7 -2
  9. package/lib/commands/ship.js +32 -4
  10. package/lib/types/config-data.d.ts +37 -1
  11. package/lib/utils/base64.d.ts +34 -0
  12. package/lib/utils/base64.js +47 -0
  13. package/lib/utils/cli-runtime-closure.d.ts +61 -0
  14. package/lib/utils/cli-runtime-closure.js +54 -17
  15. package/lib/utils/double-dash-args.d.ts +8 -0
  16. package/lib/utils/double-dash-args.js +64 -0
  17. package/lib/utils/publish-readback.d.ts +132 -0
  18. package/lib/utils/publish-readback.js +273 -0
  19. package/lib/utils/ship/changelog.d.ts +56 -0
  20. package/lib/utils/ship/changelog.js +166 -0
  21. package/lib/utils/ship/discover.d.ts +10 -1
  22. package/lib/utils/ship/discover.js +76 -2
  23. package/lib/utils/ship/entry-interpreter.d.ts +44 -0
  24. package/lib/utils/ship/entry-interpreter.js +109 -0
  25. package/lib/utils/ship/formats.js +5 -0
  26. package/lib/utils/ship/gi-namespaces.js +175 -12
  27. package/lib/utils/ship/layout.js +8 -4
  28. package/lib/utils/ship/plan.d.ts +5 -1
  29. package/lib/utils/ship/plan.js +21 -2
  30. package/lib/utils/ship/settings.d.ts +48 -2
  31. package/lib/utils/ship/settings.js +63 -3
  32. package/lib/utils/ship/stage-manifest.d.ts +22 -3
  33. package/lib/utils/ship/stage-manifest.js +40 -13
  34. package/lib/utils/ship/types.d.ts +13 -0
  35. package/package.json +19 -19
@@ -464,14 +464,27 @@ export interface ConfigDataShip extends AppMetadata {
464
464
  /**
465
465
  * The built bundle `bin/<name>` executes. Default: `gjsify.main`, then
466
466
  * `package.json#main`. Its whole directory is staged into `lib/<name>/`.
467
+ *
468
+ * A STRING is one entry for every target; a TABLE names one per OS, keyed
469
+ * like {@link ShipAppOptions} and resolved beside it. An application with two
470
+ * hosts has two bundles built from one source — `gjsify.app` decides which
471
+ * runtime a target execs, and this decides which of those bundles it hands
472
+ * that runtime (#1545).
467
473
  */
468
- bundle?: string;
474
+ bundle?: string | ShipBundleOptions;
469
475
  /** Icon file, or a directory of them. Sizes are read from the path or the filename. */
470
476
  icon?: string;
471
477
  /** A `*.gschema.xml` file, or a directory of them. */
472
478
  schemas?: string;
473
479
  /** Licence file. Default: the first of LICENSE, LICENSE.md, LICENSE.txt, COPYING. */
474
480
  licenseFile?: string;
481
+ /**
482
+ * The project's own changelog, rendered into the `.deb`'s
483
+ * `share/doc/<package>/changelog.Debian.gz` (Debian Policy § 4.4).
484
+ * Default: the first of CHANGELOG.md, CHANGELOG, CHANGELOG.txt, NEWS.md,
485
+ * searched up to the repository root exactly as `licenseFile` is.
486
+ */
487
+ changelogFile?: string;
475
488
  /** deb `Section:`. Default: derived from `categories`. */
476
489
  section?: string;
477
490
  /** rpm `Group:`. Default: derived from `categories`. */
@@ -611,6 +624,29 @@ export interface ShipAppOptions {
611
624
  /** What a Windows program directory carries. `node` is the only value its formats accept. */
612
625
  win32?: 'gjs' | 'node';
613
626
  }
627
+ /**
628
+ * `gjsify.ship.bundle` — the ENTRY per OS, the twin of {@link ShipAppOptions}.
629
+ *
630
+ * The two answer one question between them, and answering only half of it is
631
+ * what #1545 measured: `gjsify.ship.app.darwin: "node"` pointed the launcher at
632
+ * `node` while the payload stayed the project's `main` — a `--app gjs` bundle,
633
+ * because Linux is that project's primary target. Both halves were honoured,
634
+ * they contradicted each other, and the `.app` died on its first import.
635
+ *
636
+ * Same OS spelling as everywhere else in this block (`process.platform`), and
637
+ * the same fall-through: a target with no key here keeps `gjsify.main`. What it
638
+ * does NOT keep is another target's entry — a bundle declared for one OS is
639
+ * dropped from every other OS's payload, since no interpreter but that one can
640
+ * load it.
641
+ */
642
+ export interface ShipBundleOptions {
643
+ /** The entry a `.deb`/`.rpm`/Flatpak installs. */
644
+ linux?: string;
645
+ /** The entry `<App>.app/Contents/Resources/lib/` carries. */
646
+ darwin?: string;
647
+ /** The entry the Windows program directory carries. */
648
+ win32?: string;
649
+ }
614
650
  /**
615
651
  * `gjsify.ship.sign` — an IDENTITY per OS, and never a certificate.
616
652
  *
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Base64 for the CLI's own Node entry, which may not reach `@gjsify/buffer`.
3
+ *
4
+ * `@gjsify/buffer` exports exactly these two functions and this file duplicates
5
+ * them — deliberately, and the reason is a LAYER, not taste. The CLI's Node entry
6
+ * is the bootstrap artifact (ADR 0002): `gjsify install` links it before anything
7
+ * in this tree is built. `scripts/bootstrap-native-facades.mjs` emits `lib/esm`
8
+ * for its link-time runtime deps with plain `tsc`, and `buildCliRuntimeDeps()`
9
+ * resolves each one under `packages/infra/<name>`. `@gjsify/buffer` lives under
10
+ * `packages/node/` and its own `build` is `gjsify run build:gjsify && …`, i.e. it
11
+ * needs the BUNDLER — the artifact the bootstrap exists to produce. Importing it
12
+ * from here inverts that order, and the failure is not a type error: the ESM link
13
+ * fails at `bootstrap-native-facades`, so `build:infra` dies on every host before
14
+ * a single test runs.
15
+ *
16
+ * Measured: with `import { base64Encode } from '@gjsify/buffer'` in the CLI's Node
17
+ * entry, ten CI legs failed identically — "Missing at ESM link time:
18
+ * packages/infra/cli/node_modules/@gjsify/buffer/lib/esm/index.js, imported from
19
+ * lib/utils/ship/stage-manifest.js" — while all three REQUIRED checks stayed green,
20
+ * because `check-build-infra-order` holds the declaration order for TYPES and this
21
+ * is a runtime edge.
22
+ *
23
+ * So: one copy inside the CLI, imported by both call sites, rather than the two
24
+ * that prompted the deduplication in the first place. If the CLI ever gains a
25
+ * general way to reach `packages/node/*` at bootstrap time, this file is what
26
+ * should go.
27
+ */
28
+ /**
29
+ * Chunked because `String.fromCharCode(...bytes)` is a spread: a tarball-sized
30
+ * array blows the argument limit and throws `RangeError: Maximum call stack size
31
+ * exceeded`. 0x8000 is the size the CLI's publish path has always used.
32
+ */
33
+ export declare function base64Encode(bytes: Uint8Array): string;
34
+ export declare function base64Decode(text: string): Uint8Array;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Base64 for the CLI's own Node entry, which may not reach `@gjsify/buffer`.
3
+ *
4
+ * `@gjsify/buffer` exports exactly these two functions and this file duplicates
5
+ * them — deliberately, and the reason is a LAYER, not taste. The CLI's Node entry
6
+ * is the bootstrap artifact (ADR 0002): `gjsify install` links it before anything
7
+ * in this tree is built. `scripts/bootstrap-native-facades.mjs` emits `lib/esm`
8
+ * for its link-time runtime deps with plain `tsc`, and `buildCliRuntimeDeps()`
9
+ * resolves each one under `packages/infra/<name>`. `@gjsify/buffer` lives under
10
+ * `packages/node/` and its own `build` is `gjsify run build:gjsify && …`, i.e. it
11
+ * needs the BUNDLER — the artifact the bootstrap exists to produce. Importing it
12
+ * from here inverts that order, and the failure is not a type error: the ESM link
13
+ * fails at `bootstrap-native-facades`, so `build:infra` dies on every host before
14
+ * a single test runs.
15
+ *
16
+ * Measured: with `import { base64Encode } from '@gjsify/buffer'` in the CLI's Node
17
+ * entry, ten CI legs failed identically — "Missing at ESM link time:
18
+ * packages/infra/cli/node_modules/@gjsify/buffer/lib/esm/index.js, imported from
19
+ * lib/utils/ship/stage-manifest.js" — while all three REQUIRED checks stayed green,
20
+ * because `check-build-infra-order` holds the declaration order for TYPES and this
21
+ * is a runtime edge.
22
+ *
23
+ * So: one copy inside the CLI, imported by both call sites, rather than the two
24
+ * that prompted the deduplication in the first place. If the CLI ever gains a
25
+ * general way to reach `packages/node/*` at bootstrap time, this file is what
26
+ * should go.
27
+ */
28
+ /**
29
+ * Chunked because `String.fromCharCode(...bytes)` is a spread: a tarball-sized
30
+ * array blows the argument limit and throws `RangeError: Maximum call stack size
31
+ * exceeded`. 0x8000 is the size the CLI's publish path has always used.
32
+ */
33
+ export function base64Encode(bytes) {
34
+ let str = '';
35
+ const chunk = 0x8000;
36
+ for (let i = 0; i < bytes.length; i += chunk) {
37
+ str += String.fromCharCode(...bytes.subarray(i, i + chunk));
38
+ }
39
+ return btoa(str);
40
+ }
41
+ export function base64Decode(text) {
42
+ const binary = atob(text);
43
+ const bytes = new Uint8Array(binary.length);
44
+ for (let i = 0; i < binary.length; i++)
45
+ bytes[i] = binary.charCodeAt(i);
46
+ return bytes;
47
+ }
@@ -1,4 +1,65 @@
1
1
  import type { Workspace } from '@gjsify/workspace';
2
+ /**
3
+ * Literal module specifiers of `code`: static `import`/`export … from`, plus
4
+ * `import('<literal>')`.
5
+ *
6
+ * acorn rather than a regex — see the header. `allowHashBang` because the CLI's
7
+ * own entry is a bin (`#!/usr/bin/env node`), and a parse failure yields NO
8
+ * specifiers, which under-selects rather than mis-selects.
9
+ */
10
+ export interface SpecifierNode {
11
+ type?: string;
12
+ value?: unknown;
13
+ expressions?: unknown[];
14
+ quasis?: Array<{
15
+ value?: {
16
+ cooked?: unknown;
17
+ };
18
+ }>;
19
+ }
20
+ /**
21
+ * The string a node names statically, or `null` when it is computed.
22
+ *
23
+ * A substitution-free TEMPLATE LITERAL is as static as a quoted string, and
24
+ * skipping it is not hypothetical: the minifier rewrites `await
25
+ * import('gi://GLib?version=2.0')` to a backtick form, so a Literal-only reader
26
+ * silently loses every dynamic import in a built bundle.
27
+ *
28
+ * Not named for specifiers, because it does not only read them: the same
29
+ * question is asked of a CALL ARGUMENT by `utils/ship/gi-namespaces.ts`, whose
30
+ * `requireGi("Gtk", "4.0")` carries a namespace and a version in exactly the two
31
+ * spellings above.
32
+ */
33
+ export declare function staticStringValue(node: SpecifierNode | undefined): string | null;
34
+ /** One node of the walk: whatever acorn produced, seen as a bag of properties. */
35
+ export type AstNode = {
36
+ type?: string;
37
+ } & Record<string, unknown>;
38
+ /**
39
+ * Parse `code` as a module and call `visit` for every node in it.
40
+ *
41
+ * Exported because a SECOND parse is how the two readers of a built bundle
42
+ * would drift apart: {@link moduleSpecifiers} answers "what does this file
43
+ * import" and `utils/ship/gi-namespaces.ts` answers "which GI namespaces does
44
+ * it load", and the second question has an answer the first cannot see — a
45
+ * `--app node` bundle carries no `gi://` specifier at all, only the
46
+ * `requireGi()` calls the bundler rewrote them into. One walker, two readers.
47
+ *
48
+ * A parse failure yields NO nodes rather than throwing: every caller is
49
+ * summarising a file it did not write, and under-selecting is the failure mode
50
+ * that keeps a correct project buildable.
51
+ */
52
+ export declare function walkModuleAst(code: string, visit: (node: AstNode) => void): void;
53
+ /**
54
+ * The module `node` imports from, or `null` when `node` is not an import at all.
55
+ *
56
+ * FOUR node types, in one place. Every reader of a built bundle in this tree asks
57
+ * the same question of the same four — a static `import`, both `export … from`
58
+ * forms and `import()` — and each copy of that list is a chance for one reader to
59
+ * miss a form the others catch. The bare side-effect `import "gi://Soup"` is what
60
+ * that costs when it happens (`utils/ship/gi-namespaces.ts`).
61
+ */
62
+ export declare function importedSpecifier(node: AstNode): string | null;
2
63
  export declare function moduleSpecifiers(code: string): string[];
3
64
  export interface CliRuntimeClosureOptions {
4
65
  /**
@@ -79,14 +79,19 @@ function splitWorkspaceSpecifier(specifier, byName) {
79
79
  return null;
80
80
  }
81
81
  /**
82
- * The string a module specifier node names, or `null` when it is computed.
82
+ * The string a node names statically, or `null` when it is computed.
83
83
  *
84
84
  * A substitution-free TEMPLATE LITERAL is as static as a quoted string, and
85
85
  * skipping it is not hypothetical: the minifier rewrites `await
86
86
  * import('gi://GLib?version=2.0')` to a backtick form, so a Literal-only reader
87
87
  * silently loses every dynamic import in a built bundle.
88
+ *
89
+ * Not named for specifiers, because it does not only read them: the same
90
+ * question is asked of a CALL ARGUMENT by `utils/ship/gi-namespaces.ts`, whose
91
+ * `requireGi("Gtk", "4.0")` carries a namespace and a version in exactly the two
92
+ * spellings above.
88
93
  */
89
- function literalSpecifier(node) {
94
+ export function staticStringValue(node) {
90
95
  if (node?.type === 'Literal')
91
96
  return typeof node.value === 'string' ? node.value : null;
92
97
  if (node?.type === 'TemplateLiteral' && node.expressions?.length === 0) {
@@ -95,39 +100,71 @@ function literalSpecifier(node) {
95
100
  }
96
101
  return null;
97
102
  }
98
- export function moduleSpecifiers(code) {
103
+ /**
104
+ * Parse `code` as a module and call `visit` for every node in it.
105
+ *
106
+ * Exported because a SECOND parse is how the two readers of a built bundle
107
+ * would drift apart: {@link moduleSpecifiers} answers "what does this file
108
+ * import" and `utils/ship/gi-namespaces.ts` answers "which GI namespaces does
109
+ * it load", and the second question has an answer the first cannot see — a
110
+ * `--app node` bundle carries no `gi://` specifier at all, only the
111
+ * `requireGi()` calls the bundler rewrote them into. One walker, two readers.
112
+ *
113
+ * A parse failure yields NO nodes rather than throwing: every caller is
114
+ * summarising a file it did not write, and under-selecting is the failure mode
115
+ * that keeps a correct project buildable.
116
+ */
117
+ export function walkModuleAst(code, visit) {
99
118
  let ast;
100
119
  try {
101
120
  ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true });
102
121
  }
103
122
  catch {
104
- return [];
123
+ return;
105
124
  }
106
- const out = [];
107
- const visit = (node) => {
125
+ const descend = (node) => {
108
126
  if (!node || typeof node !== 'object')
109
127
  return;
110
128
  if (Array.isArray(node)) {
111
129
  for (const child of node)
112
- visit(child);
130
+ descend(child);
113
131
  return;
114
132
  }
115
133
  const n = node;
116
- if (n.type === 'ImportDeclaration' ||
117
- n.type === 'ExportNamedDeclaration' ||
118
- n.type === 'ExportAllDeclaration' ||
119
- n.type === 'ImportExpression') {
120
- const specifier = literalSpecifier(n.source);
121
- if (specifier !== null)
122
- out.push(specifier);
123
- }
134
+ visit(n);
124
135
  for (const [key, value] of Object.entries(n)) {
125
136
  if (key === 'type' || key === 'start' || key === 'end' || key === 'loc')
126
137
  continue;
127
- visit(value);
138
+ descend(value);
128
139
  }
129
140
  };
130
- visit(ast);
141
+ descend(ast);
142
+ }
143
+ /**
144
+ * The module `node` imports from, or `null` when `node` is not an import at all.
145
+ *
146
+ * FOUR node types, in one place. Every reader of a built bundle in this tree asks
147
+ * the same question of the same four — a static `import`, both `export … from`
148
+ * forms and `import()` — and each copy of that list is a chance for one reader to
149
+ * miss a form the others catch. The bare side-effect `import "gi://Soup"` is what
150
+ * that costs when it happens (`utils/ship/gi-namespaces.ts`).
151
+ */
152
+ export function importedSpecifier(node) {
153
+ if (node.type !== 'ImportDeclaration' &&
154
+ node.type !== 'ExportNamedDeclaration' &&
155
+ node.type !== 'ExportAllDeclaration' &&
156
+ node.type !== 'ImportExpression') {
157
+ return null;
158
+ }
159
+ return staticStringValue(node.source);
160
+ }
161
+ export function moduleSpecifiers(code) {
162
+ const out = [];
163
+ walkModuleAst(code, (node) => {
164
+ const specifier = importedSpecifier(node);
165
+ if (specifier !== null)
166
+ out.push(specifier);
167
+ });
131
168
  return out;
132
169
  }
133
170
  /** The file a relative specifier names, probing the extensionless spellings Node accepts. */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Everything after the `--` separator, as an argv.
3
+ *
4
+ * @param args a yargs-parsed argument object from a command whose builder sets
5
+ * `parserConfiguration({ 'populate--': true, 'parse-positional-numbers': false })`
6
+ * @throws if an entry is not a string, i.e. if that configuration is missing
7
+ */
8
+ export declare function doubleDashArgs(args: object): string[];
@@ -0,0 +1,64 @@
1
+ // The argv a caller wrote after `--`, read back off yargs.
2
+ //
3
+ // THE INCIDENT (#1531). `foreach.ts` and `run.ts` each read `args['--']` and
4
+ // narrowed it with `.filter((v): v is string => typeof v === 'string')`. That
5
+ // reads like a type narrowing and is a DELETION: yargs types a bare number in
6
+ // its `populate--` array as a `number`, so the value disappears and the option
7
+ // in front of it swallows the next flag. Measured against this repo's own
8
+ // yargs 18.0.0:
9
+ //
10
+ // in : gjsify publish --verify-timeout 5 --tag latest
11
+ // raw : ["gjsify","publish","--verify-timeout",5,"--tag","latest"]
12
+ // filter: ["gjsify","publish","--verify-timeout","--tag","latest"]
13
+ //
14
+ // That argv shipped: `npm:publish` passes `--verify-timeout 5 --tag latest`,
15
+ // and release run 33804937881 echoed `--verify-timeout --tag latest` 209 times
16
+ // — `--verify-timeout` taking `--tag` as its value, `latest` a stray
17
+ // positional, `--tag latest` gone. Nobody noticed because npm's default
18
+ // dist-tag IS `latest`, which is luck rather than design.
19
+ //
20
+ // TWO HALVES, AND THE FIRST ONE IS THE FIX. A coercion here (`String(v)`) would
21
+ // recover the value and still lose its SPELLING — measured against yargs 18.0.0,
22
+ // `1.0` comes back as 1, `0.10` as 0.1, `0x10` as 16 and `1e3` as 1000, and an
23
+ // argv is text, not arithmetic. (A leading zero is NOT in that set: `007` stays
24
+ // the string yargs was given. It reads like the obvious example and is the one
25
+ // shape here that never broke.) So the parse is corrected at
26
+ // the source: every command that sets `populate--` also sets
27
+ // `parse-positional-numbers: false`, which leaves `_`, the variadic positionals
28
+ // and the `--` tail as the strings the user typed while option values declared
29
+ // `type: 'number'` are still parsed (measured: `--jobs 4` stays `4` the number).
30
+ //
31
+ // This function is the second half. It does NOT coerce — with the parser
32
+ // configured as above a non-string cannot arrive, so one that does means the
33
+ // configuration was dropped, and that is the failure to name rather than to
34
+ // paper over. A silent `.filter()` over a parser-produced array is the wrong
35
+ // tool wherever the array is data rather than a union.
36
+ //
37
+ // ONE reader for all of it, because there were three and they disagreed:
38
+ // `foreach.ts` (twice) and `run.ts` filtered, `dlx.ts` coerced. The drifted
39
+ // copies are the ones that shipped the wrong argv.
40
+ /**
41
+ * Everything after the `--` separator, as an argv.
42
+ *
43
+ * @param args a yargs-parsed argument object from a command whose builder sets
44
+ * `parserConfiguration({ 'populate--': true, 'parse-positional-numbers': false })`
45
+ * @throws if an entry is not a string, i.e. if that configuration is missing
46
+ */
47
+ export function doubleDashArgs(args) {
48
+ const raw = args['--'];
49
+ if (raw === undefined || raw === null)
50
+ return [];
51
+ if (!Array.isArray(raw)) {
52
+ throw new Error(`gjsify: internal error — yargs' \`--\` slot holds a ${typeof raw} rather than an array, so the ` +
53
+ 'arguments after `--` cannot be forwarded. The command builder must set ' +
54
+ "`parserConfiguration({ 'populate--': true })`.");
55
+ }
56
+ return raw.map((value, index) => {
57
+ if (typeof value === 'string')
58
+ return value;
59
+ throw new Error(`gjsify: internal error — the argument after \`--\` at position ${index} arrived as a ` +
60
+ `${typeof value} (${String(value)}) instead of a string, so forwarding it would change what ` +
61
+ 'the caller wrote. The command builder must set ' +
62
+ "`parserConfiguration({ 'parse-positional-numbers': false })` alongside `populate--`.");
63
+ });
64
+ }
@@ -0,0 +1,132 @@
1
+ /** One registry answer, classified. `absent` and `error` are NOT the same fact. */
2
+ export type ReadbackProbe = {
3
+ state: 'present';
4
+ status: number;
5
+ tarball: string;
6
+ } | {
7
+ state: 'absent';
8
+ status: number;
9
+ detail: string;
10
+ }
11
+ /**
12
+ * We do not KNOW. A 5xx, a timeout, unparseable JSON — treating any of them
13
+ * as "absent" would fabricate a "never published" verdict out of a registry
14
+ * hiccup, the same distinction `verify-published-closure.mjs` draws between
15
+ * `false` and an `Error`.
16
+ */
17
+ | {
18
+ state: 'error';
19
+ status?: number;
20
+ detail: string;
21
+ };
22
+ export interface ReadbackResult {
23
+ /** True iff a probe saw the exact version with a tarball on it. */
24
+ confirmed: boolean;
25
+ /** Probes actually sent (≥ 1). */
26
+ attempts: number;
27
+ elapsedMs: number;
28
+ /** The URL that was asked, verbatim — it belongs in the failure message. */
29
+ url: string;
30
+ /** What the registry said last. On `confirmed` this is the `present` probe. */
31
+ last: ReadbackProbe;
32
+ }
33
+ export interface VerifyPublishedVersionInput {
34
+ /** Registry URL, with or without a trailing slash. */
35
+ registry: string;
36
+ /** Full package name including scope. */
37
+ name: string;
38
+ version: string;
39
+ /** Total polling budget in ms. Must be > 0 — a disabled read-back is the caller's decision. */
40
+ budgetMs: number;
41
+ /** Per-probe request timeout. Default 30 s, matching the closure script. */
42
+ probeTimeoutMs?: number;
43
+ /**
44
+ * The `authorization` header the PUT used, forwarded verbatim.
45
+ *
46
+ * A registry that requires a credential to READ answers 401/403 to an
47
+ * anonymous packument GET, which classifies as `error` — so an intact
48
+ * publish to GitHub Packages, an Artifactory repo or a Verdaccio with
49
+ * `access: $authenticated` reports `publish-unconfirmed` and, on the fatal
50
+ * default, exits 1 after the whole budget on a publish that worked.
51
+ * Measured against the first version of this file: 15 read-back GETs, every
52
+ * one anonymous, one second after a PUT that carried a token. Same origin,
53
+ * same credential, one request later — it adds no exposure the PUT did not
54
+ * already have.
55
+ */
56
+ authorization?: string;
57
+ /** Injected in tests; resolved at call time so a `globalThis.fetch` stub is honoured. */
58
+ fetchImpl?: typeof fetch;
59
+ sleep?: (ms: number) => Promise<void>;
60
+ now?: () => number;
61
+ /** Called once per probe under `GJSIFY_PUBLISH_DEBUG`. */
62
+ log?: (msg: string) => void;
63
+ }
64
+ /** Delay before probe `round + 2`. Past the table's end the last value repeats. */
65
+ export declare function backoffFor(round: number): number;
66
+ /**
67
+ * A probe's request timeout: what was configured, never more than the budget has
68
+ * left to spend — but never less than a probe needs to answer.
69
+ *
70
+ * Configured and budget are otherwise independent, and that makes
71
+ * `--verify-timeout 5` — what the 199-package sweep passes — a promise the code
72
+ * does not keep: one unanswered request may block for the 30 s default, six times
73
+ * the budget it was given, and 199 of those cost ~100 min against the ~11% the
74
+ * deferral is argued on. Clamping bounds the wall clock at
75
+ * `budget + MIN_PROBE_TIMEOUT_MS` instead. The floor is the other half of the
76
+ * same honesty: clamping all the way to zero traded a 30 s overshoot for an
77
+ * `error` verdict on a probe that was about to return the 404 (measured — it
78
+ * turned the incident's own e2e row from `absent: 404` into `request failed
79
+ * (This operation was aborted)`).
80
+ */
81
+ export declare function probeTimeoutFor(configuredMs: number, remainingMs: number): number;
82
+ /**
83
+ * Default polling budget: 300 s.
84
+ *
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.
91
+ *
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),
94
+ * 127(4), 157(3), 189(1), 251(3) — and the TOP bucket is as populated as the
95
+ * 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.
102
+ */
103
+ export declare const DEFAULT_VERIFY_BUDGET_MS = 300000;
104
+ /**
105
+ * Poll the registry until `name@version` resolves, or the budget runs out.
106
+ *
107
+ * No side effects, no exits, no printing beyond the injected `log` — the caller
108
+ * owns presentation, same contract as {@link import('./publish-diagnose.js')}.
109
+ */
110
+ export declare function verifyPublishedVersion(input: VerifyPublishedVersionInput): Promise<ReadbackResult>;
111
+ /**
112
+ * The unconfirmed-publish diagnostic, ready for stderr.
113
+ *
114
+ * The message has to answer three questions the incident log could not, which is
115
+ * why it needed the registry to reconstruct at all: what was PUT, what was asked,
116
+ * and what came back.
117
+ */
118
+ export declare function formatUnconfirmedPublish(opts: {
119
+ name: string;
120
+ version: string;
121
+ putUrl: string;
122
+ putStatus: number;
123
+ putStatusText: string;
124
+ payloadBytes: number;
125
+ readback: ReadbackResult;
126
+ /**
127
+ * What npm claimed before the read-back disagreed. `accepted` is a 2xx;
128
+ * `already-published` is a tolerated 409, where the remediation is the
129
+ * opposite one — a re-run answers the same conflict.
130
+ */
131
+ claim?: 'accepted' | 'already-published';
132
+ }): string;