@gjsify/cli 0.36.0 → 0.37.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 (116) hide show
  1. package/dist/cli.gjs.mjs +3 -3
  2. package/lib/bundler-pick.js +61 -87
  3. package/lib/cli-fail.d.ts +4 -7
  4. package/lib/cli-fail.js +29 -55
  5. package/lib/commands/affected.js +161 -286
  6. package/lib/commands/install.d.ts +63 -66
  7. package/lib/commands/install.js +372 -536
  8. package/lib/templates/install.mjs.tmpl +38 -68
  9. package/lib/types/cli-build-options.d.ts +33 -67
  10. package/lib/types/command.d.ts +9 -12
  11. package/lib/types/config-data.d.ts +125 -221
  12. package/lib/utils/auth-npmrc.d.ts +5 -10
  13. package/lib/utils/auth-npmrc.js +11 -21
  14. package/lib/utils/clear-targets.d.ts +8 -11
  15. package/lib/utils/clear-targets.js +22 -29
  16. package/lib/utils/copy-targets.d.ts +13 -19
  17. package/lib/utils/copy-targets.js +31 -44
  18. package/lib/utils/dep-aggregation.d.ts +7 -15
  19. package/lib/utils/dep-aggregation.js +9 -22
  20. package/lib/utils/detect-native-packages.d.ts +83 -116
  21. package/lib/utils/detect-native-packages.js +111 -149
  22. package/lib/utils/dir-link.d.ts +29 -48
  23. package/lib/utils/dir-link.js +33 -56
  24. package/lib/utils/discover-showcases.d.ts +6 -7
  25. package/lib/utils/discover-showcases.js +11 -15
  26. package/lib/utils/dlx-cache.d.ts +29 -38
  27. package/lib/utils/dlx-cache.js +53 -63
  28. package/lib/utils/force-exit.d.ts +0 -3
  29. package/lib/utils/force-exit.js +13 -24
  30. package/lib/utils/gi-search-path.d.ts +16 -24
  31. package/lib/utils/gi-search-path.js +54 -82
  32. package/lib/utils/gjs-bundle-guard.d.ts +5 -13
  33. package/lib/utils/gjs-bundle-guard.js +35 -57
  34. package/lib/utils/gjs-source-escape.d.ts +2 -2
  35. package/lib/utils/gjs-source-escape.js +21 -39
  36. package/lib/utils/gjsify-shim.d.ts +17 -20
  37. package/lib/utils/gjsify-shim.js +80 -120
  38. package/lib/utils/html-entry.d.ts +8 -12
  39. package/lib/utils/html-entry.js +19 -28
  40. package/lib/utils/install-backend-native.d.ts +123 -161
  41. package/lib/utils/install-backend-native.js +536 -737
  42. package/lib/utils/install-backend.d.ts +42 -57
  43. package/lib/utils/install-backend.js +16 -21
  44. package/lib/utils/install-cache-fs.d.ts +10 -15
  45. package/lib/utils/install-cache-fs.js +18 -26
  46. package/lib/utils/install-lock.d.ts +5 -6
  47. package/lib/utils/install-lock.js +46 -65
  48. package/lib/utils/install-packument-cache.d.ts +27 -33
  49. package/lib/utils/install-packument-cache.js +44 -57
  50. package/lib/utils/install-progress.d.ts +2 -9
  51. package/lib/utils/install-progress.js +14 -39
  52. package/lib/utils/install-tarball-cache.d.ts +14 -20
  53. package/lib/utils/install-tarball-cache.js +41 -59
  54. package/lib/utils/library-output.d.ts +9 -22
  55. package/lib/utils/library-output.js +19 -37
  56. package/lib/utils/load-npmrc.d.ts +5 -6
  57. package/lib/utils/load-npmrc.js +18 -27
  58. package/lib/utils/node-bundle-guard.d.ts +4 -8
  59. package/lib/utils/node-bundle-guard.js +21 -35
  60. package/lib/utils/node-script.js +33 -54
  61. package/lib/utils/node-version.d.ts +5 -14
  62. package/lib/utils/node-version.js +15 -24
  63. package/lib/utils/normalize-bundler-options.d.ts +7 -11
  64. package/lib/utils/normalize-bundler-options.js +24 -42
  65. package/lib/utils/npm-oidc.d.ts +23 -37
  66. package/lib/utils/npm-oidc.js +28 -53
  67. package/lib/utils/npm-otp-cache.d.ts +4 -4
  68. package/lib/utils/npm-otp-cache.js +20 -25
  69. package/lib/utils/npm-otp.d.ts +20 -30
  70. package/lib/utils/npm-otp.js +32 -51
  71. package/lib/utils/onboard-probe.d.ts +7 -13
  72. package/lib/utils/onboard-probe.js +16 -27
  73. package/lib/utils/parse-spec.d.ts +3 -3
  74. package/lib/utils/parse-spec.js +4 -5
  75. package/lib/utils/pkg-json-edit.d.ts +7 -12
  76. package/lib/utils/pkg-json-edit.js +13 -26
  77. package/lib/utils/pkg-json.d.ts +11 -18
  78. package/lib/utils/pkg-json.js +19 -28
  79. package/lib/utils/prefixed-output.d.ts +3 -4
  80. package/lib/utils/prefixed-output.js +13 -19
  81. package/lib/utils/prompt.d.ts +3 -4
  82. package/lib/utils/prompt.js +14 -18
  83. package/lib/utils/publish-diagnose.d.ts +11 -16
  84. package/lib/utils/publish-diagnose.js +15 -28
  85. package/lib/utils/publish-headers.d.ts +7 -11
  86. package/lib/utils/publish-headers.js +36 -65
  87. package/lib/utils/publishable-packages.d.ts +4 -6
  88. package/lib/utils/publishable-packages.js +14 -18
  89. package/lib/utils/resolve-gjs-entry.d.ts +5 -8
  90. package/lib/utils/resolve-gjs-entry.js +10 -27
  91. package/lib/utils/resolve-npm-package.d.ts +6 -11
  92. package/lib/utils/resolve-npm-package.js +29 -63
  93. package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
  94. package/lib/utils/resolve-plugin-by-name.js +9 -14
  95. package/lib/utils/run-gjs.d.ts +16 -21
  96. package/lib/utils/run-gjs.js +31 -56
  97. package/lib/utils/run-lifecycle-script.d.ts +5 -9
  98. package/lib/utils/run-lifecycle-script.js +23 -42
  99. package/lib/utils/run-node.d.ts +21 -33
  100. package/lib/utils/run-node.js +43 -64
  101. package/lib/utils/runtimes.d.ts +25 -35
  102. package/lib/utils/runtimes.js +34 -46
  103. package/lib/utils/spawn.d.ts +32 -48
  104. package/lib/utils/spawn.js +51 -84
  105. package/lib/utils/stdin.d.ts +2 -2
  106. package/lib/utils/stdin.js +10 -25
  107. package/lib/utils/suggest.d.ts +6 -7
  108. package/lib/utils/suggest.js +8 -10
  109. package/lib/utils/system-gi.d.ts +18 -27
  110. package/lib/utils/system-gi.js +60 -114
  111. package/lib/utils/trust-registry.d.ts +4 -8
  112. package/lib/utils/trust-registry.js +7 -15
  113. package/lib/utils/win32-command.d.ts +8 -14
  114. package/lib/utils/win32-command.js +29 -43
  115. package/lib/utils/workspace-root.js +7 -12
  116. package/package.json +18 -18
