@gjsify/cli 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/cli.gjs.mjs +3 -3
  2. package/lib/bundler-pick.js +61 -87
  3. package/lib/cli-fail.d.ts +4 -7
  4. package/lib/cli-fail.js +29 -55
  5. package/lib/commands/affected.js +161 -286
  6. package/lib/commands/install.d.ts +63 -66
  7. package/lib/commands/install.js +372 -536
  8. package/lib/templates/install.mjs.tmpl +38 -68
  9. package/lib/types/cli-build-options.d.ts +33 -67
  10. package/lib/types/command.d.ts +9 -12
  11. package/lib/types/config-data.d.ts +125 -221
  12. package/lib/utils/auth-npmrc.d.ts +5 -10
  13. package/lib/utils/auth-npmrc.js +11 -21
  14. package/lib/utils/clear-targets.d.ts +8 -11
  15. package/lib/utils/clear-targets.js +22 -29
  16. package/lib/utils/copy-targets.d.ts +13 -19
  17. package/lib/utils/copy-targets.js +31 -44
  18. package/lib/utils/dep-aggregation.d.ts +7 -15
  19. package/lib/utils/dep-aggregation.js +9 -22
  20. package/lib/utils/detect-native-packages.d.ts +83 -116
  21. package/lib/utils/detect-native-packages.js +111 -149
  22. package/lib/utils/dir-link.d.ts +29 -48
  23. package/lib/utils/dir-link.js +33 -56
  24. package/lib/utils/discover-showcases.d.ts +6 -7
  25. package/lib/utils/discover-showcases.js +11 -15
  26. package/lib/utils/dlx-cache.d.ts +29 -38
  27. package/lib/utils/dlx-cache.js +53 -63
  28. package/lib/utils/force-exit.d.ts +0 -3
  29. package/lib/utils/force-exit.js +13 -24
  30. package/lib/utils/gi-search-path.d.ts +16 -24
  31. package/lib/utils/gi-search-path.js +54 -82
  32. package/lib/utils/gjs-bundle-guard.d.ts +5 -13
  33. package/lib/utils/gjs-bundle-guard.js +35 -57
  34. package/lib/utils/gjs-source-escape.d.ts +2 -2
  35. package/lib/utils/gjs-source-escape.js +21 -39
  36. package/lib/utils/gjsify-shim.d.ts +17 -20
  37. package/lib/utils/gjsify-shim.js +80 -120
  38. package/lib/utils/html-entry.d.ts +8 -12
  39. package/lib/utils/html-entry.js +19 -28
  40. package/lib/utils/install-backend-native.d.ts +123 -161
  41. package/lib/utils/install-backend-native.js +536 -737
  42. package/lib/utils/install-backend.d.ts +42 -57
  43. package/lib/utils/install-backend.js +16 -21
  44. package/lib/utils/install-cache-fs.d.ts +10 -15
  45. package/lib/utils/install-cache-fs.js +18 -26
  46. package/lib/utils/install-lock.d.ts +5 -6
  47. package/lib/utils/install-lock.js +46 -65
  48. package/lib/utils/install-packument-cache.d.ts +27 -33
  49. package/lib/utils/install-packument-cache.js +44 -57
  50. package/lib/utils/install-progress.d.ts +2 -9
  51. package/lib/utils/install-progress.js +14 -39
  52. package/lib/utils/install-tarball-cache.d.ts +14 -20
  53. package/lib/utils/install-tarball-cache.js +41 -59
  54. package/lib/utils/library-output.d.ts +9 -22
  55. package/lib/utils/library-output.js +19 -37
  56. package/lib/utils/load-npmrc.d.ts +5 -6
  57. package/lib/utils/load-npmrc.js +18 -27
  58. package/lib/utils/node-bundle-guard.d.ts +4 -8
  59. package/lib/utils/node-bundle-guard.js +21 -35
  60. package/lib/utils/node-script.js +33 -54
  61. package/lib/utils/node-version.d.ts +5 -14
  62. package/lib/utils/node-version.js +15 -24
  63. package/lib/utils/normalize-bundler-options.d.ts +7 -11
  64. package/lib/utils/normalize-bundler-options.js +24 -42
  65. package/lib/utils/npm-oidc.d.ts +23 -37
  66. package/lib/utils/npm-oidc.js +28 -53
  67. package/lib/utils/npm-otp-cache.d.ts +4 -4
  68. package/lib/utils/npm-otp-cache.js +20 -25
  69. package/lib/utils/npm-otp.d.ts +20 -30
  70. package/lib/utils/npm-otp.js +32 -51
  71. package/lib/utils/onboard-probe.d.ts +7 -13
  72. package/lib/utils/onboard-probe.js +16 -27
  73. package/lib/utils/parse-spec.d.ts +3 -3
  74. package/lib/utils/parse-spec.js +4 -5
  75. package/lib/utils/pkg-json-edit.d.ts +7 -12
  76. package/lib/utils/pkg-json-edit.js +13 -26
  77. package/lib/utils/pkg-json.d.ts +11 -18
  78. package/lib/utils/pkg-json.js +19 -28
  79. package/lib/utils/prefixed-output.d.ts +3 -4
  80. package/lib/utils/prefixed-output.js +13 -19
  81. package/lib/utils/prompt.d.ts +3 -4
  82. package/lib/utils/prompt.js +14 -18
  83. package/lib/utils/publish-diagnose.d.ts +11 -16
  84. package/lib/utils/publish-diagnose.js +15 -28
  85. package/lib/utils/publish-headers.d.ts +7 -11
  86. package/lib/utils/publish-headers.js +36 -65
  87. package/lib/utils/publishable-packages.d.ts +4 -6
  88. package/lib/utils/publishable-packages.js +14 -18
  89. package/lib/utils/resolve-gjs-entry.d.ts +5 -8
  90. package/lib/utils/resolve-gjs-entry.js +10 -27
  91. package/lib/utils/resolve-npm-package.d.ts +6 -11
  92. package/lib/utils/resolve-npm-package.js +29 -63
  93. package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
  94. package/lib/utils/resolve-plugin-by-name.js +9 -14
  95. package/lib/utils/run-gjs.d.ts +16 -21
  96. package/lib/utils/run-gjs.js +31 -56
  97. package/lib/utils/run-lifecycle-script.d.ts +5 -9
  98. package/lib/utils/run-lifecycle-script.js +23 -42
  99. package/lib/utils/run-node.d.ts +21 -33
  100. package/lib/utils/run-node.js +43 -64
  101. package/lib/utils/runtimes.d.ts +25 -35
  102. package/lib/utils/runtimes.js +34 -46
  103. package/lib/utils/spawn.d.ts +32 -48
  104. package/lib/utils/spawn.js +51 -84
  105. package/lib/utils/stdin.d.ts +2 -2
  106. package/lib/utils/stdin.js +10 -25
  107. package/lib/utils/suggest.d.ts +6 -7
  108. package/lib/utils/suggest.js +8 -10
  109. package/lib/utils/system-gi.d.ts +18 -27
  110. package/lib/utils/system-gi.js +60 -114
  111. package/lib/utils/trust-registry.d.ts +4 -8
  112. package/lib/utils/trust-registry.js +7 -15
  113. package/lib/utils/win32-command.d.ts +8 -14
  114. package/lib/utils/win32-command.js +29 -43
  115. package/lib/utils/workspace-root.js +7 -12
  116. package/package.json +18 -18
