@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
@@ -2,12 +2,10 @@ import type { ChildProcess } from 'node:child_process';
2
2
  /**
3
3
  * What the calling command does once this spawn settles.
4
4
  *
5
- * - `'exit'` — the command terminates the process itself (`process.exit(…)`,
6
- * `forceExit(…)`, …). The armed GJS main loop is quit by that exit, so the
7
- * streaming async `spawn` is safe on both hosts.
8
- * - `'return'` — the command returns normally and lets the CLI unwind. Under
9
- * GJS that MUST NOT leave a main loop armed, so the child is run on the
10
- * blocking `spawnSync` path instead.
5
+ * - `'exit'` — the command terminates the process itself, which quits the armed GJS
6
+ * main loop, so the streaming async `spawn` is safe on both hosts.
7
+ * - `'return'` — the command returns and lets the CLI unwind. Under GJS that must
8
+ * not leave a main loop armed, so the child runs on the blocking `spawnSync` path.
11
9
  *
12
10
  * See the module header for the full rationale.
13
11
  */
@@ -17,59 +15,48 @@ export interface SpawnToCompletionOptions {
17
15
  completion: SpawnCompletionContract;
18
16
  /** Working directory for the child. Defaults to the parent's. */
19
17
  cwd?: string;
20
- /**
21
- * Environment for the child. Omit to inherit the parent's environment
22
- * verbatim (what `spawn` does with no `env`).
23
- */
18
+ /** Environment for the child. Omit to inherit the parent's verbatim. */
24
19
  env?: NodeJS.ProcessEnv;
25
20
  /**
26
- * Seed `FORCE_COLOR=1` (on top of `env` ?? `process.env`) unless the user
27
- * explicitly set `FORCE_COLOR` or `NO_COLOR`, so tools that key on
28
- * `process.stdout.isTTY` (chalk, picocolors, …) still emit ANSI when their
29
- * stdout is a pipe. Mirrors yarn / npm. Default `false`.
21
+ * Seed `FORCE_COLOR=1` unless `FORCE_COLOR`/`NO_COLOR` is already set, so tools
22
+ * keying on `process.stdout.isTTY` still emit ANSI into a pipe. Mirrors
23
+ * yarn/npm. Default `false`.
30
24
  */
31
25
  color?: boolean;
32
26
  /**
33
- * `'inherit'` (default) forwards the parent's stdio. `'pipe'` gives the
34
- * caller `child.stdout` / `child.stderr` via {@link onSpawn} — only
35
- * meaningful together with `completion: 'exit'` (see {@link onSpawn}).
27
+ * `'inherit'` (default) forwards the parent's stdio; `'pipe'` gives the caller
28
+ * `child.stdout`/`child.stderr` via {@link onSpawn}, so it needs
29
+ * `completion: 'exit'`.
36
30
  *
37
- * `'inherit-stderr'` inherits, but routes the child's **stdout to the
38
- * parent's stderr**. For a parent whose own stdout is a machine-readable
39
- * stream: `gjsify pack --json` emits JSON there, and a lifecycle script's
40
- * log lines interleaved into it make the result unparseable for every
41
- * caller that pipes it into `JSON.parse`.
31
+ * `'inherit-stderr'` inherits but routes the child's **stdout to the parent's
32
+ * stderr**, for a parent whose own stdout is machine-readable: `gjsify pack
33
+ * --json` emits JSON there, and interleaved lifecycle-script log lines make it
34
+ * unparseable for anything piping into `JSON.parse`.
42
35
  */
43
36
  stdio?: 'inherit' | 'pipe' | 'inherit-stderr';
44
37
  /**
45
- * Run `cmd` through the platform shell, so `&&`, `|` and env-var
46
- * references work. `cmd` is then a whole command LINE and `args` must be
47
- * empty — that is npm/yarn script semantics.
48
- *
49
- * Skips the win32 command rewrite by design, per this file's own rule:
50
- * Node routes a `shell: true` spawn through `%COMSPEC%`, which finds
51
- * `.cmd` shims on its own, so rewriting the string would corrupt it.
38
+ * Run `cmd` through the platform shell so `&&`, `|` and env-var references work.
39
+ * `cmd` is then a whole command LINE and `args` must be empty — npm/yarn script
40
+ * semantics. Skips the win32 rewrite by design: Node routes it through
41
+ * `%COMSPEC%`, which finds `.cmd` shims itself, so rewriting would corrupt it.
52
42
  */
53
43
  shell?: boolean;
54
44
  /**
55
- * Invoked synchronously with the live `ChildProcess` right after a
56
- * successful async spawn — the seam for kill-tracking registries and for
57
- * wiring piped output forwarders.
58
- *
59
- * ONLY fires on the streaming path: the blocking `spawnSync` path has no
60
- * `ChildProcess` handle at all. A site that needs the handle must
61
- * therefore declare `completion: 'exit'`.
45
+ * Called synchronously with the live `ChildProcess` after a successful async
46
+ * spawn — the seam for kill-tracking registries and piped output forwarders.
47
+ * ONLY fires on the streaming path (the blocking path has no handle), so a site
48
+ * needing the handle must declare `completion: 'exit'`.
62
49
  */
63
50
  onSpawn?: (child: ChildProcess) => void;
64
51
  /**
65
- * Map an `ENOENT` spawn failure (command not on PATH) to a domain error
66
- * carrying an install hint. Other spawn errors are rejected as-is.
52
+ * Map an `ENOENT` spawn failure to a domain error carrying an install hint.
53
+ * Other spawn errors are rejected as-is.
67
54
  */
68
55
  notFound?: (err: NodeJS.ErrnoException) => Error;
69
56
  /**
70
- * Capture ceiling for the blocking path. Defaults to 64 MiB — a
71
- * full-workspace lint with many findings runs well past Node's 1 MB
72
- * default, and an overflow would silently truncate diagnostics.
57
+ * Capture ceiling for the blocking path, default 64 MiB: a full-workspace lint
58
+ * with many findings runs well past Node's 1 MB default, and an overflow would
59
+ * silently truncate diagnostics.
73
60
  */
74
61
  maxBuffer?: number;
75
62
  }
