@gjsify/cli 0.35.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 (124) hide show
  1. package/dist/cli.gjs.mjs +136 -126
  2. package/lib/actions/build.d.ts +19 -0
  3. package/lib/actions/build.js +2 -2
  4. package/lib/bundler-pick.js +72 -98
  5. package/lib/cli-fail.d.ts +4 -7
  6. package/lib/cli-fail.js +29 -55
  7. package/lib/commands/affected.js +161 -286
  8. package/lib/commands/install.d.ts +63 -66
  9. package/lib/commands/install.js +372 -536
  10. package/lib/commands/run.d.ts +1 -0
  11. package/lib/commands/run.js +37 -0
  12. package/lib/templates/install.mjs.tmpl +38 -68
  13. package/lib/types/cli-build-options.d.ts +33 -67
  14. package/lib/types/command.d.ts +9 -12
  15. package/lib/types/config-data.d.ts +125 -221
  16. package/lib/utils/auth-npmrc.d.ts +5 -10
  17. package/lib/utils/auth-npmrc.js +11 -21
  18. package/lib/utils/check-system-deps.d.ts +16 -0
  19. package/lib/utils/check-system-deps.js +49 -0
  20. package/lib/utils/clear-targets.d.ts +8 -11
  21. package/lib/utils/clear-targets.js +22 -29
  22. package/lib/utils/copy-targets.d.ts +13 -19
  23. package/lib/utils/copy-targets.js +31 -44
  24. package/lib/utils/dep-aggregation.d.ts +7 -15
  25. package/lib/utils/dep-aggregation.js +9 -22
  26. package/lib/utils/detect-native-packages.d.ts +83 -116
  27. package/lib/utils/detect-native-packages.js +111 -149
  28. package/lib/utils/dir-link.d.ts +29 -48
  29. package/lib/utils/dir-link.js +33 -56
  30. package/lib/utils/discover-showcases.d.ts +6 -7
  31. package/lib/utils/discover-showcases.js +11 -15
  32. package/lib/utils/dlx-cache.d.ts +29 -38
  33. package/lib/utils/dlx-cache.js +53 -63
  34. package/lib/utils/force-exit.d.ts +0 -3
  35. package/lib/utils/force-exit.js +13 -24
  36. package/lib/utils/gi-search-path.d.ts +16 -24
  37. package/lib/utils/gi-search-path.js +54 -82
  38. package/lib/utils/gjs-bundle-guard.d.ts +5 -13
  39. package/lib/utils/gjs-bundle-guard.js +35 -57
  40. package/lib/utils/gjs-source-escape.d.ts +2 -2
  41. package/lib/utils/gjs-source-escape.js +21 -39
  42. package/lib/utils/gjsify-shim.d.ts +26 -8
  43. package/lib/utils/gjsify-shim.js +128 -80
  44. package/lib/utils/html-entry.d.ts +8 -12
  45. package/lib/utils/html-entry.js +19 -28
  46. package/lib/utils/install-backend-native.d.ts +123 -161
  47. package/lib/utils/install-backend-native.js +536 -737
  48. package/lib/utils/install-backend.d.ts +42 -57
  49. package/lib/utils/install-backend.js +16 -21
  50. package/lib/utils/install-cache-fs.d.ts +10 -15
  51. package/lib/utils/install-cache-fs.js +18 -26
  52. package/lib/utils/install-lock.d.ts +5 -6
  53. package/lib/utils/install-lock.js +46 -65
  54. package/lib/utils/install-packument-cache.d.ts +27 -33
  55. package/lib/utils/install-packument-cache.js +44 -57
  56. package/lib/utils/install-progress.d.ts +2 -9
  57. package/lib/utils/install-progress.js +14 -39
  58. package/lib/utils/install-tarball-cache.d.ts +14 -20
  59. package/lib/utils/install-tarball-cache.js +41 -59
  60. package/lib/utils/library-output.d.ts +9 -22
  61. package/lib/utils/library-output.js +19 -37
  62. package/lib/utils/load-npmrc.d.ts +5 -6
  63. package/lib/utils/load-npmrc.js +18 -27
  64. package/lib/utils/node-bundle-guard.d.ts +4 -8
  65. package/lib/utils/node-bundle-guard.js +21 -35
  66. package/lib/utils/node-script.d.ts +12 -0
  67. package/lib/utils/node-script.js +95 -0
  68. package/lib/utils/node-version.d.ts +5 -14
  69. package/lib/utils/node-version.js +15 -24
  70. package/lib/utils/normalize-bundler-options.d.ts +7 -11
  71. package/lib/utils/normalize-bundler-options.js +24 -42
  72. package/lib/utils/npm-oidc.d.ts +23 -37
  73. package/lib/utils/npm-oidc.js +28 -53
  74. package/lib/utils/npm-otp-cache.d.ts +4 -4
  75. package/lib/utils/npm-otp-cache.js +20 -25
  76. package/lib/utils/npm-otp.d.ts +20 -30
  77. package/lib/utils/npm-otp.js +32 -51
  78. package/lib/utils/onboard-probe.d.ts +7 -13
  79. package/lib/utils/onboard-probe.js +16 -27
  80. package/lib/utils/parse-spec.d.ts +3 -3
  81. package/lib/utils/parse-spec.js +4 -5
  82. package/lib/utils/pkg-json-edit.d.ts +7 -12
  83. package/lib/utils/pkg-json-edit.js +13 -26
  84. package/lib/utils/pkg-json.d.ts +11 -18
  85. package/lib/utils/pkg-json.js +19 -28
  86. package/lib/utils/prefixed-output.d.ts +3 -4
  87. package/lib/utils/prefixed-output.js +13 -19
  88. package/lib/utils/prompt.d.ts +3 -4
  89. package/lib/utils/prompt.js +14 -18
  90. package/lib/utils/publish-diagnose.d.ts +11 -16
  91. package/lib/utils/publish-diagnose.js +15 -28
  92. package/lib/utils/publish-headers.d.ts +7 -11
  93. package/lib/utils/publish-headers.js +36 -65
  94. package/lib/utils/publishable-packages.d.ts +4 -6
  95. package/lib/utils/publishable-packages.js +14 -18
  96. package/lib/utils/resolve-gjs-entry.d.ts +5 -8
  97. package/lib/utils/resolve-gjs-entry.js +10 -27
  98. package/lib/utils/resolve-npm-package.d.ts +6 -11
  99. package/lib/utils/resolve-npm-package.js +29 -63
  100. package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
  101. package/lib/utils/resolve-plugin-by-name.js +9 -14
  102. package/lib/utils/run-gjs.d.ts +19 -17
  103. package/lib/utils/run-gjs.js +37 -51
  104. package/lib/utils/run-lifecycle-script.d.ts +5 -9
  105. package/lib/utils/run-lifecycle-script.js +23 -42
  106. package/lib/utils/run-node.d.ts +23 -33
  107. package/lib/utils/run-node.js +56 -60
  108. package/lib/utils/runtimes.d.ts +25 -35
  109. package/lib/utils/runtimes.js +34 -46
  110. package/lib/utils/spawn.d.ts +32 -48
  111. package/lib/utils/spawn.js +51 -84
  112. package/lib/utils/stdin.d.ts +2 -2
  113. package/lib/utils/stdin.js +10 -25
  114. package/lib/utils/suggest.d.ts +6 -7
  115. package/lib/utils/suggest.js +8 -10
  116. package/lib/utils/system-gi.d.ts +18 -27
  117. package/lib/utils/system-gi.js +60 -114
  118. package/lib/utils/trust-registry.d.ts +4 -8
  119. package/lib/utils/trust-registry.js +7 -15
  120. package/lib/utils/win32-command.d.ts +8 -14
  121. package/lib/utils/win32-command.js +29 -43
  122. package/lib/utils/workspace-root.js +7 -12
  123. package/package.json +18 -17
  124. package/showcases.json +6 -0