@@ -1,65 +1,51 @@
1
- // Runtime bundler pick (Phase D-2.B.5b → D-3.1 runtime-aware default).
1
+ // Runtime bundler pick. `shouldUseNative()` chooses per host runtime: Node gets
2
+ // npm `rolldown(opts).write(opts.output)`; GJS gets `@gjsify/rolldown-native` (the
3
+ // Vala/Rust prebuild) via `bundleWithPlugins()`, with `runNativeBundle` replicating
4
+ // npm rolldown's `.write()` including nested chunk/asset subdirectories. The native
5
+ // engine is the GJS DEFAULT with no env var, because npm `rolldown` is a Rust N-API
6
+ // addon that cannot load under GJS at all.
2
7
  //
3
- // The engine is chosen by `shouldUseNative()` per host runtime:
4
- // - Node: npm `rolldown(opts).write(opts.output)`.
5
- // - GJS: `@gjsify/rolldown-native` (the Vala/Rust prebuild) via
6
- // `bundleWithPlugins()`. npm `rolldown` is a Rust N-API addon
7
- // that CANNOT load under GJS, so the native engine is the GJS
8
- // DEFAULT — no env var needed (this is the Node-free build
9
- // chain's bundler). This writes the BundleOutput to disk
10
- // (`runNativeBundle`), replicating npm rolldown's `.write()`
11
- // incl. nested chunk/asset subdirectories.
8
+ // `GJSIFY_BUNDLER=native|npm` overrides that: `native` throws when the prebuild is
9
+ // not loadable for the running architecture instead of silently switching engines;
10
+ // `npm` forces the npm crate (Node only).
12
11
  //