@@ -82,13 +69,10 @@ export interface SpawnCompletion {
82
69
  /**
83
70
  * Run `cmd args…` to completion and resolve with its exit code and signal.
84
71
  *
85
- * Resolves for ANY child exit — a non-zero code is data, not an error, so each
86
- * call site keeps its own failure message. Rejects only when the child could
87
- * not be started at all (mapped through {@link SpawnToCompletionOptions.notFound}
88
- * for ENOENT).
89
- *
90
- * Picks the streaming async path or the blocking captured path per the GJS
91
- * teardown contract documented at the top of this file.
72
+ * Resolves for ANY child exit — a non-zero code is data, not an error, so each call
73
+ * site keeps its own failure message. Rejects only when the child could not be
74
+ * started (ENOENT mapped through {@link SpawnToCompletionOptions.notFound}). Picks
75
+ * the streaming or blocking path per the teardown contract in the module header.
92
76
  */
93
77
  export declare function spawnToCompletion(cmd: string, args: readonly string[], opts: SpawnToCompletionOptions): Promise<SpawnCompletion>;
94
78
  /**
@@ -1,33 +1,23 @@
1
1
  // One `spawn` → `Promise<{ code, signal }>` wrapper for the whole CLI.
2
2
  //
3
- // Six near-identical wrappers used to live in `commands/{foreach,check,
4
- // workspace,install}.ts`, `flatpak/build.ts` and `utils/oxc-resolve.ts`. They
5
- // diverged on the completion event (`close` vs `exit`), whether an ENOENT
6
- // spawn failure got a human hint, whether `FORCE_COLOR` was seeded — and, the
7
- // dangerous one, on how they handled the GJS teardown contract below.
8
- //
9
3
  // ## The GJS teardown contract — why this helper has a host branch
10
4
  //
11
5
  // `@gjsify/child_process`'s ASYNC `spawn()` calls `ensureMainLoop()`
12
- // (`@gjsify/utils`) so the `Gio.Subprocess` `wait_async` exit callback has a
13
- // running GLib main context to be dispatched on. `ensureMainLoop()` arms a
14
- // GJS **main-loop hook** (`GLib.MainLoop.runAsync()` → `setMainLoopHook`), and
15
- // GJS's `eval_module` runs that hook AFTER the entry module's top-level await
16
- // settles — entering a blocking `g_main_loop_run()` that nothing ever quits.
17
- // GJS has no atexit hook, so the only thing that tears it back down is
18
- // `process.exit()` (whose `exitProcess` idle-schedules `quitMainLoop()` +
6
+ // (`@gjsify/utils`) to give the `Gio.Subprocess` `wait_async` exit callback a
7
+ // running GLib main context. That arms a GJS main-loop hook
8
+ // (`GLib.MainLoop.runAsync()` → `setMainLoopHook`) which `eval_module` runs AFTER
9
+ // the entry module's top-level await settles, entering a blocking
10
+ // `g_main_loop_run()` nothing ever quits. GJS has no atexit hook, so only
11
+ // `process.exit()` tears it down (`exitProcess` idle-schedules `quitMainLoop()` +
19
12
  // `imports.system.exit()`).
20
13
  //
21
- // Consequence, measured on gjs 1.88 against the committed `dist/cli.gjs.mjs`:
22
- // a command that async-spawns a child and then RETURNS NORMALLY parks at 0%
23
- // CPU **after** the child has already exited and been reaped. The symptom —
24
- // a live parent with no children, blocked in `poll`, `SNl` — is *identical*
25
- // to a lost exit callback, which is how it was previously misdiagnosed as
26
- // "the async `spawn`'s exit/close event is not delivered under the bundle".
27
- // It IS delivered: instrumenting the exit handler shows it firing with the
28
- // right code; the parent then parks in the armed loop on its way out.
14
+ // So on gjs 1.88 a command that async-spawns a child and then RETURNS NORMALLY
15
+ // parks at 0% CPU AFTER the child has exited and been reaped. The symptom — live
16
+ // parent, no children, blocked in `poll`, `SNl` — is identical to a lost exit
17
+ // callback and gets misdiagnosed as one; the event IS delivered, and the parent
18
+ // then parks in the armed loop on its way out.
29
19
  //
30
- // So the branch is NOT "GJS ⇒ spawnSync". It is exactly this:
20
+ // The branch is therefore NOT "GJS ⇒ spawnSync":
31
21
  //
32
22
  // | host | caller ends with | path |
33
23
  // |------|------------------------|-------------------------------------|
@@ -35,50 +25,37 @@
35
25
  // | GJS | `process.exit(…)` | async `spawn` (streaming) |
36
26
  // | GJS | a normal `return` | blocking `spawnSync` (captured) |
37
27
  //
38
- // which is why `gjsify foreach` / `workspace` / `check` and `runGjsBundle` run
39
- // hundreds of async-spawned children under GJS without ever hanging: every one
40
- // of them ends its handler with `process.exit(…)`. `gjsify lint` did not, and
41
- // that is the hang the `spawnSync` branch in `oxc-resolve.ts` was papering
42
- // over.
43
- //
44
- // Callers therefore declare their teardown contract via the REQUIRED
45
- // {@link SpawnToCompletionOptions.completion} field. It is required on
46
- // purpose: a new call site must state which side of the table it is on rather
47
- // than silently inherit a default that may be wrong for it.
28
+ // which is why `gjsify foreach`/`workspace`/`check` and `runGjsBundle` run hundreds
29
+ // of async-spawned children under GJS without hanging: each ends its handler with
30
+ // `process.exit(…)`. `gjsify lint` did not, and that was the hang. Hence the
31
+ // REQUIRED {@link SpawnToCompletionOptions.completion} field — a new call site must
32
+ // state which side of the table it is on rather than inherit a wrong default.
48
33
  //
49
- // ## When a raw `node:child_process` spawn is still fine — and when it is not
34
+ // ## When a raw `node:child_process` spawn is still fine
50
35
  //
51
- // This helper is also where the Windows command rewrite lives (`forWin32`
52
- // below), so the rule is about WHAT is being spawned, not about house style:
36
+ // This helper also carries the Windows command rewrite (`forWin32`), so the rule is
37
+ // about WHAT is spawned:
53
38
  //
54
- // • spawning a real executable — `git`, `gh`, `node`, `gjs`, `flatpak`, or
55
- // `process.execPath` — may use `node:child_process` directly. Windows
56
- // resolves those via `.exe` on its own.
57
- // • spawning with `shell: true` is fine too: Node routes it through
58
- // `%COMSPEC%`, which finds `.cmd` shims.
59
- // • spawning a PACKAGE MANAGER or any other `node_modules/.bin` entry by
60
- // BARE NAME — `npm`, `yarn`, `npx`, `tsc`, `oxlint`, or a `pm` variable
61
- // from `detectPackageManager()` — MUST come through here. On Windows those
62
- // are `.cmd` shims: `spawn('npm')` is ENOENT and `spawn('npm.cmd')` is
63
- // EINVAL, so a raw spawn cannot work at all.
39
+ // • a real executable (`git`, `gh`, `node`, `gjs`, `flatpak`, `process.execPath`)
40
+ // may use `node:child_process` directly — Windows resolves those via `.exe`.
41
+ // • `shell: true` is fine — Node routes it through `%COMSPEC%`, which finds
42
+ // `.cmd` shims.
43
+ // • a PACKAGE MANAGER or any `node_modules/.bin` entry by BARE NAME (`npm`,
44
+ // `yarn`, `npx`, `tsc`, `oxlint`, a `pm` from `detectPackageManager()`) MUST
45
+ // come through here: on Windows those are `.cmd` shims, where `spawn('npm')` is
46
+ // ENOENT and `spawn('npm.cmd')` is EINVAL, so a raw spawn cannot work at all.
64
47
  //
65
- // The third case is not hypothetical. Three sites kept a raw spawn after the
66
- // consolidation above and each one broke on win32 in its own way:
67
- // `commands/check.ts` (single-package mode) read only `r.status` and so exited
68
- // 1 with no diagnostic whatsoever; `utils/install-backend.ts` reported "npm not
69
- // found on PATH" on a host where npm was on PATH; `commands/onboard.ts` broke
70
- // the pre-publish build. All three were invisible to Linux and macOS CI.
48
+ // The third case is not hypothetical, and each instance was invisible to Linux and
49
+ // macOS CI: raw spawns left `commands/check.ts` exiting 1 with no diagnostic,
50
+ // `utils/install-backend.ts` reporting "npm not found on PATH" where npm was on
51
+ // PATH, and `commands/onboard.ts` breaking the pre-publish build.
71
52
  import { spawn, spawnSync } from 'node:child_process';
72
53
  import { existsSync } from 'node:fs';
73
54
  import { join } from 'node:path';
74
55
  import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
75
56
  import { resolveWin32Command } from './win32-command.js';
76
- /** Default capture ceiling on the blocking path — 64 MiB. */
77
57
  const DEFAULT_MAX_BUFFER = 64 * 1024 * 1024;
78
- /**
79
- * Build the child's environment: `undefined` (inherit) unless the caller
80
- * passed one or asked for colour seeding.
81
- */
58
+ /** `undefined` (inherit) unless the caller passed an env or asked for colour. */
82
59
  function resolveEnv(opts) {
83
60
  if (!opts.color)
84
61
  return opts.env;
@@ -87,12 +64,10 @@ function resolveEnv(opts) {
87
64
  return { ...base, ...seed };
88
65
  }
89
66
  /**
90
- * Rewrite a bare command for Windows, where `spawn('npm', …)` is ENOENT and
91
- * `spawn('npm.cmd', …)` is EINVAL — see `utils/win32-command.ts`. A no-op on
92
- * every other platform, and on win32 for anything already carrying a path or an
93
- * extension. Applied to BOTH paths below so the blocking GJS branch and the
94
- * streaming one agree; `env` is the child's, because its `PATH` (with
95
- * `node_modules/.bin` prepended) is what has to be searched.
67
+ * Rewrite a bare command for Windows (see `utils/win32-command.ts`). A no-op on
68
+ * every other platform, and on win32 for anything already carrying a path or
69
+ * extension. Applied to BOTH paths below so the blocking and streaming branches
70
+ * agree; `env` is the CHILD's, because its `PATH` is what must be searched.
96
71
  */
97
72
  function forWin32(cmd, args, env) {
98
73
  const rewritten = resolveWin32Command(cmd, args, {
@@ -110,12 +85,8 @@ function mapSpawnError(err, opts) {
110
85
  return err;
111
86
  }
112
87
  /**
113
- * Whether this call has to run on the blocking path.
114
- *
115
- * Only true under GJS for a caller that returns normally — see the module
116
- * header's table. On Node the async path is always correct (and keeps the
117
- * event loop free), and under GJS a caller that exits the process quits the
118
- * armed main loop itself.
88
+ * Whether this call has to run on the blocking path — only under GJS for a caller
89
+ * that returns normally, per the module header's table.
119
90
  */
120
91
  function mustBlock(opts) {
121
92
  return isGjs() && opts.completion === 'return';
@@ -123,13 +94,10 @@ function mustBlock(opts) {
123
94
  /**
124
95
  * Run `cmd args…` to completion and resolve with its exit code and signal.
125
96
  *
126
- * Resolves for ANY child exit — a non-zero code is data, not an error, so each
127
- * call site keeps its own failure message. Rejects only when the child could
128
- * not be started at all (mapped through {@link SpawnToCompletionOptions.notFound}
129
- * for ENOENT).
130
- *
131
- * Picks the streaming async path or the blocking captured path per the GJS
132
- * teardown contract documented at the top of this file.
97
+ * Resolves for ANY child exit — a non-zero code is data, not an error, so each call
98
+ * site keeps its own failure message. Rejects only when the child could not be
99
+ * started (ENOENT mapped through {@link SpawnToCompletionOptions.notFound}). Picks
100
+ * the streaming or blocking path per the teardown contract in the module header.
133
101
  */
134
102
  export function spawnToCompletion(cmd, args, opts) {
135
103
  const env = resolveEnv(opts);
@@ -141,9 +109,9 @@ export function spawnToCompletion(cmd, args, opts) {
141
109
  if (mustBlock(opts)) {
142
110
  // `'inherit'` is NOT forwarded through `@gjsify/child_process`'s GJS
143
111
  // spawnSync path (it drives a private main context via
144
- // `communicateWithTimeout`), so the child's diagnostics would be
145
- // silently dropped. Capture them and re-emit after the child exits.
146
- // stdin stays inherited so an interactive child still reads the tty.
112
+ // `communicateWithTimeout`), so the child's diagnostics would be silently
113
+ // dropped — capture and re-emit them instead. stdin stays inherited so an
114
+ // interactive child still reads the tty.
147
115
  const r = spawnSync(invocation.cmd, invocation.args, {
148
116
  stdio: ['inherit', 'pipe', 'pipe'],
149
117
  cwd: opts.cwd,
@@ -154,9 +122,9 @@ export function spawnToCompletion(cmd, args, opts) {
154
122
  });
155
123
  if (r.error)
156
124
  return Promise.reject(mapSpawnError(r.error, opts));
157
- // The captured stdout goes to the parent's STDERR under
158
- // `'inherit-stderr'` — same destination the streaming path routes it to,
159
- // so the two agree on where a lifecycle script's chatter lands.
125
+ // Captured stdout goes to the parent's STDERR under `'inherit-stderr'`, the
126
+ // destination the streaming path routes it to, so the two agree on where a
127
+ // lifecycle script's chatter lands.
160
128
  const out = opts.stdio === 'inherit-stderr' ? process.stderr : process.stdout;
161
129
  if (r.stdout && r.stdout.length > 0)
162
130
  out.write(r.stdout);
@@ -184,9 +152,8 @@ export function spawnToCompletion(cmd, args, opts) {
184
152
  spawnOpts.windowsVerbatimArguments = true;
185
153
  const child = spawn(win.cmd, win.args, spawnOpts);
186
154
  opts.onSpawn?.(child);
187
- // `close` (not `exit`) so piped stdio is fully drained before the
188
- // caller's flushers run. With `stdio: 'inherit'` the two are emitted
189
- // back to back, so this is a strict superset of the old `exit` sites.
155
+ // `close` (not `exit`) so piped stdio is fully drained before the caller's
156
+ // flushers run; with `stdio: 'inherit'` the two are emitted back to back.
190
157
  child.on('close', (code, signal) => {
191
158
  resolvePromise({ code, signal });
192
159
  });
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
2
+ * Read the whole of stdin as UTF-8.
3
3
  *
4
4
  * Synchronous on purpose: the callers are CLI argument-parsing paths that run
5
5
  * before any main loop exists, and the async `process.stdin` route carries the
6
- * documented resume/pause races (see `utils/prompt.ts`).
6
+ * resume/pause races documented in `utils/prompt.ts`.
7
7
  */
8
8
  export declare function readStdinText(): string;
9
9
  /**
@@ -1,37 +1,22 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Read ALL of stdin, on Node AND inside the committed GJS bundles.
3
3
  //
4
- // `readFileSync(0, 'utf-8')` is the Node idiom for "slurp stdin". It used NOT to
5
- // survive the GJS bundle: `@gjsify/fs` had no numeric-file-descriptor path, so
6
- // the `0` was coerced to a PATH and the call opened the relative file `./0` —
7
- // every GJS caller died with
4
+ // The Node idiom `readFileSync(0, 'utf-8')` means the same thing under GJS
5
+ // because `@gjsify/fs` maps the parent-supplied descriptors 0/1/2 onto the
6
+ // process's own Unix streams (`packages/node/fs/src/std-fd.ts`, reached from
7
+ // `readFileSync` via `sync.ts`).
8
8
  //
9
- // Error: ENOENT: … read '0' (readStdinLines → readFileSync)
10
- //
11
- // which silently disabled the ONE fixture-driven entry point AGENTS.md
12
- // documents for testing the CI classifier (`--changed-from-stdin`).
13
- //
14
- // THE GAP IS CLOSED AT THE CORE, so the workaround is gone: `@gjsify/fs` maps
15
- // the parent-supplied descriptors 0/1/2 onto the process's own Unix streams
16
- // (`packages/node/fs/src/std-fd.ts`, reached from `readFileSync` through
17
- // `sync.ts`), which makes the Node idiom mean the same thing under GJS. This
18
- // module used to carry two GJS-only readers for that gap — a
19
- // `GioUnix.InputStream` chunk loop and a `/dev/stdin` `Gio.File` fallback —
20
- // plus a runtime branch to choose between them. All three were consumer-side
21
- // repair for a missing core capability.
22
- //
23
- // Anything re-adding a GJS branch HERE is re-adding the workaround. If stdin
24
- // misbehaves under GJS the defect is in `@gjsify/fs` and belongs there, with a
25
- // test — that is what made the branch removable in the first place
26
- // (`packages/node/fs/src/fd-ops.spec.ts` + `tests/e2e/fs-std-fd`, which needs a
27
- // real pipe and so cannot live in a unit spec).
9
+ // Keep this module runtime-agnostic: a GJS branch here would be consumer-side
10
+ // repair for a core capability. If stdin misbehaves under GJS the defect is in
11
+ // `@gjsify/fs` and belongs there, with a test — `fd-ops.spec.ts` for the fd
12
+ // mapping, `tests/e2e/affected-classifier-bundle` for the real pipe.
28
13
  import { readFileSync } from 'node:fs';
29
14
  /**
30
- * Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
15
+ * Read the whole of stdin as UTF-8.
31
16
  *
32
17
  * Synchronous on purpose: the callers are CLI argument-parsing paths that run
33
18
  * before any main loop exists, and the async `process.stdin` route carries the
34
- * documented resume/pause races (see `utils/prompt.ts`).
19
+ * resume/pause races documented in `utils/prompt.ts`.
35
20
  */
36
21
  export function readStdinText() {
37
22
  return readFileSync(0, 'utf-8');
@@ -1,13 +1,12 @@
1
1
  /**
2
- * Levenshtein edit distance between two strings (classic DP, single-row
3
- * rolling buffer). Case-sensitive; callers lower-case first if they want a
4
- * case-insensitive match.
2
+ * Levenshtein edit distance. Case-SENSITIVE — callers lower-case first if they
3
+ * want otherwise.
5
4
  */
6
5
  export declare function editDistance(a: string, b: string): number;
7
6
  /**
8
- * The closest candidate to `target` by edit distance, or `undefined` when no
9
- * candidate is close enough. "Close enough" scales with the target length so
10
- * a short name doesn't match everything and a long one tolerates a couple of
11
- * typos: `max(2, floor(len/3))`. Comparison is case-insensitive.
7
+ * The closest candidate to `target`, or `undefined` when none is close enough.
8
+ * The threshold `max(2, floor(len/3))` scales with target length so a short name
9
+ * does not match everything while a long one still tolerates a couple of typos.
10
+ * Comparison is case-insensitive.
12
11
  */
13
12
  export declare function suggestClosest(target: string, candidates: readonly string[]): string | undefined;
@@ -1,10 +1,8 @@
1
- // Small "did you mean …?" helper for CLI name typos (unknown workspace,
2
- // unknown script, …). Pure — no platform imports — so it is unit-testable
3
- // off any runtime and safe to pull into a GJS-bundled command.
1
+ // "Did you mean …?" for CLI name typos (unknown workspace, unknown script, …).
2
+ // Kept free of platform imports so it is safe to pull into a GJS-bundled command.
4
3
  /**
5
- * Levenshtein edit distance between two strings (classic DP, single-row
6
- * rolling buffer). Case-sensitive; callers lower-case first if they want a
7
- * case-insensitive match.
4
+ * Levenshtein edit distance. Case-SENSITIVE — callers lower-case first if they
5
+ * want otherwise.
8
6
  */
9
7
  export function editDistance(a, b) {
10
8
  if (a === b)
@@ -28,10 +26,10 @@ export function editDistance(a, b) {
28
26
  return prev[b.length];
29
27
  }
30
28
  /**
31
- * The closest candidate to `target` by edit distance, or `undefined` when no
32
- * candidate is close enough. "Close enough" scales with the target length so
33
- * a short name doesn't match everything and a long one tolerates a couple of
34
- * typos: `max(2, floor(len/3))`. Comparison is case-insensitive.
29
+ * The closest candidate to `target`, or `undefined` when none is close enough.
30
+ * The threshold `max(2, floor(len/3))` scales with target length so a short name
31
+ * does not match everything while a long one still tolerates a couple of typos.
32
+ * Comparison is case-insensitive.
35
33
  */
36
34
  export function suggestClosest(target, candidates) {
37
35
  if (candidates.length === 0)
@@ -9,22 +9,17 @@ export interface SystemGiOptions {
9
9
  /** `pkg-config`'s `.pc` search directories; defaults to {@link pkgConfigSearchDirs}. */
10
10
  searchDirs?: (env: Record<string, string | undefined>) => string[];
11
11
  }
12
- /**
13
- * Split an OS search-path variable, dropping empty segments.
14
- */
12
+ /** Split an OS search-path variable, dropping empty segments. */
15
13
  export declare function splitSearchPath(value: string | undefined, separator?: string): string[];
16
14
  /**
17
15
  * `pkg-config`'s OWN configured `.pc` search path plus `$PKG_CONFIG_PATH`. Every
18
16
  * entry is a `<libdir>/pkgconfig` (or `<datadir>/pkgconfig`) directory, so its
19
- * parent is a candidate libdir — which is how a prefix nobody hardcoded is
20
- * found. Measured on the reported host: `pc_path` =
21
- * `/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/usr/lib/pkgconfig:…`,
22
- * whose first parent is exactly the `/usr/local/lib` that resolves every leaf.
17
+ * parent is a candidate libdir — which is how a prefix nobody hardcoded is found.
23
18
  *
24
- * `spawnSync` reports a missing executable as `result.error`, not by throwing —
25
- * an absent `pkg-config` is expected on a runtime-only GTK install, so it is a
26
- * checked return value here and not an exception path. (`@gjsify/child_process`
27
- * matches Node here deliberately; see its `spawnSync` note.)
19
+ * `spawnSync` reports a missing executable as `result.error`, not by throwing, so
20
+ * an absent `pkg-config` (expected on a runtime-only GTK install) is a checked
21
+ * return value and not an exception path. (`@gjsify/child_process` matches Node
22
+ * here deliberately; see its `spawnSync` note.)
28
23
  */
29
24
  export declare function pkgConfigSearchDirs(env: Record<string, string | undefined>): string[];
30
25
  /**
@@ -42,19 +37,16 @@ export declare function pkgConfigSearchDirs(env: Record<string, string | undefin
42
37
  * Sources 2 and 3 are GUESSES at a prefix, so each must show the
43
38
  * `girepository-1.0/` marker before it is believed.
44
39
  *
45
- * Empty off darwin, and that is a statement about LOADERS, not about GI. Linux
46
- * resolves these leaves through `ld.so`'s system-wide configured cache
47
- * (`/etc/ld.so.conf.d`), which a package install populates — nothing per-process
48
- * is needed. dyld is the one loader that neither consults a system-wide config
49
- * NOR re-reads its search path after launch, which is why darwin needs the
50
- * variable set at launch and nobody else does.
40
+ * Empty off darwin, and that is a statement about LOADERS, not about GI: Linux
41
+ * resolves these leaves through `ld.so`'s system-wide cache (`/etc/ld.so.conf.d`),
42
+ * which a package install populates. dyld is the one loader that neither consults
43
+ * a system-wide config NOR re-reads its search path after launch.
51
44
  *
52
- * win32 is deliberately absent rather than proven unnecessary: `PATH` is the DLL
53
- * search path, a Windows GTK distribution puts its `bin` there itself, and
54
- * Windows re-reads that path at every `LoadLibrary` — so if a system GTK ever
55
- * turns out not to be on it, the repair is a `PATH` prepend (which
56
- * {@link buildNativeEnv} already does for the prebuilds), never a second
57
- * variable. Untested there.
45
+ * win32 is deliberately absent rather than proven unnecessary, and untested:
46
+ * `PATH` is the DLL search path, a Windows GTK distribution puts its `bin` there
47
+ * itself, and Windows re-reads that path at every `LoadLibrary` — so if a system
48
+ * GTK ever turns out not to be on it, the repair is a `PATH` prepend (which
49
+ * {@link buildNativeEnv} already does for the prebuilds), never a second variable.
58
50
  *
59
51
  * @returns absolute directories, or `[]` when this platform's loader needs none
60
52
  */
@@ -63,9 +55,8 @@ export declare function systemGiLibraryDirs({ platform, env, existsDir, searchDi
63
55
  * Whether `current` (a split `DYLD_FALLBACK_LIBRARY_PATH`) already covers every
64
56
  * directory in `wanted` — the "nothing to do" test.
65
57
  *
66
- * Kept in the port even though this CLI's consumer does not branch on it: the
67
- * mirror's value is that the two files are the SAME file, and a surface that
68
- * drifts by one export is a mirror nobody can check. `system-gi.spec.ts` pins
69
- * both.
58
+ * Kept in the port even though this CLI's consumer does not branch on it: a mirror
59
+ * that drifts by one export is a mirror nobody can check. `system-gi.spec.ts`
60
+ * pins both.
70
61
  */
71
62
  export declare function pathCovers(wanted: readonly string[], current: readonly string[]): boolean;