@@ -1,20 +1,17 @@
1
1
  // Interactive prompts for `gjsify login` / `gjsify trust`.
2
2
  //
3
- // On a TTY every prompt runs inside a single RAW-mode session (`runRawSession`)
4
- // that reads key-by-key with manual echo and a guaranteed cooked-mode restore
5
- // (try/finally). This deliberately does NOT rely on the terminal's cooked
6
- // line-discipline nor on the shared `process.stdin` resume/pause cycle:
7
- // - The old visible prompt read a cooked line via `process.stdin` 'data'
8
- // events. With the line discipline in an unexpected state (e.g. ICRNL off
9
- // after a prior raw prompt) Enter arrived as a bare `\r` that never
10
- // terminated the cooked line — the prompt hung showing `name^M`. The
11
- // resume/pause churn on the shared stdin singleton between two sequential
12
- // prompts also intermittently dropped the line or resolved it empty.
13
- // - Reading raw + handling `\r`/`\n` ourselves removes both failure modes.
3
+ // On a TTY every prompt runs inside a single RAW-mode session (`runRawSession`) reading
4
+ // key-by-key with manual echo and a guaranteed cooked-mode restore. It deliberately relies on
5
+ // neither the terminal's cooked line-discipline nor the shared `process.stdin` resume/pause
6
+ // cycle: a cooked read with the line discipline in an unexpected state (ICRNL off after a prior
7
+ // raw prompt) saw Enter arrive as a bare `\r` that never terminated the line, hanging the prompt
8
+ // showing `name^M`, and the resume/pause churn on the shared stdin singleton between sequential
9
+ // prompts intermittently dropped the line or resolved it empty. Handling `\r`/`\n` here removes
10
+ // both failure modes.
14
11
  //
15
- // Raw mode (via @gjsify/process → terminal-native) now also clears ISIG, so
16
- // Ctrl-C is delivered as a `\x03` keystroke we handle (restore + exit) instead
17
- // of a SIGINT that would kill the process leaving the terminal in raw mode.
12
+ // Raw mode (via @gjsify/process → terminal-native) also clears ISIG, so Ctrl-C arrives as a
13
+ // `\x03` keystroke handled below (restore + exit) rather than a SIGINT that would kill the
14
+ // process leaving the terminal in raw mode.
18
15
  //
19
16
  // Non-TTY stdin (piped input, CI) keeps a plain line read so
20
17
  // `printf 'user\npass\n' | gjsify login` still works.
@@ -26,10 +23,9 @@ function isTtyStdin() {
26
23
  return Boolean(stdin.isTTY) && typeof stdin.setRawMode === 'function';
27
24
  }
28
25
  /**
29
- * Pure key handler for the raw-mode line editor — the single source of truth
30
- * for how a keystroke updates the buffer + echo. Kept pure (no I/O) so it is
31
- * unit-testable: this is what makes Enter (`\r` OR `\n`) reliably submit, masks
32
- * passwords, and treats Ctrl-C as an interrupt rather than text.
26
+ * Pure key handler for the raw-mode line editor — the single source of truth for how a keystroke
27
+ * updates the buffer + echo. Pure (no I/O) so the rules are unit-testable: Enter is `\r` OR `\n`,
28
+ * passwords mask, and Ctrl-C is an interrupt rather than text.
33
29
  */