13
- // `GJSIFY_BUNDLER=native|npm` is an explicit override: `native` forces
14
- // the native engine and throws if its prebuild isn't loadable for the
15
- // running architecture (instead of silently switching engines); `npm`
16
- // forces the npm crate (Node only).
12
+ // Plugin hooks arrive either as bare functions (`load(id)`) or as
13
+ // `{filter, handler}` objects; both are translated to `NativePlugin` form, with
14
+ // `filter.id` regex/string sources becoming `idFilter.<hook>` regex strings on the
15
+ // Rust side, and `this.resolve()` / `this.warn()` / `this.error()` routing through
16
+ // the nested protocol.
17
17
  //
18
- // Plugin shape conversion: rolldown plugins may declare hooks either
19
- // as bare functions (`load(id)`) or as `{filter, handler}` objects.
20
- // Both shapes are translated to `NativePlugin` form. `filter.id`
21
- // regex/string sources become `idFilter.<hook>` regex strings on the
22
- // Rust side. `this.resolve()` / `this.warn()` / `this.error()` calls
23
- // inside hook handlers route through the B.3 nested protocol.
24
- //
25
- // Plugins that depend on rolldown context methods we don't implement
26
- // (`this.parse`, `this.emitFile`, `this.getModuleInfo`, …) will fail
27
- // at hook-call time. The current gjsify plugin set doesn't use any
28
- // of those, so we don't gate on it; future incompatibilities surface
29
- // as build errors rather than silent wrong behavior.
18
+ // Plugins using rolldown context methods this bridge does not implement
19
+ // (`this.parse`, `this.emitFile`, `this.getModuleInfo`, …) fail at hook-call time.
20
+ // Deliberately not gated: the current gjsify plugin set uses none of them, and a
21
+ // future incompatibility surfaces as a build error rather than silent wrong output.
30
22
  import { promises as fs, existsSync } from 'node:fs';
31
23
  import * as path from 'node:path';
32
24
  import { createRequire } from 'node:module';
33
25
  import { pathToFileURL } from 'node:url';
34
26
  import { resolveNpmPackage } from './utils/resolve-npm-package.js';
35
- // Static, not dynamic: `diagnoseNativeEngine()` is synchronous and is called
36
- // inside a `throw` expression. Safe — check-system-deps imports only `node:`
37
- // builtins, so there is no cycle, and every one of them is already in the GJS
38
- // bundle via `commands/install.ts`.
27
+ // Static, not dynamic: `diagnoseNativeEngine()` is synchronous and called inside a
28
+ // `throw` expression. Safe because check-system-deps imports only `node:` builtins,
29
+ // all of which the GJS bundle already carries via `commands/install.ts`.
39
30
  import { buildInstallCommand, detectPackageManager, missingSystemDepsFor } from './utils/check-system-deps.js';
40
31
  import { activateNativePrebuilds } from './utils/gi-search-path.js';
41
32
  import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