@@ -1,26 +1,20 @@
1
1
  // Shared utility for running a `--app node` bundle on Node.js with the native
2
2
  // typelib env wired the same way `runGjsBundle` does.
3
3
  //
4
- // A `gjsify build --app node` bundle of a `gi://` source runs the GObject stack
5
- // through `@gjsify/node-gi` (the Axis-5 reverse bridge). Two things have to be
6
- // true at runtime:
4
+ // A `--app node` bundle of a `gi://` source runs the GObject stack through `@gjsify/node-gi`
5
+ // (the Axis-5 reverse bridge). Two things must hold at runtime:
7
6
  //
8
- // 1. `@gjsify/node-gi` must resolve FROM THE BUNDLE — it is kept EXTERNAL by
9
- // the bundler (a native addon), so it has to physically exist in a
10
- // node_modules the bundle's own directory can reach (that is where Node
11
- // will look), with the CWD as a fallback. We verify this up front and emit
12
- // a clear hint instead of letting Node throw `ERR_MODULE_NOT_FOUND` deep in
13
- // the bundle. See `resolveNodeGiForBundle`.
14
- // 2. `gi://Gtk` / `gi://Adw` etc. must resolve. System typelibs come from the
15
- // default GI search path, but any `@gjsify/*` Vala bridge a story pulls in
16
- // ships its typelib in `prebuilds/` — so we reuse the SAME
17
- // `computeNativeEnvForBundle()` walker `runGjsBundle` uses to prepend
18
- // `GI_TYPELIB_PATH` / `LD_LIBRARY_PATH`. It is a no-op when no native
19
- // gjsify packages are present (the storybook's common case).
7
+ // 1. `@gjsify/node-gi` must resolve FROM THE BUNDLE — the bundler keeps it EXTERNAL (native
8
+ // addon), so it has to physically exist in a node_modules the bundle's own directory can
9
+ // reach, with the CWD as a fallback. Checked up front so the failure is a clear hint
10
+ // rather than an `ERR_MODULE_NOT_FOUND` deep in the bundle (`resolveNodeGiForBundle`).
11
+ // 2. `gi://Gtk` / `gi://Adw` must resolve. System typelibs come from the default GI search
12
+ // path, but an `@gjsify/*` Vala bridge ships its typelib in `prebuilds/`, so the SAME
13
+ // `computeNativeEnvForBundle()` walker `runGjsBundle` uses prepends `GI_TYPELIB_PATH` /
14
+ // `LD_LIBRARY_PATH`. A no-op when no native gjsify packages are present.
20
15
  //