34
30
  export function applyKey(buf, ch, mask) {
35
31
  if (ch === '\r' || ch === '\n') {
@@ -3,36 +3,31 @@ export type Diagnose404Reason = 'dead-token' | 'live-token-404' | 'unknown';
3
3
  export interface Diagnose404Result {
4
4
  /** Discriminant — drives the JSON shape + exit-code path in the caller. */
5
5
  reason: Diagnose404Reason;
6
- /** Username from `/-/whoami` when reason === 'live-token-404'. */
6
+ /** Set only when `reason === 'live-token-404'`. */
7
7
  username?: string;
8
- /** Multi-line, human-friendly hint ready for stderr emission. */
8
+ /** Multi-line hint, ready for stderr as-is. */
9
9
  message: string;
10
10
  }
11
11
  interface Diagnose404Input {
12
12
  /** Full package name including scope, e.g. `@gjsify/abort-controller`. */
13
13
  packageName: string;
14
- /** Pinned version we tried to publish. */
15
14
  version: string;
16
- /** Registry URL (with or without trailing slash). */
15
+ /** Registry URL, with or without trailing slash. */
17
16
  registry: string;
18
- /** Parsed .npmrc — used to derive Authorization on the whoami probe. */
17
+ /** Source of the Authorization header on the whoami probe. */
19
18
  npmrc: NpmrcConfig | undefined;
20
19
  }
21
20
  /**
22
- * Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: any thrown
23
- * error / non-2xx falls through to `reason: 'unknown'` so the caller's
24
- * generic error path runs untouched.
25
- *
26
- * Pure async I/O — no side effects, no process.exit, no console writes.
27
- * The caller owns presentation (stderr vs stdout, plain vs JSON).
21
+ * Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: a thrown error or
22
+ * non-2xx yields `reason: 'unknown'`, leaving the caller's generic error path
23
+ * untouched. No side effects and no writes — the caller owns presentation.
28
24
  */
29
25
  export declare function diagnose404(input: Diagnose404Input): Promise<Diagnose404Result>;
30
26
  /**
31
- * Heuristic: is the 404 body the "dead-token-or-missing-package" shape?
32
- * npm's PUT returns plain text `Not Found` (sometimes empty body). We trigger
33
- * the diagnostic for both, plus for the JSON `{"error":"Not Found"}` shape
34
- * just in case. Other 404 bodies (e.g. with a structured npm error code)
35
- * keep the generic error path.
27
+ * Is the 404 body the "dead-token-or-missing-package" shape? npm's PUT answers
28
+ * with plain-text `Not Found` or an empty body; the JSON `{"error":"Not Found"}`
29
+ * form is accepted too. Any other 404 body — notably one carrying a structured npm
30
+ * error code — keeps the generic error path.
36
31
  */
37
32
  export declare function is404DiagnosticCandidate(body: string): boolean;
38
33
  export {};
@@ -1,31 +1,19 @@
1
1
  // Dead-token vs new-package diagnostic for `gjsify publish`.
2
2
  //
3
- // When the publish PUT returns `404 Not Found` (body `"Not Found"` or empty)
4
- // it is ambiguous between two very different situations:
3
+ // A `404 Not Found` on the publish PUT is npm's shape for two very different
4
+ // causes, and guessing sends the user down the wrong path entirely: the
5
+ // `_authToken` has been revoked/expired (npm answers an unrecognised bearer with
6
+ // 404, not 401), or the scoped package genuinely is not on npm yet and needs the
7
+ // first-publish bootstrap from AGENTS.md.
5
8
  //
6
- // 1. The `_authToken` in ~/.npmrc has been revoked / expired. The npm
7
- // registry's 404 is its way of saying "we don't recognize this
8
- // bearer". Easy to mistake for a missing-package error and send the
9
- // user down the wrong path (Trusted-Publisher-bootstrap setup).
10
- // 2. The scoped `@gjsify/<pkg>` genuinely doesn't exist on npm yet —
11
- // the first-publish bootstrap step from AGENTS.md.
12
- //
13
- // `GET /-/whoami` with the SAME Authorization header disambiguates: a live
14
- // token returns `{"username": "..."}`, a dead token returns `{}` (status 200
15
- // in both cases — the empty body is npm's signal).
16
- //
17
- // `npm publish` from npm-cli handles dead tokens with `401 EOTP` + a clear
18
- // "one-time-password or refresh the token" hint. The 404 path is npm's
19
- // PUT-specific shape and `gjsify publish` previously just printed the
20
- // opaque body. This helper closes the diagnostic gap.
9
+ // `GET /-/whoami` with the SAME Authorization header disambiguates: a live token
10
+ // returns `{"username": "…"}`, a dead one returns `{}` — status 200 for both, so
11
+ // the EMPTY BODY is the signal.
21
12
  import { whoami } from '@gjsify/npm-registry';
22
13
  /**
23
- * Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: any thrown
24
- * error / non-2xx falls through to `reason: 'unknown'` so the caller's
25
- * generic error path runs untouched.
26
- *
27
- * Pure async I/O — no side effects, no process.exit, no console writes.
28
- * The caller owns presentation (stderr vs stdout, plain vs JSON).
14
+ * Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: a thrown error or
15
+ * non-2xx yields `reason: 'unknown'`, leaving the caller's generic error path
16
+ * untouched. No side effects and no writes — the caller owns presentation.
29
17
  */
30
18
  export async function diagnose404(input) {
31
19
  const { packageName, version, registry, npmrc } = input;
@@ -85,11 +73,10 @@ function formatUnknown(name, version) {
85
73
  return `gjsify publish: ${name}@${version} — 404 Not Found`;
86
74
  }
87
75
  /**
88
- * Heuristic: is the 404 body the "dead-token-or-missing-package" shape?
89
- * npm's PUT returns plain text `Not Found` (sometimes empty body). We trigger
90
- * the diagnostic for both, plus for the JSON `{"error":"Not Found"}` shape
91
- * just in case. Other 404 bodies (e.g. with a structured npm error code)
92
- * keep the generic error path.
76
+ * Is the 404 body the "dead-token-or-missing-package" shape? npm's PUT answers
77
+ * with plain-text `Not Found` or an empty body; the JSON `{"error":"Not Found"}`
78
+ * form is accepted too. Any other 404 body — notably one carrying a structured npm
79
+ * error code — keeps the generic error path.
93
80
  */
94
81
  export function is404DiagnosticCandidate(body) {
95
82
  const trimmed = body.trim();
@@ -1,15 +1,13 @@
1
1
  import { type NpmrcConfig } from '@gjsify/npm-registry';
2
2
  /**
3
3
  * Percent-encode a package name for the publish PUT path, matching
4
- * npm-package-arg's `escapedName` convention (npa.js:188):
5
- * `this.escapedName = name.replace('/', '%2f')`.
4
+ * npm-package-arg's `escapedName` convention.
6
5
  *
7
6
  * Scoped: `@scope/name` → `@scope%2fname` (literal `@`, lowercase `%2f`, the
8
7
  * scope/name segments NOT otherwise re-encoded — npm only replaces the slash).
9
- * Unscoped: returned unchanged (npa stores the bare name; the only characters
10
- * npm forbids are those `encodeURIComponent` would touch, which npa rejects up
11
- * front). The npm registry is picky about this exact shape on the publish PUT
12
- * URL — `%2F` (uppercase) or a re-encoded `@` would miss the create route.
8
+ * Unscoped: returned unchanged. The registry is picky about this exact shape on
9
+ * the publish PUT URL — uppercase `%2F` or a re-encoded `@` misses the create
10
+ * route.
13
11
  */
14
12
  export declare function escapePackageName(name: string): string;
15
13
  export interface PublishHeaderOptions {
@@ -19,8 +17,7 @@ export interface PublishHeaderOptions {
19
17
  otp?: string;
20
18
  /**
21
19
  * Override the `authorization` header (e.g. an OIDC Bearer token resolved
22
- * after `buildHeaders` ran). When set it replaces whatever the npmrc auth
23
- * resolution produced.
20
+ * after `buildHeaders` ran), replacing whatever npmrc auth resolution produced.
24
21
  */
25
22
  authorization?: string;
26
23
  /**
@@ -37,8 +34,7 @@ export interface PublishHeaderOptions {
37
34
  * `npm-command: publish`, `npm-auth-type` (`web`, or `legacy` when `--otp`),
38
35
  * `user-agent`, and `npm-otp` when an OTP is supplied.
39
36
  *
40
- * Notably it does NOT set an `accept: *` wildcard — `npm-registry-fetch` sends
41
- * no `accept` header on the publish PUT; the stray wildcard gjsify used to send
42
- * is dropped to match.
37
+ * Notably NO `accept` header: `npm-registry-fetch` sends none on the publish PUT,
38
+ * and a wildcard here misses the create route.
43
39
  */
44
40
  export declare function buildPublishHeaders(url: string, opts: PublishHeaderOptions): Record<string, string>;
@@ -3,48 +3,30 @@
3
3
  // package correctly.
4
4
  //
5
5
  // Root cause this fixes: a first-publish of a never-before-published
6
- // `@scope/<name>` via token auth was returning a bare `404 Not Found` (body
7
- // `Not Found`, NOT a JSON npm error). That is a routing miss, not a
8
- // payload-validation failure — npm's write frontdoor keys the create path on
9
- // the `npm-command` header, which gjsify was not sending. `npm publish` with
10
- // the same ~/.npmrc token succeeds because libnpmpublish goes through
6
+ // `@scope/<name>` via token auth returned a bare `404 Not Found` (body
7
+ // `Not Found`, NOT a JSON npm error). That is a ROUTING miss, not a
8
+ // payload-validation failure — npm's write frontdoor keys the create path on the
9
+ // `npm-command` header, which gjsify was not sending. `npm publish` with the same
10
+ // ~/.npmrc token succeeds because libnpmpublish goes through
11
11
  // `npm-registry-fetch`'s `getHeaders`, which always sets `npm-command`
12
- // (+ `npm-auth-type`, + a real `user-agent`).
13
- //
14
- // Evidence (refs/npm-cli):
15
- // - `lib/npm.js:404` `flat.npmCommand = this.command` → set for EVERY npm
16
- // command; equals `"publish"` for `npm publish`.
17
- // - `node_modules/npm-registry-fetch/lib/index.js:231-233`
18
- // `if (opts.npmCommand) headers['npm-command'] = opts.npmCommand`
19
- // and `:219-221` `if (opts.authType) headers['npm-auth-type'] = …`
20
- // and `:215-217` `'user-agent': opts.userAgent`.
21
- // - `workspaces/config/lib/definitions/definitions.js:282-290` — `auth-type`
22
- // default `'web'`, flattened to `flatOptions.authType`.
23
- // - `:1581-1587` — when `--otp` is set, npm forces `auth-type` to `'legacy'`
24
- // before flattening, so an OTP publish sends `npm-auth-type: legacy`.
25
- // - `node_modules/npm-package-arg/lib/npa.js:188`
26
- // `this.escapedName = name.replace('/', '%2f')` — lowercase `%2f`,
27
- // literal `@`. (gjsify's URL encoding already matches this — it was never
28
- // the bug.)
12
+ // (+ `npm-auth-type`, + a real `user-agent`). Reference: `refs/npm-cli`.
29
13
  //
30
14
  // This module pulls in no `gi://` and no runtime fetch/Soup stack, so it is
31
- // unit-testable on both Node and GJS. Its one filesystem touch is the
32
- // manifest walk backing the user-agent version — see `cliVersion()`.
15
+ // unit-testable on both Node and GJS. Its one filesystem touch is the manifest
16
+ // walk backing the user-agent version — see `cliVersion()`.
33
17
  import { readFileSync } from 'node:fs';
34
18
  import { dirname } from 'node:path';
35
19
  import { fileURLToPath } from 'node:url';
36
20
  import { buildHeaders } from '@gjsify/npm-registry';
37
21
  /**
38
22
  * Percent-encode a package name for the publish PUT path, matching
39
- * npm-package-arg's `escapedName` convention (npa.js:188):
40
- * `this.escapedName = name.replace('/', '%2f')`.
23
+ * npm-package-arg's `escapedName` convention.
41
24
  *
42
25
  * Scoped: `@scope/name` → `@scope%2fname` (literal `@`, lowercase `%2f`, the
43
26
  * scope/name segments NOT otherwise re-encoded — npm only replaces the slash).
44
- * Unscoped: returned unchanged (npa stores the bare name; the only characters
45
- * npm forbids are those `encodeURIComponent` would touch, which npa rejects up
46
- * front). The npm registry is picky about this exact shape on the publish PUT
47
- * URL — `%2F` (uppercase) or a re-encoded `@` would miss the create route.
27
+ * Unscoped: returned unchanged. The registry is picky about this exact shape on
28
+ * the publish PUT URL — uppercase `%2F` or a re-encoded `@` misses the create
29
+ * route.
48
30
  */
49
31
  export function escapePackageName(name) {
50
32
  return name.replace('/', '%2f');
@@ -57,34 +39,26 @@ export function escapePackageName(name) {
57
39
  * `npm-command: publish`, `npm-auth-type` (`web`, or `legacy` when `--otp`),
58
40
  * `user-agent`, and `npm-otp` when an OTP is supplied.
59
41
  *
60
- * Notably it does NOT set an `accept: *` wildcard — `npm-registry-fetch` sends
61
- * no `accept` header on the publish PUT; the stray wildcard gjsify used to send
62
- * is dropped to match.
42
+ * Notably NO `accept` header: `npm-registry-fetch` sends none on the publish PUT,
43
+ * and a wildcard here misses the create route.
63
44
  */
64
45
  export function buildPublishHeaders(url, opts) {
65
46
  const headers = buildHeaders(url, { npmrc: opts.npmrc });
66
- // npm-registry-fetch never sets `accept` on the publish PUT — drop the
67
- // placeholder `accept-encoding` we don't want to influence the create route
68
- // either. (buildHeaders defaults accept-encoding to `identity`; a publish
69
- // PUT body is JSON we generated, so the encoding header is irrelevant — but
70
- // we keep it harmless and explicit by leaving it as buildHeaders set it.)
71
- // content-type: application/json — npm-registry-fetch sets this for any
72
- // non-string/non-stream/non-buffer body (index.js:63-67); our body is a
73
- // JSON string, so we set it explicitly.
47
+ // `buildHeaders`' `accept-encoding: identity` default is left as it set it —
48
+ // the PUT body is JSON we generated, so the encoding header is irrelevant here.
49
+ // npm-registry-fetch sets this for any non-string/non-stream/non-buffer body;
50
+ // ours is a JSON string, so it is set explicitly.
74
51
  headers['content-type'] = 'application/json';
75
- // npm-command: publish — THE routing header. npm sets it for every command
76
- // (npm.js:404); the registry's write frontdoor uses it to route the
77
- // create-package PUT. Missing it = bare `404 Not Found` on a new package.
52
+ // THE routing header: npm sets it for every command, and the registry's write
53
+ // frontdoor uses it to route the create-package PUT. Missing it = bare
54
+ // `404 Not Found` on a new package.
78
55
  headers['npm-command'] = 'publish';
79
- // npm-auth-type — `web` by default, `legacy` when an OTP is supplied
80
- // (definitions.js:1583-1586). Sent by getHeaders (index.js:219-221).
56
+ // npm forces auth-type `legacy` whenever `--otp` is set, `web` otherwise.
81
57
  headers['npm-auth-type'] = opts.otp ? 'legacy' : 'web';
82
- // user-agent — a real npm-shaped UA (getHeaders index.js:215-217).
83
58
  headers['user-agent'] = opts.userAgent ?? defaultUserAgent();
84
- // npm-otp — only when an OTP is supplied (index.js:243-245).
85
59
  if (opts.otp)
86
60
  headers['npm-otp'] = opts.otp;
87
- // authorization override (OIDC token resolved after npmrc auth).
61
+ // OIDC token resolved after npmrc auth.
88
62
  if (opts.authorization)
89
63
  headers['authorization'] = opts.authorization;
90
64
  return headers;
@@ -94,21 +68,18 @@ export function buildPublishHeaders(url, opts) {
94
68
  * `@gjsify/cli` is a DUAL-ENTRY package and neither layer covers both entries:
95
69
  *
96
70
  * 1. **`__PACKAGE_VERSION__`** — the compile-time define
97
- * (`package.json#gjsify.defineFromPackageJson`), the mechanism
98
- * `@gjsify/npm-registry` uses for the same stale-UA bug. Zero I/O, but the
99
- * bundler only ever writes `dist/cli.gjs.mjs`: the npm-wired Node bin under
100
- * `lib/` is emitted by plain `tsc`, which knows nothing about defines. A
101
- * define alone would therefore have left the Node entry — the one `npm
102
- * install @gjsify/cli` actually runs — on a permanent placeholder.
71
+ * (`package.json#gjsify.defineFromPackageJson`). Zero I/O, but the bundler only
72
+ * writes `dist/cli.gjs.mjs`; the npm-wired Node bin under `lib/` comes from
73
+ * plain `tsc`, which knows nothing about defines. A define alone would leave the
74
+ * Node entry — the one `npm install @gjsify/cli` runs — on a permanent placeholder.
103
75
  *
104
76
  * 2. **an upward manifest walk** for the nearest `package.json` naming
105
77
  * `@gjsify/cli`, covering `lib/`. Deliberately NOT a fixed
106
- * `new URL('../../package.json', import.meta.url)` read: that resolves
107
- * correctly from `lib/utils/` but points one directory too high from
108
- * `dist/`, and the build-time `inlineStaticReads` pass that would have
109
- * frozen it is scoped to `node_modules` paths, so first-party source never
110
- * gets it. The walk is depth-independent and so works from `src/`, `lib/`,
111
- * `dist/`, and from an installed `node_modules/@gjsify/cli/**` alike.
78
+ * `new URL('../../package.json', import.meta.url)` read: that resolves correctly
79
+ * from `lib/utils/` but points one directory too high from `dist/`, and the
80
+ * build-time `inlineStaticReads` pass that would have frozen it is scoped to
81
+ * `node_modules` paths. The walk is depth-independent, so it works from `src/`,
82
+ * `lib/`, `dist/` and an installed `node_modules/@gjsify/cli/**` alike.
112
83
  *
113
84
  * `GJSIFY_CLI_PACKAGE_JSON` overrides both, matching `self-update.ts`.
114
85
  */
@@ -147,10 +118,10 @@ function walkForCliVersion() {
147
118
  return '';
148
119
  }
149
120
  /**
150
- * npm-CLI-shaped user-agent. Format mirrors npm-registry-fetch's
151
- * `default-opts.js` (`npm@<v>/node@<v>+<arch> (<platform>)`); we substitute the
152
- * gjsify CLI identity. The registry does not validate the exact value — it just
153
- * must be a non-placeholder UA, which some WAF / CDN frontdoors require.
121
+ * npm-CLI-shaped user-agent, mirroring npm-registry-fetch's
122
+ * `npm@<v>/node@<v>+<arch> (<platform>)` with the gjsify CLI identity substituted.
123
+ * The registry does not validate the value; it must merely be a non-placeholder UA,
124
+ * which some WAF / CDN frontdoors require.
154
125
  */
155
126
  function defaultUserAgent() {
156
127
  const proc = globalThis.process;
@@ -1,18 +1,16 @@
1
1
  import type { Workspace } from '@gjsify/workspace';
2
2
  /**
3
3
  * Package-group directories holding publishable packages that are NOT root
4
- * workspaces. Add one here the moment a new non-workspace publishable group
5
- * appears — this list is the only thing standing between such a package and a
6
- * silently broken first release.
4
+ * workspaces. Extend this the moment a new such group appears — the list is the
5
+ * only thing standing between one and a silently broken first release.
7
6
  */
8
7
  export declare const NON_WORKSPACE_PUBLISHABLE_DIRS: readonly [readonly ["packages", "node-gi"], readonly ["packages", "napi"]];
9
8
  /**
10
9
  * Publishable packages in {@link NON_WORKSPACE_PUBLISHABLE_DIRS}, shaped as
11
10
  * `Workspace` entries so callers can treat them like any other package.
12
11
  *
13
- * Best-effort by design: a missing directory or an unreadable/malformed
14
- * manifest is skipped rather than thrown, because this runs inside sweeps whose
15
- * value is covering everything they can reach.
12
+ * Best-effort by design — a missing directory or malformed manifest is skipped,
13
+ * not thrown: this runs inside sweeps whose value is covering everything reachable.
16
14
  */
17
15
  export declare function discoverNonWorkspacePublishables(cwd: string): Workspace[];
18
16
  /**
@@ -1,23 +1,20 @@
1
1
  // Publishable-package discovery shared by `gjsify trust` and `gjsify onboard`.
2
2
  //
3
- // `discoverWorkspaces` only sees ROOT WORKSPACES. Some publishable packages
3
+ // `discoverWorkspaces` only sees ROOT WORKSPACES, and some publishable packages
4
4
  // deliberately are not: each is an independent native engine with its own CI
5
- // workflow, kept out of the workspace graph so a full `gjsify foreach` never
6
- // tries to build it. They still publish through `release.yml`, so every sweep
7
- // that reasons about "what do we publish" has to include them — otherwise the
8
- // sweep silently under-reports and the package's first release fails the OIDC
9
- // exchange with `404 — package not found`.
10
- //
11
- // That is not hypothetical: `gjsify onboard` reported 127 packages "already
12
- // done" on 2026-07-26 while `@gjsify/napi` was neither in the list nor
13
- // published at all, because only `packages/node-gi/*` was carved out.
5
+ // workflow, kept out of the graph so a full `gjsify foreach` never tries to build
6
+ // it. They still publish through `release.yml`, so a sweep that reasons about
7
+ // "what do we publish" must include them — otherwise it silently under-reports and
8
+ // the package's first release fails the OIDC exchange with `404 — package not
9
+ // found`. Measured: `gjsify onboard` called 127 packages "already done" while
10
+ // `@gjsify/napi` was neither listed nor published, because only
11
+ // `packages/node-gi/*` had been carved out.
14
12
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
15
13
  import { dirname, join } from 'node:path';
16
14
  /**
17
15
  * Package-group directories holding publishable packages that are NOT root
18
- * workspaces. Add one here the moment a new non-workspace publishable group
19
- * appears — this list is the only thing standing between such a package and a
20
- * silently broken first release.
16
+ * workspaces. Extend this the moment a new such group appears — the list is the
17
+ * only thing standing between one and a silently broken first release.
21
18
  */
22
19
  export const NON_WORKSPACE_PUBLISHABLE_DIRS = [
23
20
  ['packages', 'node-gi'], // @gjsify/node-gi + the @gjsify/gtk-runtime-* bundles
@@ -27,13 +24,12 @@ export const NON_WORKSPACE_PUBLISHABLE_DIRS = [
27
24
  * Publishable packages in {@link NON_WORKSPACE_PUBLISHABLE_DIRS}, shaped as
28
25
  * `Workspace` entries so callers can treat them like any other package.
29
26
  *
30
- * Best-effort by design: a missing directory or an unreadable/malformed
31
- * manifest is skipped rather than thrown, because this runs inside sweeps whose
32
- * value is covering everything they can reach.
27
+ * Best-effort by design — a missing directory or malformed manifest is skipped,
28
+ * not thrown: this runs inside sweeps whose value is covering everything reachable.
33
29
  */
34
30
  export function discoverNonWorkspacePublishables(cwd) {
35
- // Walk up to the repo root. Anchored on the FIRST configured group rather
36
- // than on `packages/` itself, which also exists inside individual packages.
31
+ // Walk up to the repo root, anchored on the FIRST configured group rather than
32
+ // on `packages/` itself, which also exists inside individual packages.
37
33
  let root = cwd;
38
34
  const anchor = NON_WORKSPACE_PUBLISHABLE_DIRS[0];
39
35
  for (let i = 0; i < 8; i++) {
@@ -5,14 +5,11 @@ interface ResolvedEntry {
5
5
  }
6
6
  export declare function resolveGjsEntry(pkgDir: string, binName: string | null): ResolvedEntry;
7
7
  /**
8
- * Resolve the `--app node` bundle of a package for the non-gjs runtimes
9
- * (node/bun/deno all consume it). Resolution order:
10
- * 1. explicit `gjsify.example.node` (a package can name its node bundle)
11
- * 2. convention: the node twin of the GJS entry — swap the `<name>.gjs.<ext>`
12
- * infix to `<name>.node.<ext>` and probe `.node.{mjs,js,cjs}` (gjsify's
13
- * `build:node → dist/<name>.node.mjs` convention; the GJS entry is
14
- * `dist/<name>.gjs.js`, so the extension can differ).
15
- * Hard-fails with an actionable hint when neither is found.
8
+ * Resolve the `--app node` bundle, which node/bun/deno all consume. An explicit
9
+ * `gjsify.example.node` wins; otherwise the node twin of the GJS entry is derived by
10
+ * swapping the `.gjs.<ext>` infix for `.node.<ext>` and probing `mjs`/`js`/`cjs` —
11
+ * the extension can differ, since `build:node` emits `dist/<name>.node.mjs` while the
12
+ * GJS entry is `dist/<name>.gjs.js`.
16
13
  */
17
14
  export declare function resolveNodeEntry(pkgDir: string): string;
18
15
  export {};
@@ -1,20 +1,8 @@
1
- // Resolve the GJS entry point of an installed package.
2
- //
3
- // Per the `gjsify` field convention (see CLI reference):
4
- //
5
- // {
6
- // "gjsify": {
7
- // "main": "dist/gjs.js",
8
- // "bin": { "name-a": "dist/a.js", "name-b": "dist/b.js" }
9
- // }
10
- // }
11
- //
12
- // Resolution order:
13
- // 1. user-supplied bin name + `gjsify.bin[name]` → that path
14
- // 2. single-entry `gjsify.bin` → the only path
15
- // 3. `gjsify.main` → that path
16
- // 4. fallback: `package.json#main` → that path (advisory warning)
17
- // 5. otherwise: hard-fail with a fix hint
1
+ // Resolve the GJS entry point of an installed package, per the `gjsify` field
2
+ // convention. In order: a requested `gjsify.bin[name]`, a single-entry
3
+ // `gjsify.bin`, `gjsify.main`, then `package.json#main` — the last flagged
4
+ // `fromFallback` so the caller can warn (`commands/dlx.ts` does). No entry at all,
5
+ // or several bins with none named, hard-fails with a fix hint.
18
6
  import { readFileSync } from 'node:fs';
19
7
  import { existsSync } from 'node:fs';
20
8
  import { join, resolve } from 'node:path';
@@ -64,14 +52,11 @@ export function resolveGjsEntry(pkgDir, binName) {
64
52
  return { bundlePath, binName: resolvedBin, fromFallback };
65
53
  }
66
54
  /**
67
- * Resolve the `--app node` bundle of a package for the non-gjs runtimes
68
- * (node/bun/deno all consume it). Resolution order:
69
- * 1. explicit `gjsify.example.node` (a package can name its node bundle)
70
- * 2. convention: the node twin of the GJS entry — swap the `<name>.gjs.<ext>`
71
- * infix to `<name>.node.<ext>` and probe `.node.{mjs,js,cjs}` (gjsify's
72
- * `build:node → dist/<name>.node.mjs` convention; the GJS entry is
73
- * `dist/<name>.gjs.js`, so the extension can differ).
74
- * Hard-fails with an actionable hint when neither is found.
55
+ * Resolve the `--app node` bundle, which node/bun/deno all consume. An explicit
56
+ * `gjsify.example.node` wins; otherwise the node twin of the GJS entry is derived by
57
+ * swapping the `.gjs.<ext>` infix for `.node.<ext>` and probing `mjs`/`js`/`cjs` —
58
+ * the extension can differ, since `build:node` emits `dist/<name>.node.mjs` while the
59
+ * GJS entry is `dist/<name>.gjs.js`.
75
60
  */
76
61
  export function resolveNodeEntry(pkgDir) {
77
62
  const pkgJsonPath = join(pkgDir, 'package.json');
@@ -79,7 +64,6 @@ export function resolveNodeEntry(pkgDir) {
79
64
  throw new Error(`no package.json found at ${pkgDir}`);
80
65
  }
81
66
  const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
82
- // 1. Explicit declaration wins.
83
67
  const explicit = pkg.gjsify?.example?.node;
84
68
  if (explicit) {
85
69
  const p = resolve(pkgDir, explicit);
@@ -88,7 +72,6 @@ export function resolveNodeEntry(pkgDir) {
88
72
  }
89
73
  return p;
90
74
  }
91
- // 2. Derive from the GJS entry.
92
75
  const gjsEntry = pkg.gjsify?.main ?? (pkg.gjsify?.bin ? Object.values(pkg.gjsify.bin)[0] : undefined) ?? pkg.main;
93
76
  if (!gjsEntry) {
94
77
  throw new Error(`package "${pkg.name ?? pkgDir}" has no entry to derive a node bundle from — ` +
@@ -2,20 +2,15 @@ export interface ResolveNpmPackageOptions {
2
2
  /** Anchor directory to search from (typically the caller's cwd). */
3
3
  cwd?: string;
4
4
  /**
5
- * Bundle path or module URL to anchor the bundle-side createRequire
6
- * at. Pass `import.meta.url` from the call site so the bundle's
7
- * own node_modules chain stays reachable even when the CLI is
8
- * invoked from an unrelated cwd.
5
+ * Bundle path or module URL to anchor the bundle-side createRequire at. Pass
6
+ * `import.meta.url` from the call site so the bundle's own node_modules chain stays
7
+ * reachable even when the CLI is invoked from an unrelated cwd.
9
8
  */
10
9
  bundleUrl?: string;
11
10
  }
12
11
  /**
13
- * Resolve a bare npm specifier through multiple createRequire anchors.
14
- * Returns the absolute filesystem path of the resolved module, or null
15
- * if no anchor succeeded.
16
- *
17
- * Each anchor is tried in priority order — `GJSIFY_NODE_PATH` (highest)
18
- * → caller cwd → workspace root → bundle URL → parent-dir walk from
19
- * cwd. The first anchor whose `require.resolve()` succeeds wins.
12
+ * Resolve a bare npm specifier through the anchors listed above, returning the resolved
13
+ * module's absolute path or null when every anchor failed. Callers usually wrap the result in
14
+ * `pathToFileURL().href` before a dynamic `import(...)`.
20
15
  */
21
16
  export declare function resolveNpmPackage(specifier: string, opts?: ResolveNpmPackageOptions): string | null;