42
- // npm `rolldown` is a Rust crate with platform-specific prebuilds; loading
43
- // it eagerly at module init pulls musl-detection code that does
44
- // `require('node:fs')` synchronously — fine on Node, but fatal under GJS
45
- // where the createRequire polyfill rejects synchronous builtin loads.
46
- // Dynamic import keeps it off the GJS code path entirely; the native
47
- // branch (`@gjsify/rolldown-native`) handles bundling there.
33
+ // Loaded lazily: eager module-init loading of the npm crate pulls musl-detection
34
+ // code that calls `require('node:fs')` synchronously — fine on Node, fatal under
35
+ // GJS, where the createRequire polyfill rejects synchronous builtin loads.
48
36
  async function loadNpmRolldown() {
49
- // Under GJS the npm crate can NEVER run (its JS wrapper requires a
50
- // napi binary). Reaching this loader under GJS means the native
51
- // engine probe failed — historically that surfaced as an opaque
52
- // `ImportError: Unsupported URI scheme for importing: node` from deep
53
- // inside the crate's wrapper (e.g. on a truly-cold workspace where
54
- // `@gjsify/rolldown-native`'s lib/ is not built yet, issue #497).
55
- // Fail with the actionable diagnosis instead.
37
+ // Under GJS the npm crate can NEVER run (its JS wrapper needs a napi binary), so
38
+ // reaching this loader there means the native probe failed. That used to surface
39
+ // as an opaque `ImportError: Unsupported URI scheme for importing: node` from deep
40
+ // inside the crate's wrapper (#497, on a cold workspace where the native engine's
41
+ // lib/ was not built yet). Fail with the actionable diagnosis instead.
56
42
  if (isGjs()) {
57
43
  throw new Error('gjsify build: no usable bundler engine under GJS — `@gjsify/rolldown-native` is not loadable, ' +
58
44
  'and the npm `rolldown` engine is a Rust napi crate that cannot run under GJS.\n' +
59
45
  diagnoseNativeEngine());
60
46
  }
61
- // Indirect specifier so Rolldown's static-analysis doesn't try to
62
- // bundle the npm crate into a GJS target build.
47
+ // Indirect specifier so Rolldown's static analysis does not try to bundle the npm
48
+ // crate into a GJS target build.
63
49
  const specifier = 'rolldown';
64
50
  const target = resolveImportTargetForGjs(specifier);
65
51
  const mod = (await import(/* @vite-ignore */ target));
@@ -69,24 +55,19 @@ async function loadNpmRolldown() {
69
55
  * Say WHY the native engine could not be loaded, read off disk rather than
70
56
  * guessed.
71
57
  *
72
- * The causes need different fixes, and this message used to assert the rarest
73
- * of them ("the CLI bundle was invoked DIRECTLY … bypasses the `gjsify`
74
- * launcher") as the "most likely cause". On the by-far most common one — a
75
- * freshly cloned + freshly installed tree, where the engine's JS facade has
76
- * simply never been built — that reading is not merely unhelpful, it points at
77
- * a launcher that was in fact used correctly, so the reader goes looking for an
78
- * env-var problem that does not exist. `scripts/verify-committed-bundles.mjs`
79
- * hit exactly that and it blocked releases. ADR 0021 then removed the bypassed
80
- * launcher as a CAUSE outright, so it is no longer among the candidates at all.
58
+ * The causes need different fixes, so never name the rarest as "most likely": this
59
+ * message used to lead with a bypassed `gjsify` launcher, which on the by-far most
60
+ * common case — a fresh clone where the engine's JS facade has never been built —
61
+ * sent readers after an env-var problem that did not exist, and blocked releases via
62
+ * `scripts/verify-committed-bundles.mjs`. ADR 0021 has since removed the bypassed
63
+ * launcher as a cause outright.
81
64
  *
82
65
  * NOTHING here may throw or recurse into the failure being explained. Branches 1
83
- * and 2 are `existsSync`-only (no import, no resolution). Branch 3 additionally
84
- * asks `missingSystemDepsFor()` and reads the memoized `activateNativePrebuilds()`
85
- * result — both safe by construction rather than by a catch: the former routes
86
- * every probe through `tryExecFile`, which returns null on any failure, and the
87
- * latter is an array the failing load attempt already computed. Keep that
88
- * property when editing: a probe that dies here replaces the diagnosis with its
89
- * own stack.
66
+ * and 2 are `existsSync`-only. Branch 3 also asks `missingSystemDepsFor()` and reads
67
+ * the memoized `activateNativePrebuilds()` result — safe by construction rather than
68
+ * by a catch: the former routes every probe through `tryExecFile`, which returns
69
+ * null on any failure, and the latter is an array the failed load already computed.
70
+ * Keep that property: a probe that dies here replaces the diagnosis with its stack.
90
71
  */
91
72
  function diagnoseNativeEngine() {
92
73
  let pkgDir = null;
@@ -122,29 +103,23 @@ function diagnoseNativeEngine() {
122
103
  'of failing. That is fixed in `@gjsify/module`, so a `gjsify` OLDER than the fix still stops here — ' +
123
104
  'update it (`gjs -m install.mjs`), or seed `lib/` from the published package, which ships it.');
124
105
  }
125
- // Branch 3: installed AND built, so the typelib/prebuild LOOKUP failed.
126
- //
127
- // This branch used to assert "Most likely the CLI bundle was invoked
128
- // DIRECTLY … bypasses the launcher" — repeating, one branch later, the exact
129
- // mistake this function's docblock records fixing in branch 2: naming the
130
- // rarest cause as the most likely one. After #994 measured `ldd` over every
131
- // committed linux-x64 prebuild on a consumer-baseline host, a missing
132
- // `libjson-glib-1.0.so.0` is at least as common, and it is what ts-for-gir#437
133
- // actually hit — with this message on screen.
134
- //
135
- // So both candidates are now MEASURED, cheaply and without any call that can
136
- // throw while explaining a failure, and the measured one is printed first.
106
+ // Branch 3: installed AND built, so the typelib/prebuild LOOKUP failed. Both
107
+ // remaining candidates are MEASURED, cheaply and without any call that can throw
108
+ // while explaining a failure, and the measured one prints first: #994 ran `ldd`
109
+ // over every committed linux-x64 prebuild on a consumer-baseline host and found a
110
+ // missing `libjson-glib-1.0.so.0` at least as common as the alternative — it is
111
+ // what ts-for-gir#437 hit, with this message on screen.
137
112
  //
138
- // ADR 0021 RETIRED THE THIRD CANDIDATE. "The launcher was bypassed, export
139
- // these two variables yourself" is no longer a cause of failure at all:
140
- // `tryLoadNative()` calls `activateNativePrebuilds()` before the engine is
141
- // loaded, which puts every detected prebuild dir on girepository's typelib
142
- // AND library search paths in-process. Do not reintroduce it — besides now
143
- // being false, the path it printed (`<facade>/prebuilds/<target>`) had been
144
- // wrong since ADR 0017 moved every artifact into a per-target SIBLING
145
- // package, so it told readers to export a directory that does not exist.
113
+ // ADR 0021 RETIRED A THIRD CANDIDATE: "the launcher was bypassed, export these
114
+ // two variables yourself" is no longer a cause at all, because `tryLoadNative()`
115
+ // calls `activateNativePrebuilds()` before the engine loads, putting every
116
+ // detected prebuild dir on girepository's typelib AND library search paths
117
+ // in-process. Do not reintroduce it — besides being false, the path it printed
118
+ // (`<facade>/prebuilds/<target>`) has been wrong since ADR 0017 moved every
119
+ // artifact into a per-target SIBLING package, so it named a directory that does
120
+ // not exist.
146
121
  const parts = ['The JS facade is built, so this is the typelib/prebuild lookup failing.'];
147
- // (1) A declared system library that is not on this host. `missingSystemDepsFor`
122
+ // (1) A declared system library missing from this host. `missingSystemDepsFor`
148
123
  // reads the same tables `gjsify system-check` does, so the answer cannot drift
149
124
  // from the declaration.
150
125
  const missing = missingSystemDepsFor('@gjsify/rolldown-native');
@@ -160,11 +135,10 @@ function diagnoseNativeEngine() {
160
135
  else
161
136
  parts.push(`Install ${names} with your system package manager (this host's manager was not recognised, so no command is suggested rather than a wrong one).`);
162
137
  }
163
- // (2) The remaining cause, last because it is the least actionable: no
164
- // prebuild for this architecture. Read off the set `activateNativePrebuilds()`
165
- // ACTUALLY put on the search paths — memoized, so this is a cached-array read
166
- // that cannot throw, and unlike the old hand-composed path it names the
167
- // per-target sibling package that really holds the artifact.
138
+ // (2) No prebuild for this architecture — last because it is least actionable.
139
+ // Read off the set `activateNativePrebuilds()` ACTUALLY put on the search paths:
140
+ // a memoized cached-array read that cannot throw, and it names the per-target
141
+ // sibling package really holding the artifact.
168
142
  if (missing.length === 0) {
169
143
  const engine = activateNativePrebuilds().find((p) => p.name.startsWith('@gjsify/rolldown-native'));
170
144
  parts.push(engine
package/lib/cli-fail.d.ts CHANGED
@@ -11,12 +11,9 @@ export type CliFailure =
11
11
  };
12
12
  /**
13
13
  * Classify a yargs `fail(msg, err)` callback into the two cases that need
14
- * different output.
15
- *
16
- * Pure on purpose — the whole point is that the rule is testable without
17
- * driving a process to its death. `cli-fail.spec.ts` additionally pins the
18
- * yargs-side half (which call site passes `null`) against a real instance, so a
19
- * yargs upgrade that changes the routing fails a test instead of silently
20
- * turning every runtime error back into a help dump.
14
+ * different output. Kept pure so the rule is testable without driving a process
15
+ * to its death; `cli-fail.spec.ts` pins the yargs-side half against a real
16
+ * instance, so a yargs upgrade that reroutes `fail` fails a test instead of
17
+ * silently turning every runtime error back into a help dump.
21
18
  */
22
19
  export declare function classifyCliFailure(msg: string | null | undefined, _err: unknown): CliFailure;
package/lib/cli-fail.js CHANGED
@@ -1,66 +1,40 @@
1
- // How the CLI ends when something goes wrong — the one place that decides
2
- // whether a failure is a USAGE error (help is the right output) or a RUNTIME
3
- // error (help is noise that buries the answer).
1
+ // The one place that decides whether a CLI failure is a USAGE error (help is the
2
+ // right output) or a RUNTIME error (help buries the answer).
4
3
  //
5
- // The defect this exists for, measured from outside the repo: `gjsify build`
6
- // under GJS without `@gjsify/rolldown-native` prints a CORRECT and actionable
7
- // diagnosis — `diagnoseNativeEngine()` names the package and the exact install
8
- // command — and then yargs prints ~60 lines of `gjsify build` help on top of it
9
- // and exits 1. In a CI log the actionable line scrolls out of view and the
10
- // visible artifact is a help dump, which reads as "bad arguments". That is what
11
- // ts-for-gir's `test-e2e` leg looked like (#1005). A missing engine is not a
12
- // usage error and must not leave through the usage path.
4
+ // A missing native engine must not leave through the usage path: `gjsify build`
5
+ // under GJS without `@gjsify/rolldown-native` printed an actionable diagnosis
6
+ // naming the package and the install command, and then ~60 lines of yargs help on
7
+ // top of it, so the CI log read as "bad arguments" (#1005).
13
8
  //
14
- // Four things about yargs make this module's shape non-obvious. All four were
15
- // measured against the pinned `yargs@18.0.0` (note `@types/yargs` is still 17,
16
- // one major behind, so the types lie in one place — see below):
9
+ // Four yargs behaviours shape this module, measured against the pinned yargs 18
10
+ // (`@types/yargs` is still 17, so the types understate `fail`):
17
11
  //
18
- // 1. `.fail()` IS reached for an async handler rejection. `command.js:253-260`
19
- // does `if (isPromise(innerArgv) && !hasParseCallback()) innerArgv.catch(e =>
20
- // usage.fail(null, e))`, and `hasParseCallback()` is false because
21
- // `cli-app.ts` calls `parseAsync()` with no function argument. So no
22
- // try/catch around `parseAsync` is needed to see handler errors here.
12
+ // 1. `.fail()` IS reached for an async handler rejection, because `cli-app.ts`
13
+ // calls `parseAsync()` with no callback. No try/catch around it is needed.
14
+ // 2. `msg` is the discriminator, NOT `err`: every usage/validation/parse site
15
+ // passes a string, and only the handler rejection passes `null`. Branching on
16
+ // `err` would suppress help for genuine parser errors — exactly backwards.
17
+ // 3. Registering a handler removes yargs' own `showHelp` + `exit(1)`, so the
18
+ // usage branch must terminate itself. `throw` rather than relying on
19
+ // `showHelp()` setting `hasOutput`, which only holds while the chain is
20
+ // synchronous and would break under a future async builder or middleware.
21
+ // 4. Never re-throw the incoming `YError` — yargs routes that class back into
22
+ // `usage.fail()` and prints help twice. Throw a plain `Error` with `cause`.
23
23
  //
24
- // 2. THE DISCRIMINATOR IS `msg`, NOT `err`. Every usage/validation/parse
25
- // failure passes a STRING: all ten `validation.js` sites call
26
- // `usage.fail(msg)`, `command.js:370` calls `fail(parsed.error.message,
27
- // parsed.error)`, and `yargs-factory.js:188/192/1473` call `fail(msg, err)`.
28
- // Only `command.js:257` — the handler rejection — passes `null`. Branching on
29
- // `err` instead would suppress help for genuine parser errors, which is
30
- // exactly backwards.
31
- //
32
- // 3. REGISTERING A HANDLER REMOVES YARGS' OWN EXIT. With no `.fail()` yargs does
33
- // `showHelp('error')` + `logger.error(msg || err)` + `exit(1)` itself
34
- // (`usage.js:27-70`). With one, it does none of that — so the usage branch has
35
- // to terminate on its own. `showHelp()` happens to set `hasOutput`
36
- // (`yargs-factory.js:834`), which blocks the handler while the chain is still
37
- // synchronous, but that is a side effect, not a guarantee: `throw` is what
38
- // survives a future async builder or middleware.
39
- //
40
- // 4. NEVER RE-THROW THE INCOMING `YError`. `yargs-factory.js:1471-1475` routes
41
- // that class back into `usage.fail()`, so re-throwing it prints the help a
42
- // second time. The usage branch throws a plain `Error` with the original
43
- // attached as `cause`.
44
- //
45
- // The runtime branch deliberately prints NOTHING and exits NOTHING: the rejection
46
- // propagates out of `parseAsync()` on its own, and `index.ts` is the single
47
- // printer (it also handles `GJSIFY_DEBUG` stacks and the GJS-side `gjsExit(1)`,
48
- // which a second printer here would duplicate). Suppressing yargs'
49
- // help-and-exit IS this branch's whole job — it only looks like a no-op.
24
+ // The runtime branch prints and exits NOTHING on purpose: the rejection
25
+ // propagates out of `parseAsync()` and `index.ts` is the single printer (it owns
26
+ // `GJSIFY_DEBUG` stacks and the GJS-side `gjsExit(1)`). Suppressing yargs'
27
+ // help-and-exit is this branch's whole job — it only looks like a no-op.
50
28
  /**
51
29
  * Classify a yargs `fail(msg, err)` callback into the two cases that need
52
- * different output.
53
- *
54
- * Pure on purpose — the whole point is that the rule is testable without
55
- * driving a process to its death. `cli-fail.spec.ts` additionally pins the
56
- * yargs-side half (which call site passes `null`) against a real instance, so a
57
- * yargs upgrade that changes the routing fails a test instead of silently
58
- * turning every runtime error back into a help dump.
30
+ * different output. Kept pure so the rule is testable without driving a process
31
+ * to its death; `cli-fail.spec.ts` pins the yargs-side half against a real
32
+ * instance, so a yargs upgrade that reroutes `fail` fails a test instead of
33
+ * silently turning every runtime error back into a help dump.
59
34
  */
60
35
  export function classifyCliFailure(msg, _err) {
61
- // A non-empty string means yargs itself diagnosed the argv. An empty string
62
- // is treated as "no message" rather than as an empty usage error: it carries
63
- // nothing to print, and printing help with no reason is the worst of both.
36
+ // An empty string counts as "no message", not as an empty usage error: it has
37
+ // nothing to print, and help with no reason is the worst of both.
64
38
  if (typeof msg === 'string' && msg.length > 0)
65
39
  return { kind: 'usage', message: msg };
66
40
  return { kind: 'runtime' };