21
- // The node binary is `process.execPath` when the CLI itself runs under Node, and
22
- // the PATH `node` otherwise (under the committed GJS bundle `process.execPath` is
23
- // `gjs`, which cannot run a `--app node` bundle).
16
+ // The node binary is `process.execPath` when the CLI runs under Node, else PATH `node` — under
17
+ // the GJS bundle `process.execPath` is `gjs`, which cannot run a `--app node` bundle.
24
18
  import { spawn } from 'node:child_process';
25
19
  import { existsSync, readFileSync } from 'node:fs';
26
20
  import { dirname, join, resolve } from 'node:path';
@@ -28,10 +22,8 @@ import { isNode } from '@gjsify/rolldown-plugin-gjsify/runtime';
28
22
  import { computeNativeEnvForBundle } from './run-gjs.js';
29
23
  import { RUNTIMES } from './runtimes.js';
30
24
  /**
31
- * Walk up from `startDir` looking for `<node_modules>/@gjsify/node-gi`. Returns
32
- * the package directory, or null when it is not installed anywhere on the chain.
33
- * A plain fs walk (works identically on Node and GJS) — Node's own resolver is
34
- * not available under the GJS-bundled CLI.
25
+ * Walk up from `startDir` for `<node_modules>/@gjsify/node-gi`, returning its package directory
26
+ * or null. A plain fs walk because Node's own resolver is not available under the GJS-bundled CLI.
35
27
  */
36
28
  export function resolveNodeGi(startDir) {
37
29
  let dir = resolve(startDir);
@@ -46,19 +38,15 @@ export function resolveNodeGi(startDir) {
46
38
  }
47
39
  }
48
40
  /**
49
- * Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
50
- * BUNDLE's own directory first, then from `cwd`.
41
+ * Resolve `@gjsify/node-gi` for a bundle about to launch: the BUNDLE's own directory first, then
42
+ * `cwd`.
51
43
  *
52
- * The bundle's directory is the correct base and cwd is the fallback, for the
53
- * same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
54
- * kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
55
- * bare specifier from inside the bundle — which Node performs relative to the
56
- * importing file, never relative to cwd. A dlx-cache layout
57
- * (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
58
- * sitting right beside the bundle and nothing at all under the user's cwd, so a
59
- * cwd-only probe rejected a run that would have worked. The cwd probe is kept
60
- * because a project-local `node_modules` legitimately satisfies a bundle built
61
- * into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
44
+ * The bundle's directory is the correct base for the same reason {@link computeNativeEnvForBundle}
45
+ * walks both: node-gi is kept EXTERNAL, so what must be satisfiable is the RUNTIME's resolution
46
+ * of a bare specifier from inside the bundle, which Node performs relative to the importing file,
47
+ * never to cwd. A dlx-cache layout has node-gi beside the bundle and nothing under the user's
48
+ * cwd, so a cwd-only probe rejected a run that would have worked. The cwd probe stays because a
49
+ * project-local `node_modules` legitimately satisfies a bundle built into a `dist/` outside it.
62
50
  */
