@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
@@ -8,10 +8,9 @@
8
8
  // ]
9
9
  // }
10
10
  //
11
- // Lets `package.json#gjsify` describe the full plugin chain without dropping
12
- // to a JS-form config file (`gjsify.config.mjs`). Resolution is anchored at
13
- // the project root (where the config lives) so the project's own
14
- // `node_modules` wins over the CLI's own dependencies.
11
+ // Lets `package.json#gjsify` describe the full plugin chain without dropping to a JS-form config
12
+ // file (`gjsify.config.mjs`). Resolution is anchored at the project root, so the project's own
13
+ // `node_modules` wins over the CLI's dependencies.
15
14
  import { createRequire } from 'node:module';
16
15
  import { join } from 'node:path';
17
16
  import { pathToFileURL } from 'node:url';
@@ -22,9 +21,8 @@ export function isPluginByName(value) {
22
21
  return (typeof value === 'object' &&
23
22
  value !== null &&
24
23
  typeof value.name === 'string' &&
25
- // RolldownPluginOption can be `false | null | undefined | Plugin | Promise<Plugin>`.
26
- // A real plugin always has a function-shape behavior; `name` alone is shared
27
- // with our shape, so we additionally require absence of plugin-shape fields.
24
+ // `name` alone does not discriminate — a real Rolldown plugin has it too — so the
25
+ // absence of every plugin-shape hook is what separates the two.
28
26
  !('apply' in value) &&
29
27
  !('resolveId' in value) &&
30
28
  !('load' in value) &&
@@ -33,13 +31,10 @@ export function isPluginByName(value) {
33
31
  !('generateBundle' in value));
34
32
  }
35
33
  /**
36
- * Resolve a list of mixed user plugins. Entries that are already plugin
37
- * objects pass through unchanged; entries shaped like `PluginByName` get
38
- * dynamically imported, instantiated with their `options`, and returned in
39
- * the same position. Resolution is anchored at `projectDir`.
40
- *
41
- * Throws when a name fails to resolve, when the chosen export is not a
42
- * function, or when the factory returns nothing.
34
+ * Resolve a list of mixed user plugins, anchored at `projectDir`. Plugin objects pass through
35
+ * unchanged; `PluginByName` entries are imported, instantiated with their `options`, and returned
36
+ * in the same position. Throws when a name fails to resolve, the chosen export is not a function,
37
+ * or the factory returns nothing.
43
38
  */
44
39
  export async function resolveUserPlugins(plugins, projectDir, options = {}) {
45
40
  const loadModule = options.loadModule ?? defaultLoadModule;
@@ -1,32 +1,34 @@
1
1
  import { type NativeEnv } from './detect-native-packages.js';
2
2
  /**
3
- * Pure env computation for a given bundle. Returns the typelib +
4
- * shared-library search paths that {@link runGjsBundle} would inject into the
5
- * spawned `gjs` process, plus the formatted env-prefix string used for the
6
- * `$ …` echo. Which library variable is set is host-dependent
7
- * (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see `buildNativeEnv`.
3
+ * Pure env computation: the typelib + shared-library search paths {@link runGjsBundle} would
4
+ * inject into the spawned `gjs`, plus the formatted prefix for the `$ …` echo. Which library
5
+ * variable is set is host-dependent (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see
6
+ * `buildNativeEnv`.
8
7
  */
9
8
  export declare function computeNativeEnvForBundle(bundlePath: string, cwd?: string, inherited?: Record<string, string | undefined>): {
10
9
  env: NativeEnv;
11
10
  envPrefix: string;
12
11
  };
13
12
  /**
14
- * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
15
- * for any installed native gjsify packages discoverable from either the CWD
16
- * or the bundle's own node_modules tree.
13
+ * Options for {@link runGjsBundle}, which runs a GJS bundle with `GI_TYPELIB_PATH` and the
14
+ * host's library variable set for any native gjsify packages discoverable from the CWD or the
15
+ * bundle's own node_modules tree.
17
16
  */
18
17
  export interface RunGjsBundleOptions {
19
18
  /**
20
- * Exit this process with code 0 once the child succeeds. Under GJS,
21
- * `ensureMainLoop()` (armed by spawn) keeps the parent's GLib loop alive
22
- * after the child exits — a TERMINAL caller (`gjsify run <file>`, `dlx`)
23
- * must opt in here or it parks forever (this exact gap hung CI's
24
- * "Test WebGL conformance" for 83 min: the suite finished in 1.35 s, the
25
- * parent `gjsify run conformance.gjs.js` never exited). Callers that
26
- * continue after the bundle (e.g. `gjsify test`'s multi-runtime
27
- * aggregation loop) MUST leave this false — an unconditional exit here
28
- * truncated `gjsify test` after the first gjs bundle.
19
+ * Exit this process with code 0 once the child succeeds. Under GJS, `ensureMainLoop()`
20
+ * (armed by spawn) keeps the parent's GLib loop alive after the child exits, so a TERMINAL
21
+ * caller (`gjsify run <file>`, `dlx`) must opt in or it parks forever — this gap hung CI's
22
+ * WebGL conformance job for 83 min on a suite that finished in 1.35 s. Callers that
23
+ * continue after the bundle (`gjsify test`'s multi-runtime loop) MUST leave this false; an
24
+ * unconditional exit truncated `gjsify test` after the first gjs bundle.
29
25
  */
30
26
  exitOnSuccess?: boolean;
27
+ /**
28
+ * Suppress the `$ <env> gjs -m …` echo. Worth its ~2 kB of env prefix for a one-shot
29
+ * `gjsify run <bundle>`; pure noise for a build script invoked a dozen times per chain
30
+ * (`utils/node-script.ts`).
31
+ */
32
+ quiet?: boolean;
31
33
  }
32
34
  export declare function runGjsBundle(bundlePath: string, extraArgs?: string[], options?: RunGjsBundleOptions): Promise<void>;
@@ -1,30 +1,22 @@
1
1
  // Shared utility for running a GJS bundle with native package env vars.
2
2
  // Used by `gjsify run`, `gjsify dlx`, and the showcase command (via dlx).
3
3
  //
4
- // Detection runs the same exhaustive node_modules walker (`detectNativePackages`)
5
- // from two starting points and merges by package name (CWD shadows bundle):
4
+ // Detection runs `detectNativePackages` from two starting points and merges by package name
5
+ // (CWD shadows bundle): `process.cwd()` for native deps in the user's project, and
6
+ // `dirname(bundlePath)` for those in whatever node_modules the bundle lives in — critical for
7
+ // `gjsify dlx`, where the bundle sits under `~/.cache/gjsify/dlx/<sha>/…` and the user's CWD
8
+ // is unrelated. The bundle-side walk also catches transitive deps' typelibs.
6
9
  //
7
- // 1. process.cwd() — picks up native deps in the user's project
8
- // (yarn / pnpm / npm node_modules walking up).
9
- // 2. dirname(bundlePath) — picks up native deps in whatever node_modules
10
- // the bundle lives in. Critical for `gjsify dlx`
11
- // where the bundle resides in
12
- // `~/.cache/gjsify/dlx/<sha>/.../node_modules/<pkg>/dist/`
13
- // and the user's CWD is unrelated. The bundle-side
14
- // walk also catches transitive deps' typelibs.
15
- //
16
- // Env composition is split out as `computeNativeEnvForBundle()` — a pure
17
- // function that takes a bundle path + cwd and returns the env it would inject.
18
- // This lets the e2e tests assert the env without spawning gjs.
10
+ // Env composition is split out as the pure `computeNativeEnvForBundle()` so the e2e tests can
11
+ // assert the env without spawning gjs.
19
12
  import { spawn } from 'node:child_process';
20
13
  import { dirname, resolve } from 'node:path';
21
14
  import { detectNativePackages, buildNativeEnv } from './detect-native-packages.js';
22
15
  /**
23
- * Pure env computation for a given bundle. Returns the typelib +
24
- * shared-library search paths that {@link runGjsBundle} would inject into the
25
- * spawned `gjs` process, plus the formatted env-prefix string used for the
26
- * `$ …` echo. Which library variable is set is host-dependent
27
- * (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see `buildNativeEnv`.
16
+ * Pure env computation: the typelib + shared-library search paths {@link runGjsBundle} would
17
+ * inject into the spawned `gjs`, plus the formatted prefix for the `$ …` echo. Which library
18
+ * variable is set is host-dependent (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see
19
+ * `buildNativeEnv`.
28
20
  */
29
21
  export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd(), inherited = process.env) {
30
22
  const resolvedBundle = resolve(bundlePath);
@@ -32,23 +24,16 @@ export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd(), inher
32
24
  const bundlePackages = detectNativePackages(dirname(resolvedBundle));
33
25
  const seen = new Set(cwdPackages.map((p) => p.name));
34
26
  const nativePackages = [...cwdPackages, ...bundlePackages.filter((p) => !seen.has(p.name))];
35
- // `inherited` feeds BOTH halves — the composition and the comparison. Split
36
- // them and the win32 branch disagrees with itself: `buildNativeEnv` writes
37
- // the library variable back under the host's own spelling (`Path` in a
38
- // stock env block), so a comparison keyed on the canonical `PATH` would
39
- // look up a key that is not there and call every run a change.
27
+ // `inherited` feeds BOTH halves — composition and comparison. Split them and the win32
28
+ // branch disagrees with itself: `buildNativeEnv` writes the library variable back under
29
+ // the host's own spelling (`Path` in a stock env block), so a comparison keyed on the
30
+ // canonical `PATH` would look up a key that is not there and call every run a change.
40
31
  const env = buildNativeEnv(nativePackages, { env: inherited });
41
- // Show only what this CLI actually CHANGED. `buildNativeEnv` prepends the
42
- // detected directories to the inherited value and returns the result
43
- // unconditionally, so with no native packages it hands back the host's own
44
- // variable verbatim — and the echo then claimed the CLI had set it.
45
- //
46
- // Harmless on Linux, where `LD_LIBRARY_PATH` is usually unset and the empty
47
- // filter caught it. On Windows the library variable IS `PATH`, which is
48
- // never empty, so every run printed a ~2 kB dump of the host PATH in front
49
- // of a command the user is invited to copy — burying the command, and
50
- // claiming a change that never happened. Comparing against the inherited
51
- // value is what makes the echo honest on both.
32
+ // Show only what this CLI actually CHANGED: `buildNativeEnv` prepends to the inherited
33
+ // value unconditionally, so with no native packages it hands back the host's own variable
34
+ // verbatim. Harmless on Linux, where `LD_LIBRARY_PATH` is usually unset; on Windows the
35
+ // library variable IS `PATH`, never empty, so every run printed a ~2 kB dump of the host
36
+ // PATH in front of a command the user is invited to copy.
52
37
  const envPrefix = Object.entries(env)
53
38
  .filter(([key, value]) => value !== undefined && value !== '' && value !== inherited[key])
54
39
  .map(([key, value]) => `${key}=${value}`)
@@ -62,18 +47,21 @@ export async function runGjsBundle(bundlePath, extraArgs = [], options = {}) {
62
47
  ...nativeEnv,
63
48
  };
64
49
  const gjsArgs = ['-m', bundlePath, ...extraArgs];
65
- // Print the exact command being executed so users can copy-paste it to
66
- // run gjs directly without the wrapper. Env vars are only shown if we
67
- // actually set any (i.e. native gjsify packages were detected).
68
- // Use stderr so that children speaking a protocol on stdout (e.g. an
69
- // MCP stdio server) receive an uncontaminated stdout stream.
50
+ // Echo the exact command so users can copy-paste it and run gjs without the wrapper.
51
+ // stderr, so children speaking a protocol on stdout (an MCP stdio server) keep a clean one.
70
52
  const gjsCommand = ['gjs', ...gjsArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
71
- console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
53
+ if (!options.quiet)
54
+ console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
72
55
  const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
73
56
  let failed = false;
57
+ // The CHILD's own exit code, re-raised verbatim; a spawn error (no `gjs` on PATH) has none
58
+ // of its own and falls back to 1. Collapsing every failure to 1 lost information callers
59
+ // act on — a chain reading `$?` to tell "tool failed" from "tool says no" saw 1 for both.
60
+ let childCode = 1;
74
61
  await new Promise((resolvePromise, reject) => {
75
62
  child.on('close', (code) => {
76
63
  if (code !== 0) {
64
+ childCode = code ?? 1;
77
65
  reject(new Error(`gjs exited with code ${code}`));
78
66
  }
79
67
  else {
@@ -86,16 +74,14 @@ export async function runGjsBundle(bundlePath, extraArgs = [], options = {}) {
86
74
  failed = true;
87
75
  });
88
76
  if (failed) {
89
- // Set `process.exitCode` synchronously BEFORE exiting: under GJS
90
- // `process.exit()` is deferred (the GLib main loop is armed), so when
91
- // this runs via run.ts's in-process script dispatch, execution returns
92
- // to the caller, which propagates by reading `process.exitCode`. Without
93
- // this, `gjsify run <script>` whose body is `gjsify run <bundle>`
94
- // swallowed a non-zero gjs exit and returned 0 — masking a failing
95
- // `test:gjs` in the `test` script chain (and, via `gjsify foreach test`,
96
- // in CI). The `return` guards against a fall-through second `exit`.
97
- process.exitCode = 1;
98
- return process.exit(1);
77
+ // Set `process.exitCode` synchronously BEFORE exiting: under GJS `process.exit()` is
78
+ // deferred (the GLib main loop is armed), so under run.ts's in-process script dispatch
79
+ // execution returns to the caller, which propagates by reading `process.exitCode`.
80
+ // Without it, `gjsify run <script>` wrapping `gjsify run <bundle>` swallowed a non-zero
81
+ // gjs exit and returned 0 — masking a failing `test:gjs` in the `test` chain, and via
82
+ // `gjsify foreach test` in CI. The `return` guards a fall-through second `exit`.
83
+ process.exitCode = childCode;
84
+ return process.exit(childCode);
99
85
  }
100
86
  // See RunGjsBundleOptions.exitOnSuccess — terminal callers opt in,
101
87
  // mid-flow callers (test.ts's runtime loop) keep the process alive.
@@ -2,20 +2,16 @@ export interface RunLifecycleScriptOptions {
2
2
  /** When true, do not throw on missing scripts — return `false` instead. */
3
3
  optional?: boolean;
4
4
  /**
5
- * Stdio inheritance for the spawned script. Default `'inherit'` so output
6
- * appears in the parent's terminal. Pass `'inherit-stderr'` to mirror
7
- * inheritance but redirect the child's stdout → parent's stderr — used by
8
- * `gjsify pack --json` and `gjsify publish` so the parent's stdout stays
9
- * a clean JSON stream (script log lines would otherwise corrupt the
10
- * machine-readable output that callers `JSON.parse`).
5
+ * Stdio for the spawned script; default `'inherit'`. `'inherit-stderr'` redirects the
6
+ * child's stdout → parent's stderr, used by `gjsify pack --json` and `gjsify publish` so
7
+ * script log lines cannot corrupt the JSON stream callers `JSON.parse`.
11
8
  */
12
9
  stdio?: 'inherit' | 'inherit-stderr' | 'pipe' | 'ignore';
13
10
  /** Extra environment variables layered on top of the defaults. */
14
11
  env?: Record<string, string>;
15
12
  }
16
13
  /**
17
- * Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
18
- * Returns `true` if the script existed and exited 0. Returns `false` if
19
- * `optional: true` and the script is missing. Throws on non-zero exit.
14
+ * Run the lifecycle script `pkg.scripts[name]` from `wsDir`. `true` when it existed and exited
15
+ * 0, `false` when it is missing and `optional` is set; throws on a non-zero exit.
20
16
  */
21
17
  export declare function runLifecycleScript(wsDir: string, pkg: Record<string, unknown>, name: string, opts?: RunLifecycleScriptOptions): Promise<boolean>;
@@ -2,47 +2,31 @@
2
2
  // `postpack`, `postpublish`, …) from inside a `gjsify pack` / `gjsify
3
3
  // publish` flow.
4
4
  //
5
- // Why this exists separately from `gjsify run`: `gjsify run <script>` ends
6
- // with `process.exit(<code>)` so it can be used as a CLI entrypoint that
7
- // behaves like `yarn run` / `npm run`. That's wrong for the embedded use
8
- // case where pack/publish needs the lifecycle script to finish, then keep
9
- // running its own logic. This helper resolves to a Promise that settles
10
- // when the child exits.
5
+ // Separate from `gjsify run` because that ends with `process.exit(<code>)` so it can act as a
6
+ // CLI entrypoint like `yarn run`. Pack/publish instead needs the lifecycle script to finish and
7
+ // then keep running its own logic, so this settles a Promise when the child exits.
11
8
  //
12
- // ## Not calling `process.exit()` is what ARMED the hang, not what avoided it
13
- //
14
- // This header used to end "no process.exit, no GLib-mainloop intermingling
15
- // from `ensureMainLoop`". The second half was exactly backwards, and it is why
16
- // nobody looked here for #1010.
17
- //
18
- // `@gjsify/child_process`'s async `spawn()` calls `ensureMainLoop()`
19
- // unconditionally — the caller has no say. That arms a GJS main-loop hook whose
20
- // blocking `g_main_loop_run()` only `process.exit()` tears down. So a helper
21
- // that spawns asynchronously and deliberately does NOT exit leaves the loop
22
- // armed forever: `gjsify pack` on a package with a `prepack` wrote its tarball,
23
- // printed its complete JSON, and then parked at 0% CPU until killed at 5m30s.
24
- // A package with no lifecycle script exited in under a second, and the same
25
- // pack under Node exited in 0.68s.
26
- //
27
- // `utils/spawn.ts` already owns that contract and makes every call site DECLARE
28
- // which side of it they are on. This helper was the one spawn in the CLI that
29
- // bypassed it — the seventh of the "six near-identical wrappers" that file was
30
- // written to consolidate. It now goes through it with `completion: 'return'`,
31
- // which is what "settle, then keep running our own logic" means.
9
+ // NOT calling `process.exit()` is what ARMS a hang, not what avoids it (#1010):
10
+ // `@gjsify/child_process`'s async `spawn()` calls `ensureMainLoop()` unconditionally — the
11
+ // caller has no say — and that main-loop hook's blocking `g_main_loop_run()` is torn down only
12
+ // by `process.exit()`. A helper that spawns asynchronously and deliberately does not exit
13
+ // therefore leaves the loop armed forever: `gjsify pack` on a package with a `prepack` wrote
14
+ // its tarball, printed its complete JSON, then parked at 0% CPU until killed at 5m30s (no
15
+ // lifecycle script: under a second; the same pack under Node: 0.68s). `utils/spawn.ts` owns
16
+ // that contract and makes every call site DECLARE which side of it it is on; this goes through
17
+ // it with `completion: 'return'`.
32
18
  //
33
19
  // Matches yarn / npm script semantics:
34
20
  // - `shell: true` so `&&` / `|` / env-var refs work
35
21
  // - PATH prepended with `<wsDir>/node_modules/.bin` + monorepo-root bin
36
- // - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version`
37
- // env vars set
22
+ // - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version` set
38
23
  // - FORCE_COLOR=1 default unless caller overrides
39
24
  import { delimiter, join } from 'node:path';
40
25
  import { describeExit, spawnToCompletion } from './spawn.js';
41
26
  import { findWorkspaceRoot } from './workspace-root.js';
42
27
  /**
43
- * Run a lifecycle script defined in `pkg.scripts[name]` from `wsDir`.
44
- * Returns `true` if the script existed and exited 0. Returns `false` if
45
- * `optional: true` and the script is missing. Throws on non-zero exit.
28
+ * Run the lifecycle script `pkg.scripts[name]` from `wsDir`. `true` when it existed and exited
29
+ * 0, `false` when it is missing and `optional` is set; throws on a non-zero exit.
46
30
  */
47
31
  export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
48
32
  const scripts = pkg.scripts ?? {};
@@ -57,11 +41,9 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
57
41
  if (monorepoRoot && monorepoRoot !== wsDir) {
58
42
  binDirs.push(join(monorepoRoot, 'node_modules', '.bin'));
59
43
  }
60
- // Match yarn / npm color-forcing default — see runScript() in run.ts
61
- // for the full reasoning. Without this, lifecycle scripts that call
62
- // tools like biome / esbuild / tsc lose ANSI color in piped contexts
63
- // (CI logs, redirected output) because `process.stdout.isTTY` is
64
- // false for the spawned child.
44
+ // Match yarn / npm color-forcing — see `runScript()` in run.ts. Without it, scripts calling
45
+ // biome / esbuild / tsc lose ANSI color in piped contexts (CI logs, redirected output)
46
+ // because `process.stdout.isTTY` is false for the spawned child.
65
47
  const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
66
48
  const env = {
67
49
  ...process.env,
@@ -72,16 +54,15 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
72
54
  npm_package_version: pkg.version ?? '',
73
55
  ...opts.env,
74
56
  };
75
- // `'ignore'` has no `spawnToCompletion` equivalent and no call site in the
76
- // tree; folding it onto `'inherit'` would silently start printing where a
77
- // caller asked for silence, so it is rejected rather than approximated.
57
+ // `'ignore'` has no `spawnToCompletion` equivalent and no call site; folding it onto
58
+ // `'inherit'` would start printing where a caller asked for silence, so it is rejected
59
+ // rather than approximated.
78
60
  if (opts.stdio === 'ignore') {
79
61
  throw new Error('gjsify lifecycle-script: stdio "ignore" is not supported — see utils/spawn.ts');
80
62
  }
81
63
  const result = await spawnToCompletion(literal, [], {
82
- // The whole point of this helper: pack/publish waits for the script and
83
- // then keeps running. Under GJS that is the blocking path, which is what
84
- // keeps the armed main loop from outliving us — see the header.
64
+ // pack/publish waits for the script, then keeps running. Under GJS that is the blocking
65
+ // path, which is what keeps the armed main loop from outliving us — see the header.
85
66
  completion: 'return',
86
67
  cwd: wsDir,
87
68
  env: env,
@@ -1,25 +1,19 @@
1
1
  import { type ExampleRuntime } from './runtimes.js';
2
2
  /**
3
- * Walk up from `startDir` looking for `<node_modules>/@gjsify/node-gi`. Returns
4
- * the package directory, or null when it is not installed anywhere on the chain.
5
- * A plain fs walk (works identically on Node and GJS) — Node's own resolver is
6
- * not available under the GJS-bundled CLI.
3
+ * Walk up from `startDir` for `<node_modules>/@gjsify/node-gi`, returning its package directory
4
+ * or null. A plain fs walk because Node's own resolver is not available under the GJS-bundled CLI.
7
5
  */
8
6
  export declare function resolveNodeGi(startDir: string): string | null;
9
7
  /**
10
- * Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
11
- * BUNDLE's own directory first, then from `cwd`.
8
+ * Resolve `@gjsify/node-gi` for a bundle about to launch: the BUNDLE's own directory first, then
9
+ * `cwd`.
12
10
  *
13
- * The bundle's directory is the correct base and cwd is the fallback, for the
14
- * same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
15
- * kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
16
- * bare specifier from inside the bundle — which Node performs relative to the
17
- * importing file, never relative to cwd. A dlx-cache layout
18
- * (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
19
- * sitting right beside the bundle and nothing at all under the user's cwd, so a
20
- * cwd-only probe rejected a run that would have worked. The cwd probe is kept
21
- * because a project-local `node_modules` legitimately satisfies a bundle built
22
- * into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
11
+ * The bundle's directory is the correct base for the same reason {@link computeNativeEnvForBundle}
12
+ * walks both: node-gi is kept EXTERNAL, so what must be satisfiable is the RUNTIME's resolution
13
+ * of a bare specifier from inside the bundle, which Node performs relative to the importing file,
14
+ * never to cwd. A dlx-cache layout has node-gi beside the bundle and nothing under the user's
15
+ * cwd, so a cwd-only probe rejected a run that would have worked. The cwd probe stays because a
16
+ * project-local `node_modules` legitimately satisfies a bundle built into a `dist/` outside it.
23
17
  */
24
18
  export declare function resolveNodeGiForBundle(bundlePath: string, cwd: string): string | null;
25
19
  /** Pick the node executable: the current one when on Node, else PATH `node`. */
@@ -27,31 +21,27 @@ export declare function nodeBinary(): string;
27
21
  export interface RunNodeBundleOptions {
28
22
  /** Exit this process with code 0 once the child succeeds (terminal callers). */
29
23
  exitOnSuccess?: boolean;
24
+ /** Suppress the `$ <env> node …` echo — see `RunGjsBundleOptions.quiet`. */
25
+ quiet?: boolean;
30
26
  }
31
27
  /**
32
- * Run a `--app node` bundle on Node.js, auto-setting LD_LIBRARY_PATH and
33
- * GI_TYPELIB_PATH for any installed native gjsify packages (same detection as
34
- * {@link runGjsBundle}). Verifies `@gjsify/node-gi` is resolvable first and
35
- * throws a clear, actionable error otherwise.
28
+ * Run a `--app node` bundle on Node.js with the native typelib env set (same detection as
29
+ * {@link runGjsBundle}). Throws when `@gjsify/node-gi` is not resolvable.
36
30
  */
37
31
  export declare function runNodeBundle(bundlePath: string, extraArgs?: string[], options?: RunNodeBundleOptions): Promise<void>;
38
32
  /**
39
- * Whether a `--app node` bundle actually consumes `@gjsify/node-gi` (the gi://
40
- * reverse bridge is kept EXTERNAL, so a bundle that uses gi:// imports the shim
41
- * by name). A plain Node app (express, a pure `node:*` program) does NOT, and
42
- * must run on node/bun/deno WITHOUT node-gi installed — so the node-gi check is
43
- * conditional on this, not unconditional like the storybook path.
33
+ * Whether a `--app node` bundle actually consumes `@gjsify/node-gi` — kept EXTERNAL, so a bundle
34
+ * using gi:// imports the shim by name. A plain Node app does NOT, and must run on node/bun/deno
35
+ * WITHOUT node-gi installed, which is why the check is conditional here and not unconditional
36
+ * like the storybook path.
44
37
  */
45
38
  export declare function bundleRequiresNodeGi(bundlePath: string): boolean;
46
39
  /**
47
- * Run a built bundle on a non-gjs runtime (node / bun / deno). All three consume
48
- * the SAME `--app node` bundle (Node-API is their common ABI), so this is the
49
- * shared launcher `gjsify run --runtime` and `gjsify showcase --runtime` use for
50
- * node/bun/deno. The native typelib env is wired exactly like {@link runGjsBundle}
51
- * (so a bundle pulling an `@gjsify/*` Vala bridge resolves its prebuild), and
52
- * `@gjsify/node-gi` is required ONLY when the bundle actually uses gi://.
40
+ * Run a built bundle on a non-gjs runtime (node / bun / deno). All three consume the SAME
41
+ * `--app node` bundle — Node-API is their common ABI — so this is the shared launcher for
42
+ * `gjsify run --runtime` and `gjsify showcase --runtime`. Native typelib env is wired as in
43
+ * {@link runGjsBundle}; `@gjsify/node-gi` is required ONLY when the bundle actually uses gi://.
53
44
  *
54
- * `gjs` is intentionally NOT handled here — the caller dispatches it to
55
- * {@link runGjsBundle}.
45
+ * `gjs` is intentionally NOT handled here — the caller dispatches it to {@link runGjsBundle}.
56
46
  */
57
47
  export declare function runRuntimeBundle(runtime: Exclude<ExampleRuntime, 'gjs'>, bundlePath: string, extraArgs?: string[], options?: RunNodeBundleOptions): Promise<void>;