63
51
  export function resolveNodeGiForBundle(bundlePath, cwd) {
64
52
  const resolvedBundle = resolve(bundlePath);
@@ -69,10 +57,8 @@ export function nodeBinary() {
69
57
  return isNode() && process.execPath ? process.execPath : 'node';
70
58
  }
71
59
  /**
72
- * Run a `--app node` bundle on Node.js, auto-setting LD_LIBRARY_PATH and
73
- * GI_TYPELIB_PATH for any installed native gjsify packages (same detection as
74
- * {@link runGjsBundle}). Verifies `@gjsify/node-gi` is resolvable first and
75
- * throws a clear, actionable error otherwise.
60
+ * Run a `--app node` bundle on Node.js with the native typelib env set (same detection as
61
+ * {@link runGjsBundle}). Throws when `@gjsify/node-gi` is not resolvable.
76
62
  */
77
63
  export async function runNodeBundle(bundlePath, extraArgs = [], options = {}) {
78
64
  const cwd = process.cwd();
@@ -85,8 +71,7 @@ export async function runNodeBundle(bundlePath, extraArgs = [], options = {}) {
85
71
  const env = { ...process.env, ...nativeEnv };
86
72
  const nodeBin = nodeBinary();
87
73
  const nodeArgs = [bundlePath, ...extraArgs];
88
- // Echo the exact command (stderr, so a child speaking a protocol on stdout
89
- // stays uncontaminated) so users can reproduce the run without the wrapper.
74
+ // Echo on stderr, so a child speaking a protocol on stdout stays uncontaminated.
90
75
  const cmd = [nodeBin, ...nodeArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
91
76
  console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${cmd}`);
92
77
  const child = spawn(nodeBin, nodeArgs, { env, stdio: 'inherit' });
@@ -106,11 +91,10 @@ export async function runNodeBundle(bundlePath, extraArgs = [], options = {}) {
106
91
  process.exit(0);
107
92
  }
108
93
  /**
109
- * Whether a `--app node` bundle actually consumes `@gjsify/node-gi` (the gi://
110
- * reverse bridge is kept EXTERNAL, so a bundle that uses gi:// imports the shim
111
- * by name). A plain Node app (express, a pure `node:*` program) does NOT, and
112
- * must run on node/bun/deno WITHOUT node-gi installed — so the node-gi check is
113
- * conditional on this, not unconditional like the storybook path.
94
+ * Whether a `--app node` bundle actually consumes `@gjsify/node-gi` — kept EXTERNAL, so a bundle
95
+ * using gi:// imports the shim by name. A plain Node app does NOT, and must run on node/bun/deno
96
+ * WITHOUT node-gi installed, which is why the check is conditional here and not unconditional
97
+ * like the storybook path.
114
98
  */
115
99
  export function bundleRequiresNodeGi(bundlePath) {
116
100
  try {
@@ -121,15 +105,12 @@ export function bundleRequiresNodeGi(bundlePath) {
121
105
  }
122
106
  }
123
107
  /**
124
- * Run a built bundle on a non-gjs runtime (node / bun / deno). All three consume
125
- * the SAME `--app node` bundle (Node-API is their common ABI), so this is the
126
- * shared launcher `gjsify run --runtime` and `gjsify showcase --runtime` use for
127
- * node/bun/deno. The native typelib env is wired exactly like {@link runGjsBundle}
128
- * (so a bundle pulling an `@gjsify/*` Vala bridge resolves its prebuild), and
129
- * `@gjsify/node-gi` is required ONLY when the bundle actually uses gi://.
108
+ * Run a built bundle on a non-gjs runtime (node / bun / deno). All three consume the SAME
109
+ * `--app node` bundle — Node-API is their common ABI — so this is the shared launcher for
110
+ * `gjsify run --runtime` and `gjsify showcase --runtime`. Native typelib env is wired as in
111
+ * {@link runGjsBundle}; `@gjsify/node-gi` is required ONLY when the bundle actually uses gi://.
130
112
  *
131
- * `gjs` is intentionally NOT handled here — the caller dispatches it to
132
- * {@link runGjsBundle}.
113
+ * `gjs` is intentionally NOT handled here — the caller dispatches it to {@link runGjsBundle}.
133
114
  */
134
115
  export async function runRuntimeBundle(runtime, bundlePath, extraArgs = [], options = {}) {
135
116
  const cwd = process.cwd();
@@ -141,27 +122,42 @@ export async function runRuntimeBundle(runtime, bundlePath, extraArgs = [], opti
141
122
  }
142
123
  const { env: nativeEnv, envPrefix } = computeNativeEnvForBundle(resolvedBundle, cwd);
143
124
  const env = { ...process.env, ...nativeEnv };
144
- // Reuse the shared RUNTIMES launch spec for the argv (deno's
145
- // `--node-modules-dir=manual`, etc.), substituting the resolved node binary
146
- // for the `node` runtime (under a GJS-hosted CLI `process.execPath` is gjs).
125
+ // The shared RUNTIMES launch spec supplies the argv (deno's `--node-modules-dir=manual`
126
+ // etc.); the `node` runtime substitutes the resolved binary, since under a GJS-hosted CLI
127
+ // `process.execPath` is gjs.
147
128
  const [, launchArgs] = RUNTIMES[runtime].launch(resolvedBundle, extraArgs);
148
129
  const cmd = runtime === 'node' ? nodeBinary() : RUNTIMES[runtime].probe;
149
130
  // Echo on stderr so a child speaking a protocol on stdout stays clean.
150
131
  const shown = [cmd, ...launchArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
151
- console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${shown}`);
132
+ if (!options.quiet)
133
+ console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${shown}`);
152
134
  const child = spawn(cmd, launchArgs, { env, stdio: 'inherit' });
135
+ // Re-raise the CHILD's own code rather than a blanket 1 — a script run through
136
+ // `--node-script` reports its status through this path, and a caller reading `$?` needs
137
+ // the real one.
138
+ let failed = false;
139
+ let childCode = 1;
153
140
  await new Promise((resolvePromise, reject) => {
154
141
  child.on('close', (code) => {
155
- if (code !== 0)
142
+ if (code !== 0) {
143
+ childCode = code ?? 1;
156
144
  reject(new Error(`${runtime} exited with code ${code}`));
145
+ }
157
146
  else
158
147
  resolvePromise();
159
148
  });
160
149
  child.on('error', reject);
161
150
  }).catch((err) => {
162
151
  console.error(err.message);
163
- process.exit(1);
152
+ failed = true;
164
153
  });
154
+ // `return process.exit(…)`, and a flag rather than an exit inside the `catch`: under GJS
155
+ // `process.exit()` is deferred and RETURNS, so a bare call in the catch fell through to the
156
+ // `exitOnSuccess` line below and scheduled a second exit.
157
+ if (failed) {
158
+ process.exitCode = childCode;
159
+ return process.exit(childCode);
160
+ }
165
161
  if (options.exitOnSuccess)
166
- process.exit(0);
162
+ return process.exit(0);
167
163
  }
@@ -16,10 +16,9 @@ export interface RuntimeSpec {
16
16
  readonly launch: (entry: string, extraArgs?: readonly string[]) => [string, string[]];
17
17
  }
18
18
  /**
19
- * The runtime map — the generalization of the node-gi harness's `RUNTIMES`.
20
19
  * `deno` uses `--node-modules-dir=manual`: `auto` would re-resolve the example's
21
- * heavy build-time dep tree (`@gjsify/cli` + platform binaries) and hang, when
22
- * the app only needs the already-linked runtime dep `@gjsify/node-gi`.
20
+ * heavy build-time dep tree (`@gjsify/cli` + platform binaries) and hang, when the
21
+ * app only needs the already-linked runtime dep `@gjsify/node-gi`.
23
22
  */
24
23
  export declare const RUNTIMES: Record<ExampleRuntime, RuntimeSpec>;
25
24
  /** Type guard: is `value` one of the four known runtimes? */
@@ -34,22 +33,18 @@ export declare function buildAppForRuntime(runtime: ExampleRuntime): 'gjs' | 'no
34
33
  * `--app gjs` bundle" and "needs a gjs interpreter" are the same fact, and a
35
34
  * hand-written second list drifts the first time a runtime is added.
36
35
  *
37
- * The caller must resolve the runtime BEFORE gating on it. `gjsify showcase`
38
- * called `runMinimalChecks()` — which marks `gjs` `required` — three statements
39
- * ABOVE the line resolving `args.runtime`, so `--runtime node` aborted with
40
- * "Missing system dependencies: ✗ GJS" before reaching the `runtime !== 'gjs'`
41
- * branch that never touches gjs. Every showcase was unreachable on a host
42
- * without gjs (Windows, plain Node/bun/deno) — including the default path,
43
- * since `defaultExampleRuntime()` falls back to the host runtime for exactly
44
- * those hosts.
36
+ * The caller must resolve the runtime BEFORE gating on it. Calling
37
+ * `runMinimalChecks()` (which marks `gjs` `required`) above the line that resolves
38
+ * `args.runtime` made every showcase unreachable on a host without gjs — `--runtime
39
+ * node` aborted with "Missing system dependencies: ✗ GJS" before reaching the
40
+ * branch that never touches gjs, the default path included.
45
41
  */
46
42
  export declare function requiresGjsSystemDeps(runtime: ExampleRuntime): boolean;
47
43
  /** Whether `<probe> --version` succeeds (i.e. the runtime binary is on PATH). */
48
44
  export declare function isRuntimeOnPath(probe: string): boolean;
49
45
  /**
50
- * Whether a specific runtime is runnable here. The HOST runtime (the one the
51
- * CLI itself executes in — gjs / node / bun / deno) is always available: we are
52
- * running on it, so no PATH probe is needed. Every other runtime is probed via
46
+ * Whether a specific runtime is runnable here. The HOST runtime needs no PATH probe
47
+ * — the CLI is executing on it; every other runtime is probed via
53
48
  * `<binary> --version`.
54
49
  */
55
50
  export declare function isRuntimeAvailable(runtime: ExampleRuntime): boolean;
@@ -59,20 +54,17 @@ export declare function availableRuntimes(): ExampleRuntime[];
59
54
  * The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
60
55
  * whenever gjs is runnable here, else the host runtime.
61
56
  *
62
- * This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
63
- * artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
64
- * showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
65
- * docs call the default. Following the host instead means `npx @gjsify/cli
66
- * showcase <name>` (host = node) silently asks for the `--app node` bundle,
67
- * which most showcases do not ship and which additionally needs
68
- * `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
69
- * the primary documented entry point failed on a missing file.
57
+ * Deliberately NOT a plain `hostRuntime()`. A showcase's canonical artifact is its
58
+ * `--app gjs` bundle (`gjsify.main`) — what every showcase ships and what `gjsify
59
+ * dlx` installs. Following the host instead makes `npx @gjsify/cli showcase <name>`
60
+ * (host = node) ask for the `--app node` bundle, which most showcases do not ship
61
+ * and which also needs `@gjsify/node-gi` in the consumer's project, so the
62
+ * first-run experience of the primary documented entry point failed on a missing
63
+ * file.
70
64
  *
71
- * Same rule `gjsify run` already applies in its bare-file path, where a
72
- * `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
73
- * here the "is it a gjs artifact" question is answered by the command instead
74
- * of by sniffing the file. `--runtime` still overrides explicitly, and a host
75
- * WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
65
+ * Same rule `gjsify run` applies in its bare-file path (`isLikelyGjsBundle`), with
66
+ * "is it a gjs artifact" answered by the command rather than by sniffing the file.
67
+ * `--runtime` still overrides, and a host WITHOUT gjs keeps following the host so
76
68
  * node-capable showcases stay runnable there.
77
69
  */
78
70
  export declare function defaultExampleRuntime(): ExampleRuntime;
@@ -82,10 +74,9 @@ interface ExampleGjsifyField {
82
74
  };
83
75
  }
84
76
  /**
85
- * Read a package's declared example runtimes. Returns the (validated) list, or
86
- * `null` when the package declares none — the "unconstrained / permissive" case.
87
- * Unknown entries in the list are dropped (so a future runtime name doesn't hard
88
- * error against an older CLI).
77
+ * A package's declared example runtimes, or `null` when it declares none (the
78
+ * permissive case). Unknown entries are dropped so a future runtime name does not
79
+ * hard-error against an older CLI.
89
80
  */
90
81
  export declare function readDeclaredRuntimes(pkg: {
91
82
  gjsify?: ExampleGjsifyField;
@@ -96,10 +87,9 @@ export interface RuntimeSupportResult {
96
87
  message?: string;
97
88
  }
98
89
  /**
99
- * Validate a requested runtime against a package's declaration. A `null`
100
- * declaration is permissive (always ok). When the runtime is not in the
101
- * declared set, returns a clear, actionable error message (never throws) so
102
- * callers can print it and exit cleanly.
90
+ * Validate a requested runtime against a package's declaration; a `null`
91
+ * declaration is permissive. Returns a message rather than throwing, so callers
92
+ * print it and exit cleanly.
103
93
  */
104
94
  export declare function checkRuntimeSupported(runtime: ExampleRuntime, declared: ExampleRuntime[] | null, name: string): RuntimeSupportResult;
105
95
  export {};
@@ -1,27 +1,24 @@
1
1
  // Shared runtime-selector tooling for running gjsify examples/showcases across
2
2
  // the FOUR runtimes gjsify targets: gjs, node, bun, deno.
3
3
  //
4
- // This is the single source of truth generalized from the node-gi quad-runtime
5
- // example's `harness.mjs`. Both the CLI (`gjsify run`/`gjsify showcase
6
- // --runtime`) and the examples (the node-gi `harness.mjs`) consume it — so the
7
- // RUNTIMES map, the runtime→build-target mapping and `availableRuntimes()` live
8
- // in ONE place instead of a per-example copy.
4
+ // Both the CLI (`gjsify run` / `gjsify showcase --runtime`) and the examples (the
5
+ // node-gi `harness.mjs`) consume this, so the RUNTIMES map, the
6
+ // runtime→build-target mapping and `availableRuntimes()` live in ONE place instead
7
+ // of a per-example copy.
9
8
  //
10
- // The module is intentionally PURE (only `node:child_process` and the pure,
11
- // side-effect-free `@gjsify/rolldown-plugin-gjsify/runtime` host detector, no
12
- // `gi://` / native imports) so an external example can deep-import it from the
13
- // installed CLI (`@gjsify/cli/lib/utils/runtimes.js`) without pulling the whole
14
- // bundler in. Mirrors the existing `@gjsify/cli/lib/utils/run-gjs.js`
15
- // deep-import precedent used by the dlx e2e suites.
9
+ // Intentionally PURE (only `node:child_process` and the side-effect-free
10
+ // `@gjsify/rolldown-plugin-gjsify/runtime` host detector, no `gi://` or native
11
+ // imports) so an external example can deep-import it from the installed CLI
12
+ // (`@gjsify/cli/lib/utils/runtimes.js`) without pulling in the whole bundler — the
13
+ // same deep-import precedent as `@gjsify/cli/lib/utils/run-gjs.js`.
16
14
  import { execFileSync } from 'node:child_process';
17
15
  import { hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
18
16
  /** All runtimes, in canonical order (gjs first — it is the reference). */
19
17
  export const EXAMPLE_RUNTIMES = ['gjs', 'node', 'bun', 'deno'];
20
18
  /**
21
- * The runtime map — the generalization of the node-gi harness's `RUNTIMES`.
22
19
  * `deno` uses `--node-modules-dir=manual`: `auto` would re-resolve the example's
23
- * heavy build-time dep tree (`@gjsify/cli` + platform binaries) and hang, when
24
- * the app only needs the already-linked runtime dep `@gjsify/node-gi`.
20
+ * heavy build-time dep tree (`@gjsify/cli` + platform binaries) and hang, when the
21
+ * app only needs the already-linked runtime dep `@gjsify/node-gi`.
25
22
  */
26
23
  export const RUNTIMES = {
27
24
  gjs: { probe: 'gjs', buildApp: 'gjs', launch: (e, a = []) => ['gjs', ['-m', e, ...a]] },
@@ -49,14 +46,11 @@ export function buildAppForRuntime(runtime) {
49
46
  * `--app gjs` bundle" and "needs a gjs interpreter" are the same fact, and a
50
47
  * hand-written second list drifts the first time a runtime is added.
51
48
  *
52
- * The caller must resolve the runtime BEFORE gating on it. `gjsify showcase`
53
- * called `runMinimalChecks()` — which marks `gjs` `required` — three statements
54
- * ABOVE the line resolving `args.runtime`, so `--runtime node` aborted with
55
- * "Missing system dependencies: ✗ GJS" before reaching the `runtime !== 'gjs'`
56
- * branch that never touches gjs. Every showcase was unreachable on a host
57
- * without gjs (Windows, plain Node/bun/deno) — including the default path,
58
- * since `defaultExampleRuntime()` falls back to the host runtime for exactly
59
- * those hosts.
49
+ * The caller must resolve the runtime BEFORE gating on it. Calling
50
+ * `runMinimalChecks()` (which marks `gjs` `required`) above the line that resolves
51
+ * `args.runtime` made every showcase unreachable on a host without gjs — `--runtime
52
+ * node` aborted with "Missing system dependencies: ✗ GJS" before reaching the
53
+ * branch that never touches gjs, the default path included.
60
54
  */
61
55
  export function requiresGjsSystemDeps(runtime) {
62
56
  return buildAppForRuntime(runtime) === 'gjs';
@@ -72,9 +66,8 @@ export function isRuntimeOnPath(probe) {
72
66
  }
73
67
  }
74
68
  /**
75
- * Whether a specific runtime is runnable here. The HOST runtime (the one the
76
- * CLI itself executes in — gjs / node / bun / deno) is always available: we are
77
- * running on it, so no PATH probe is needed. Every other runtime is probed via
69
+ * Whether a specific runtime is runnable here. The HOST runtime needs no PATH probe
70
+ * — the CLI is executing on it; every other runtime is probed via
78
71
  * `<binary> --version`.
79
72
  */
80
73
  export function isRuntimeAvailable(runtime) {
@@ -88,30 +81,26 @@ export function availableRuntimes() {
88
81
  * The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
89
82
  * whenever gjs is runnable here, else the host runtime.
90
83
  *
91
- * This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
92
- * artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
93
- * showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
94
- * docs call the default. Following the host instead means `npx @gjsify/cli
95
- * showcase <name>` (host = node) silently asks for the `--app node` bundle,
96
- * which most showcases do not ship and which additionally needs
97
- * `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
98
- * the primary documented entry point failed on a missing file.
84
+ * Deliberately NOT a plain `hostRuntime()`. A showcase's canonical artifact is its
85
+ * `--app gjs` bundle (`gjsify.main`) — what every showcase ships and what `gjsify
86
+ * dlx` installs. Following the host instead makes `npx @gjsify/cli showcase <name>`
87
+ * (host = node) ask for the `--app node` bundle, which most showcases do not ship
88
+ * and which also needs `@gjsify/node-gi` in the consumer's project, so the
89
+ * first-run experience of the primary documented entry point failed on a missing
90
+ * file.
99
91
  *
100
- * Same rule `gjsify run` already applies in its bare-file path, where a
101
- * `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
102
- * here the "is it a gjs artifact" question is answered by the command instead
103
- * of by sniffing the file. `--runtime` still overrides explicitly, and a host
104
- * WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
92
+ * Same rule `gjsify run` applies in its bare-file path (`isLikelyGjsBundle`), with
93
+ * "is it a gjs artifact" answered by the command rather than by sniffing the file.
94
+ * `--runtime` still overrides, and a host WITHOUT gjs keeps following the host so
105
95
  * node-capable showcases stay runnable there.
106
96
  */
107
97
  export function defaultExampleRuntime() {
108
98
  return isRuntimeAvailable('gjs') ? 'gjs' : hostRuntime();
109
99
  }
110
100
  /**
111
- * Read a package's declared example runtimes. Returns the (validated) list, or
112
- * `null` when the package declares none — the "unconstrained / permissive" case.
113
- * Unknown entries in the list are dropped (so a future runtime name doesn't hard
114
- * error against an older CLI).
101
+ * A package's declared example runtimes, or `null` when it declares none (the
102
+ * permissive case). Unknown entries are dropped so a future runtime name does not
103
+ * hard-error against an older CLI.
115
104
  */
116
105
  export function readDeclaredRuntimes(pkg) {
117
106
  const list = pkg?.gjsify?.example?.runtimes;
@@ -120,10 +109,9 @@ export function readDeclaredRuntimes(pkg) {
120
109
  return list.filter((v) => typeof v === 'string' && isExampleRuntime(v));
121
110
  }
122
111
  /**
123
- * Validate a requested runtime against a package's declaration. A `null`
124
- * declaration is permissive (always ok). When the runtime is not in the
125
- * declared set, returns a clear, actionable error message (never throws) so
126
- * callers can print it and exit cleanly.
112
+ * Validate a requested runtime against a package's declaration; a `null`
113
+ * declaration is permissive. Returns a message rather than throwing, so callers
114
+ * print it and exit cleanly.
127
115
  */
128
116
  export function checkRuntimeSupported(runtime, declared, name) {
129
117
  if (declared === null)
@@ -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
  /**