@gjsify/cli 0.36.0 → 0.38.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 (122) hide show
  1. package/dist/cli.gjs.mjs +169 -169
  2. package/lib/actions/build.d.ts +7 -0
  3. package/lib/actions/build.js +18 -3
  4. package/lib/bundler-pick.js +61 -87
  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/config.d.ts +14 -0
  11. package/lib/config.js +18 -1
  12. package/lib/templates/install.mjs.tmpl +164 -75
  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 +137 -221
  16. package/lib/utils/auth-npmrc.d.ts +5 -10
  17. package/lib/utils/auth-npmrc.js +11 -21
  18. package/lib/utils/bin-shim.d.ts +40 -25
  19. package/lib/utils/bin-shim.js +51 -26
  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 +89 -122
  27. package/lib/utils/detect-native-packages.js +119 -169
  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 +17 -20
  43. package/lib/utils/gjsify-shim.js +80 -120
  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.js +40 -53
  67. package/lib/utils/node-version.d.ts +5 -14
  68. package/lib/utils/node-version.js +15 -24
  69. package/lib/utils/normalize-bundler-options.d.ts +7 -11
  70. package/lib/utils/normalize-bundler-options.js +24 -42
  71. package/lib/utils/npm-oidc.d.ts +23 -37
  72. package/lib/utils/npm-oidc.js +28 -53
  73. package/lib/utils/npm-otp-cache.d.ts +4 -4
  74. package/lib/utils/npm-otp-cache.js +20 -25
  75. package/lib/utils/npm-otp.d.ts +20 -30
  76. package/lib/utils/npm-otp.js +32 -51
  77. package/lib/utils/onboard-probe.d.ts +7 -13
  78. package/lib/utils/onboard-probe.js +16 -27
  79. package/lib/utils/parse-spec.d.ts +3 -3
  80. package/lib/utils/parse-spec.js +4 -5
  81. package/lib/utils/pkg-json-edit.d.ts +7 -12
  82. package/lib/utils/pkg-json-edit.js +13 -26
  83. package/lib/utils/pkg-json.d.ts +11 -18
  84. package/lib/utils/pkg-json.js +19 -28
  85. package/lib/utils/prefixed-output.d.ts +3 -4
  86. package/lib/utils/prefixed-output.js +13 -19
  87. package/lib/utils/prompt.d.ts +3 -4
  88. package/lib/utils/prompt.js +14 -18
  89. package/lib/utils/publish-diagnose.d.ts +11 -16
  90. package/lib/utils/publish-diagnose.js +15 -28
  91. package/lib/utils/publish-headers.d.ts +7 -11
  92. package/lib/utils/publish-headers.js +36 -65
  93. package/lib/utils/publishable-packages.d.ts +4 -6
  94. package/lib/utils/publishable-packages.js +14 -18
  95. package/lib/utils/resolve-gjs-entry.d.ts +5 -8
  96. package/lib/utils/resolve-gjs-entry.js +10 -27
  97. package/lib/utils/resolve-npm-package.d.ts +6 -11
  98. package/lib/utils/resolve-npm-package.js +29 -63
  99. package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
  100. package/lib/utils/resolve-plugin-by-name.js +9 -14
  101. package/lib/utils/run-gjs.d.ts +16 -21
  102. package/lib/utils/run-gjs.js +31 -56
  103. package/lib/utils/run-lifecycle-script.d.ts +5 -9
  104. package/lib/utils/run-lifecycle-script.js +23 -42
  105. package/lib/utils/run-node.d.ts +21 -33
  106. package/lib/utils/run-node.js +43 -64
  107. package/lib/utils/runtimes.d.ts +25 -35
  108. package/lib/utils/runtimes.js +34 -46
  109. package/lib/utils/spawn.d.ts +32 -48
  110. package/lib/utils/spawn.js +51 -84
  111. package/lib/utils/stdin.d.ts +2 -2
  112. package/lib/utils/stdin.js +10 -25
  113. package/lib/utils/suggest.d.ts +6 -7
  114. package/lib/utils/suggest.js +8 -10
  115. package/lib/utils/system-gi.d.ts +36 -27
  116. package/lib/utils/system-gi.js +85 -114
  117. package/lib/utils/trust-registry.d.ts +4 -8
  118. package/lib/utils/trust-registry.js +7 -15
  119. package/lib/utils/win32-command.d.ts +8 -14
  120. package/lib/utils/win32-command.js +29 -43
  121. package/lib/utils/workspace-root.js +7 -12
  122. package/package.json +18 -18
@@ -3,110 +3,65 @@
3
3
  // dynamic loader can find the ones a typelib names by BARE LEAF
4
4
  // (`libgtk-4.1.dylib`, `libgdk_pixbuf-2.0.0.dylib`, …).
5
5
  //
6
- // ── A DELIBERATE MIRROR ──────────────────────────────────────────────────────
6
+ // A PINNED MIRROR of `packages/node-gi/node-gi/system-gi.js` — the two must agree
7
+ // and `system-gi.spec.ts` asserts it over a table of injected host shapes. Why a
8
+ // mirror and not an import, and what is owed to retire it:
9
+ // `status/open-todos.md` § "systemGiLibraryDirs() lives in two places".
7
10
  //
8
- // This is a PINNED PORT of `packages/node-gi/node-gi/system-gi.js`. The two must
9
- // agree, and `system-gi.spec.ts` is the check: it imports node-gi's copy by
10
- // relative path and asserts both return identical arrays for a table of injected
11
- // inputs. That is the same shape `impliedExampleNodeEntry()` in
12
- // `@gjsify/manifest-conformance` uses for `resolveNodeEntry()` in this package —
13
- // "this is the check, that is the consumer".
11
+ // WHY THIS EXISTS. A failed `dlopen('libgtk-4.1.dylib')` surfaces one layer up as
12
+ // something that reads like a type-system bug — `TypeError: Gtk.DrawingArea is not
13
+ // a subclassable GObject type` under Node, `Error: Unsupported type void, deriving
14
+ // from fundamental void` under a bare `gjs -c "imports.gi.Gtk; Gtk.init()"`:
15
+ // `gi_repository_require` resolves the typelib, `g_module_open` then fails, and no
16
+ // `gtk_*_get_type` is ever registered.
14
17
  //
15
- // Why a mirror and not an import: ADR 0005 Decision 2 forbids any Tier-1/Tier-2
16
- // package taking a `dependencies`/`optionalDependencies` edge on
17
- // `@gjsify/node-gi`, and `@gjsify/cli` is Tier 1. The alternative — lifting the
18
- // module into a new shared npm name — is the documented expensive path (manual
19
- // first publish + Trusted Publisher bootstrap; the `@gjsify/tls-native` incident
20
- // stalled 60+ packages), so it is tracked in `status/open-todos.md` instead of
21
- // taken here. A spec may reach across the repo because it is never bundled into
22
- // the published `lib/`, which is what lets the agreement be machine-checked
23
- // without creating the forbidden edge.
18
+ // The gap is the LOADER, not the installation (measured on macOS 15.7.8 / Homebrew
19
+ // /usr/local / GTK 4.22.4). `node` has no rpath; Homebrew's `gjs` has one into
20
+ // GLIB's keg alone (`…/opt/glib/lib`), so it resolves the glib-family leaves it
21
+ // links against and no other keg's. `/usr/local/lib`, where `libgtk-4.1.dylib`
22
+ // actually is, is in neither, nor in dyld's default fallback path.
24
23
  //
25
- // ── WHY THIS EXISTS ──────────────────────────────────────────────────────────
26
- //
27
- // Measured twice, on the same class of host, in two runtimes:
28
- //
29
- // 1. Node (macOS 15.7.8 / Homebrew /usr/local / Node 24.18.1 / GTK 4.22.4, the
30
- // first release that shipped a darwin-x64 addon) — this is node-gi's report:
31
- //
32
- // GLib-GIRepository-WARNING: Failed to load shared library 'libgtk-4.1.dylib'
33
- // referenced by the typelib: dlopen(libgtk-4.1.dylib, 0x0009): tried:
34
- // 'libgtk-4.1.dylib' (no such file), …, '/usr/lib/libgtk-4.1.dylib' (no such file)
35
- // TypeError: Gtk.DrawingArea is not a subclassable GObject type
36
- //
37
- // 2. GJS (same VM, gjs 1.88.1, `gjs -c "imports.gi.Gtk; Gtk.init()"`, nothing
38
- // gjsify in the process at all) — the reason this CLI needs the module too:
39
- //
40
- // dlopen(libgtk-4.1.dylib, 0x0009): tried: 'libgtk-4.1.dylib' (no such file),
41
- // '/System/Volumes/Preboot/Cryptexes/OSlibgtk-4.1.dylib' (no such file),
42
- // '/usr/local/Cellar/gjs/1.88.1/bin/../../../../opt/glib/lib/libgtk-4.1.dylib'
43
- // (no such file), '/usr/lib/libgtk-4.1.dylib' (no such file, not in dyld cache)
44
- // JS ERROR: Error: Unsupported type void, deriving from fundamental void
45
- //
46
- // Read the third path dyld tried: Homebrew's `gjs` binary has an rpath, and it
47
- // points into GLIB's keg (`…/opt/glib/lib`) — nowhere else. So Homebrew's gjs
48
- // resolves the glib-family leaves it links against and NO other keg's. The
49
- // difference between the two reports is cosmetic, not structural: `node` has no
50
- // rpath at all, `gjs` has one into the wrong keg, and `/usr/local/lib` — where
51
- // `libgtk-4.1.dylib` actually is — is in neither, nor in dyld's default fallback
52
- // path. The gap is the LOADER, not the installation: `gi_repository_require`
53
- // resolves the typelib, `g_module_open('libgtk-4.1.dylib')` then fails, no
54
- // `gtk_*_get_type` is ever registered, and the failure surfaces one layer up as
55
- // something that reads like a type-system bug.
56
- //
57
- // The value CANNOT come from `pkg-config --variable=libdir gtk4`. Measured on the
58
- // same host: that answers `/usr/local/Cellar/gtk4/4.22.4/lib` — a PER-FORMULA
59
- // directory that fixes GTK and leaves `libgdk_pixbuf-2.0.0.dylib` (a different
60
- // formula, so a different Cellar libdir) still unresolvable. What works is the
61
- // directory every keg is symlinked into, `<prefix>/lib`, because it is the UNION.
62
- // So the question this module answers is "which directories hold the GI stack",
63
- // never "where is one library".
24
+ // The value CANNOT come from `pkg-config --variable=libdir gtk4`: that answers
25
+ // `/usr/local/Cellar/gtk4/4.22.4/lib`, a PER-FORMULA directory that fixes GTK and
26
+ // leaves `libgdk_pixbuf-2.0.0.dylib` (different formula, different Cellar libdir)
27
+ // unresolvable. What works is `<prefix>/lib`, the UNION every keg is symlinked into.
28
+ // The question is "which directories hold the GI stack", never "where is one library".
64
29
  //
65
30
  // THE RULE — a typelib and the library it names are SIBLINGS by GI's own install
66
31
  // layout: `<libdir>/girepository-1.0/Gtk-4.0.typelib` beside
67
- // `<libdir>/libgtk-4.1.dylib`. A directory holding `girepository-1.0/` therefore
68
- // IS a GI library directory. That convention is what makes this
69
- // package-manager-agnostic: it lands on `/usr/local/lib` (Homebrew x64),
70
- // `/opt/homebrew/lib` (Homebrew arm64), `/opt/local/lib` (MacPorts) or a custom
71
- // prefix without needing to know that any of them exist.
32
+ // `<libdir>/libgtk-4.1.dylib`. A directory holding `girepository-1.0/` therefore IS
33
+ // a GI library directory, which is what makes this package-manager-agnostic — it
34
+ // lands on Homebrew, MacPorts or a custom prefix without knowing any of them exist.
72
35
  //
73
36
  // PURE FUNCTION of injected host facts (platform / env / fs / pkg-config), like
74
- // `resolvePrebuildDirName()` and `buildNativeEnv()` beside it — so the darwin
75
- // branch is unit-testable from a Linux host and the host values are read only at
76
- // the outermost call site, as defaults.
77
- //
78
- // ── HOW THE CONSUMER DIFFERS FROM NODE-GI'S ──────────────────────────────────
37
+ // `resolvePrebuildDirName()` and `buildNativeEnv()` beside it, so the darwin branch
38
+ // is unit-testable from Linux.
79
39
  //
80
- // node-gi RE-EXECS its own process with the result on `DYLD_FALLBACK_LIBRARY_PATH`,
81
- // because dyld captures that variable at process LAUNCH and an in-process
82
- // `process.env` mutation therefore does nothing. This CLI never needs a re-exec:
83
- // it is the one LAUNCHING the gjs child (`buildNativeEnv()` →
84
- // `spawn('gjs', …, { env })`), so it sets the variable in the child's launch env,
85
- // which is the same repair one hop earlier. A launcher could not fix this from
86
- // inside the bundle either way — GI resolves typelibs and their backers before the
87
- // bundle's first line runs.
40
+ // node-gi RE-EXECS itself with the result on `DYLD_FALLBACK_LIBRARY_PATH`, because
41
+ // dyld captures that variable at process LAUNCH and an in-process `process.env`
42
+ // mutation does nothing. This CLI needs no re-exec: it is the one LAUNCHING the gjs
43
+ // child (`buildNativeEnv()` → `spawn('gjs', …, { env })`), the same repair one hop
44
+ // earlier. A launcher inside the bundle could not fix it either way — GI resolves
45
+ // typelibs and their backers before the bundle's first line runs.
88
46
  import { spawnSync } from 'node:child_process';
89
47
  import { statSync } from 'node:fs';
90
- // POSIX path semantics, not the HOST's. This function answers a question about
91
- // a darwin filesystem — `platform` is a PARAMETER, so the host running the code
92
- // may be any of the three. Node's default `join`/`dirname`/`basename` follow the
93
- // host, so on win32 `join('/usr/local/lib', 'girepository-1.0')` becomes
94
- // `\usr\local\lib\girepository-1.0` and never matches the darwin path the
95
- // caller (or the injected `existsDir`) is holding. Measured on the Windows leg:
96
- // three `systemGiLibraryDirs` specs returned `[]` where they assert a prefix.
97
- // `PROBED_GI_LIBDIRS` is darwin-only, so POSIX is always the right dialect here.
48
+ // POSIX path semantics, not the HOST's: `platform` is a PARAMETER, so this answers
49
+ // a question about a darwin filesystem from any host. Node's host-following
50
+ // `join` turns `join('/usr/local/lib', 'girepository-1.0')` into
51
+ // `\usr\local\lib\girepository-1.0` on win32, matching nothing the caller (or the
52
+ // injected `existsDir`) holds — measured on the Windows leg, three
53
+ // `systemGiLibraryDirs` specs returned `[]` where they assert a prefix.
98
54
  import { posix } from 'node:path';
99
55
  const { basename, dirname, join } = posix;
100
56
  /**
101
57
  * Prefixes a macOS GI stack is installed under in practice — probed, never
102
58
  * assumed: each is accepted only when it actually holds a `girepository-1.0/`.
103
59
  *
104
- * This is the no-pkg-config safety net, and it is load-bearing rather than
105
- * belt-and-braces: `brew install gtk4` installs RUNTIME dependencies only, so a
106
- * user running a published showcase routinely has the entire GTK stack and no
107
- * `pkg-config` at all (it is a BUILD dependency of those formulae). The
108
- * pkg-config source below is the GENERAL mechanism — it is what finds a jhbuild
109
- * or otherwise bespoke prefix — but it cannot be the only one.
60
+ * Load-bearing rather than belt-and-braces: `brew install gtk4` installs RUNTIME
61
+ * dependencies only, so a user running a published showcase routinely has the
62
+ * whole GTK stack and no `pkg-config` at all (a BUILD dependency of those
63
+ * formulae). The pkg-config source below is the general mechanism — it finds a
64
+ * jhbuild or otherwise bespoke prefix — but it cannot be the only one.
110
65
  */
111
66
  const PROBED_GI_LIBDIRS = {
112
67
  darwin: ['/opt/homebrew/lib', '/usr/local/lib', '/opt/local/lib'],
@@ -115,9 +70,7 @@ const PROBED_GI_LIBDIRS = {
115
70
  const TYPELIB_SUBDIR = 'girepository-1.0';
116
71
  /** Memoized `pkg-config` answer: the spawn happens at most once per process. */
117
72
  let pkgConfigDirsCache = null;
118
- /**
119
- * Split an OS search-path variable, dropping empty segments.
120
- */
73
+ /** Split an OS search-path variable, dropping empty segments. */
121
74
  export function splitSearchPath(value, separator = ':') {
122
75
  return (value ?? '').split(separator).filter(Boolean);
123
76
  }
@@ -134,15 +87,12 @@ function isDirectory(dir) {
134
87
  /**
135
88
  * `pkg-config`'s OWN configured `.pc` search path plus `$PKG_CONFIG_PATH`. Every
136
89
  * entry is a `<libdir>/pkgconfig` (or `<datadir>/pkgconfig`) directory, so its
137
- * parent is a candidate libdir — which is how a prefix nobody hardcoded is
138
- * found. Measured on the reported host: `pc_path` =
139
- * `/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/usr/lib/pkgconfig:…`,
140
- * whose first parent is exactly the `/usr/local/lib` that resolves every leaf.
90
+ * parent is a candidate libdir — which is how a prefix nobody hardcoded is found.
141
91
  *
142
- * `spawnSync` reports a missing executable as `result.error`, not by throwing —
143
- * an absent `pkg-config` is expected on a runtime-only GTK install, so it is a
144
- * checked return value here and not an exception path. (`@gjsify/child_process`
145
- * matches Node here deliberately; see its `spawnSync` note.)
92
+ * `spawnSync` reports a missing executable as `result.error`, not by throwing, so
93
+ * an absent `pkg-config` (expected on a runtime-only GTK install) is a checked
94
+ * return value and not an exception path. (`@gjsify/child_process` matches Node
95
+ * here deliberately; see its `spawnSync` note.)
146
96
  */
147
97
  export function pkgConfigSearchDirs(env) {
148
98
  if (pkgConfigDirsCache)
@@ -172,19 +122,16 @@ export function pkgConfigSearchDirs(env) {
172
122
  * Sources 2 and 3 are GUESSES at a prefix, so each must show the
173
123
  * `girepository-1.0/` marker before it is believed.
174
124
  *
175
- * Empty off darwin, and that is a statement about LOADERS, not about GI. Linux
176
- * resolves these leaves through `ld.so`'s system-wide configured cache
177
- * (`/etc/ld.so.conf.d`), which a package install populates — nothing per-process
178
- * is needed. dyld is the one loader that neither consults a system-wide config
179
- * NOR re-reads its search path after launch, which is why darwin needs the
180
- * variable set at launch and nobody else does.
125
+ * Empty off darwin, and that is a statement about LOADERS, not about GI: Linux
126
+ * resolves these leaves through `ld.so`'s system-wide cache (`/etc/ld.so.conf.d`),
127
+ * which a package install populates. dyld is the one loader that neither consults
128
+ * a system-wide config NOR re-reads its search path after launch.
181
129
  *
182
- * win32 is deliberately absent rather than proven unnecessary: `PATH` is the DLL
183
- * search path, a Windows GTK distribution puts its `bin` there itself, and
184
- * Windows re-reads that path at every `LoadLibrary` — so if a system GTK ever
185
- * turns out not to be on it, the repair is a `PATH` prepend (which
186
- * {@link buildNativeEnv} already does for the prebuilds), never a second
187
- * variable. Untested there.
130
+ * win32 is deliberately absent rather than proven unnecessary, and untested:
131
+ * `PATH` is the DLL search path, a Windows GTK distribution puts its `bin` there
132
+ * itself, and Windows re-reads that path at every `LoadLibrary` — so if a system
133
+ * GTK ever turns out not to be on it, the repair is a `PATH` prepend (which
134
+ * {@link buildNativeEnv} already does for the prebuilds), never a second variable.
188
135
  *
189
136
  * @returns absolute directories, or `[]` when this platform's loader needs none
190
137
  */
@@ -220,11 +167,35 @@ export function systemGiLibraryDirs({ platform = process.platform, env = process
220
167
  * Whether `current` (a split `DYLD_FALLBACK_LIBRARY_PATH`) already covers every
221
168
  * directory in `wanted` — the "nothing to do" test.
222
169
  *
223
- * Kept in the port even though this CLI's consumer does not branch on it: the
224
- * mirror's value is that the two files are the SAME file, and a surface that
225
- * drifts by one export is a mirror nobody can check. `system-gi.spec.ts` pins
226
- * both.
170
+ * Kept in the port even though this CLI's consumer does not branch on it: a mirror
171
+ * that drifts by one export is a mirror nobody can check. `system-gi.spec.ts`
172
+ * pins both.
227
173
  */
228
174
  export function pathCovers(wanted, current) {
229
175
  return wanted.every((dir) => current.includes(dir));
230
176
  }
177
+ /**
178
+ * dyld's OWN default fallback list, verbatim from `dyld(1)`. Setting
179
+ * `DYLD_FALLBACK_LIBRARY_PATH` REPLACES it, so anything composed for that
180
+ * variable must carry it as a tail. The incident that bought this rule is in the
181
+ * ORIGINAL (`packages/node-gi/node-gi/system-gi.js`); the mirror does not
182
+ * restate it.
183
+ */
184
+ export function dyldDefaultFallbackDirs(env = process.env) {
185
+ const home = env['HOME'];
186
+ return [...(home ? [`${home}/lib`] : []), '/usr/local/lib', '/lib', '/usr/lib'];
187
+ }
188
+ /**
189
+ * Compose a `DYLD_FALLBACK_LIBRARY_PATH` value: `wanted`, then the launching
190
+ * environment's own value, then {@link dyldDefaultFallbackDirs} —
191
+ * unconditionally, so a child launched through gjsify never searches LESS than
192
+ * one launched without it. Deduplicated: `systemGiLibraryDirs()` can legitimately
193
+ * yield a directory the tail also names, and a repeated entry costs the loader a
194
+ * second stat on every miss. First occurrence wins everywhere, so dropping later
195
+ * repeats is behaviour-preserving.
196
+ */
197
+ export function composeDyldFallback(wanted, env = process.env) {
198
+ return [
199
+ ...new Set([...wanted, ...splitSearchPath(env['DYLD_FALLBACK_LIBRARY_PATH']), ...dyldDefaultFallbackDirs(env)]),
200
+ ].join(':');
201
+ }
@@ -29,9 +29,8 @@ export interface TrustEntry {
29
29
  };
30
30
  }
31
31
  /**
32
- * The POST body for `trust github`. npm always sends an ARRAY with a single
33
- * config object carrying `type`, `claims` AND `permissions` (the last is
34
- * required — `--allow-publish` → `['createPackage']`).
32
+ * The POST body for `trust github`: npm always sends an ARRAY with a single config object, and
33
+ * `permissions` is required alongside `type`/`claims`.
35
34
  */
36
35
  export declare function githubTrustBody(opts: {
37
36
  repository: string;
@@ -62,10 +61,7 @@ export declare function classifyTrustList(status: number, body: unknown, opts: {
62
61
  workflow: string;
63
62
  }): TrustState;
64
63
  /**
65
- * Derive `owner/repo` from a git remote URL. Handles the common GitHub forms:
66
- * git@github.com:owner/repo.git
67
- * ssh://git@github.com/owner/repo.git
68
- * https://github.com/owner/repo(.git)
69
- * Returns null when it can't confidently extract two segments.
64
+ * Derive `owner/repo` from a git remote URL (scp-like `git@host:owner/repo.git` and the
65
+ * `ssh://`/`https://` forms). Null when two segments cannot be confidently extracted.
70
66
  */
71
67
  export declare function parseRepoFromGitRemote(url: string): string | null;
@@ -5,9 +5,8 @@
5
5
  // github: POST <registry>/-/package/<escaped>/trust body: [{type,claims}]
6
6
  // revoke: DELETE <registry>/-/package/<escaped>/trust/<id>
7
7
  //
8
- // Everything here is side-effect-free so it can be unit-tested without a
9
- // network; the command (commands/trust.ts) does the fetch + OTP orchestration.
10
- // Reference: refs/npm-cli/lib/trust-cmd.js + lib/commands/trust/*.js.
8
+ // Side-effect-free so it unit-tests without a network; `commands/trust.ts` does the fetch + OTP
9
+ // orchestration. Reference: refs/npm-cli/lib/trust-cmd.js + lib/commands/trust/*.js.
11
10
  import { escapePackageName } from './publish-headers.js';
12
11
  /** `<registry>/-/package/<@scope%2fname>/trust`. */
13
12
  export function trustUrl(registry, pkgName) {
@@ -25,9 +24,8 @@ export function trustRevokeUrl(registry, pkgName, id) {
25
24
  export const PERMISSION_PUBLISH = 'createPackage';
26
25
  export const PERMISSION_STAGE_PUBLISH = 'createStagedPackage';
27
26
  /**
28
- * The POST body for `trust github`. npm always sends an ARRAY with a single
29
- * config object carrying `type`, `claims` AND `permissions` (the last is
30
- * required — `--allow-publish` → `['createPackage']`).
27
+ * The POST body for `trust github`: npm always sends an ARRAY with a single config object, and
28
+ * `permissions` is required alongside `type`/`claims`.
31
29
  */
32
30
  export function githubTrustBody(opts) {
33
31
  const claims = {
@@ -77,24 +75,18 @@ export function classifyTrustList(status, body, opts) {
77
75
  return 'error';
78
76
  }
79
77
  /**
80
- * Derive `owner/repo` from a git remote URL. Handles the common GitHub forms:
81
- * git@github.com:owner/repo.git
82
- * ssh://git@github.com/owner/repo.git
83
- * https://github.com/owner/repo(.git)
84
- * Returns null when it can't confidently extract two segments.
78
+ * Derive `owner/repo` from a git remote URL (scp-like `git@host:owner/repo.git` and the
79
+ * `ssh://`/`https://` forms). Null when two segments cannot be confidently extracted.
85
80
  */
86
81
  export function parseRepoFromGitRemote(url) {
87
82
  let s = url.trim();
88
83
  if (!s)
89
84
  return null;
90
- // Strip a trailing .git
91
85
  s = s.replace(/\.git$/, '');
92
- // scp-like: git@host:owner/repo
93
86
  const scp = s.match(/^[^/@]+@[^:]+:(.+)$/);
94
87
  if (scp) {
95
88
  return twoSegments(scp[1]);
96
89
  }
97
- // URL forms: ssh://, https://, http://, git://
98
90
  const m = s.match(/^[a-z]+:\/\/[^/]+\/(.+)$/i);
99
91
  if (m) {
100
92
  return twoSegments(m[1]);
@@ -105,6 +97,6 @@ function twoSegments(path) {
105
97
  const parts = path.split('/').filter((p) => p.length > 0);
106
98
  if (parts.length < 2)
107
99
  return null;
108
- // owner/repo are the LAST two segments (handles nested groups → take repo + its owner)
100
+ // The LAST two segments, so a nested group path still yields repo + its owner.
109
101
  return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
110
102
  }
@@ -21,10 +21,9 @@ export interface Win32ResolveContext {
21
21
  /**
22
22
  * Read `name` from `env` case-insensitively.
23
23
  *
24
- * Windows environment variables are case-insensitive and `process.env` mirrors
25
- * that, but a plain object handed in as `env` (which is what every spawn call
26
- * site builds) does not — a child env carrying `Path` rather than `PATH` would
27
- * otherwise silently resolve nothing.
24
+ * Windows env vars are case-insensitive and `process.env` mirrors that, but a plain object —
25
+ * what every spawn call site builds — does not, so a child env carrying `Path` rather than
26
+ * `PATH` would silently resolve nothing.
28
27
  */
29
28
  export declare function lookupEnv(env: NodeJS.ProcessEnv, name: string): string | undefined;
30
29
  /** Escape a command path for cmd.exe. Port of cross-spawn's `escape.command`. */
@@ -35,20 +34,15 @@ export declare function escapeCmdCommand(arg: string): string;
35
34
  */
36
35
  export declare function escapeCmdArgument(arg: string, doubleEscapeMetaChars?: boolean): string;
37
36
  /**
38
- * Build the `cmd.exe /d /s /c "…"` invocation for a resolved, non-directly-
39
- * executable target. Pure — the unit tests drive this from any host.
40
- *
41
- * @param commandFile Resolved absolute path of the target (e.g. the `.cmd` shim).
42
- * @param args The caller's arguments, unescaped.
43
- * @param comspec The interpreter, normally `%COMSPEC%`.
37
+ * Build the `cmd.exe /d /s /c "…"` invocation for a resolved, non-directly-executable target.
38
+ * `args` are the caller's UNESCAPED arguments; `comspec` is normally `%COMSPEC%`.
44
39
  */
45
40
  export declare function buildCmdExeInvocation(commandFile: string, args: readonly string[], comspec?: string): Win32Invocation;
46
41
  /**
47
42
  * Rewrite a bare command name into something Windows can actually spawn.
48
43
  *
49
- * Returns `undefined` when nothing needs rewriting — not win32, not a bare
50
- * name, or not found on `PATH`. The last case is deliberate: letting `spawn`
51
- * fail with `ENOENT` preserves the contract callers key their install hints on
52
- * (see the module header).
44
+ * `undefined` when nothing needs rewriting — not win32, not a bare name, or not found on
45
+ * `PATH`. The last case is deliberate: letting `spawn` fail with `ENOENT` preserves the
46
+ * contract callers key their install hints on (see the module header).
53
47
  */
54
48
  export declare function resolveWin32Command(cmd: string, args: readonly string[], ctx: Win32ResolveContext): Win32Invocation | undefined;
@@ -6,36 +6,28 @@
6
6
  // Linux host (the same shape `utils/bin-shim.ts` uses), and narrowed to the one
7
7
  // case the CLI needs — resolving a bare command name before `spawn`.
8
8
  //
9
- // WHY THIS EXISTS
10
- //
11
- // `spawn('npm', …)` cannot work on Windows, and the failure is not a PATH
12
- // problem. `CreateProcess` appends only `.exe` when it searches PATH for a bare
13
- // name, and every npm-installed bin on Windows is a `.cmd` shim. Measured on
14
- // win32 x64 / Node 24.18.1:
9
+ // `spawn('npm', …)` cannot work on Windows, and the failure is not a PATH problem:
10
+ // `CreateProcess` appends only `.exe` when it searches PATH for a bare name, and every
11
+ // npm-installed bin on Windows is a `.cmd` shim. Measured on win32 x64 / Node 24.18.1:
15
12
  //
16
13
  // spawn('npm', ['--version']) → ENOENT (.cmd is never tried)
17
14
  // spawn('node', ['--version']) → ok (.exe is appended)
18
15
  // spawn('npm.cmd', ['--version']) → EINVAL (Node refuses a batch file)
19
16
  //
20
- // The EINVAL is Node's CVE-2024-27980 mitigation: a `.cmd`/`.bat` target has to
21
- // go through a command interpreter. So resolving the shim path is NOT enough —
22
- // it must be invoked as `cmd.exe /d /s /c "…"` with every argument escaped for
23
- // cmd's parser, which is what {@link buildCmdExeInvocation} does.
24
- //
25
- // That gap took out the whole orchestration layer, not one command: `gjsify
26
- // workspace`/`foreach`/`check` spawn `<pm> run <script>` per package and the
27
- // package manager is `npm` on every non-GJS host, so on Windows each one died
28
- // with `spawn npm ENOENT` before running a single script.
17
+ // The EINVAL is Node's CVE-2024-27980 mitigation: a `.cmd`/`.bat` target must go through a
18
+ // command interpreter, so resolving the shim path is NOT enough — it has to be invoked as
19
+ // `cmd.exe /d /s /c "…"` with every argument escaped for cmd's parser ({@link
20
+ // buildCmdExeInvocation}). The gap took out the whole orchestration layer, not one command:
21
+ // `gjsify workspace`/`foreach`/`check` spawn `<pm> run <script>` per package and the package
22
+ // manager is `npm` on every non-GJS host, so on Windows each died with `spawn npm ENOENT`
23
+ // before running a single script.
29
24
  //
30
- // WHY RESOLUTION HAPPENS HERE AND NOT IN cmd.exe
31
- //
32
- // Handing the bare name to `cmd.exe /c` would also find the `.cmd` — and would
33
- // destroy the ENOENT contract every caller depends on. A missing command would
34
- // come back as a generic exit 1 with an unparsable message instead of an
35
- // `ENOENT` error, and `SpawnToCompletionOptions.notFound` (the install hints)
36
- // plus `commands/tsc.ts`'s transparent fall back to upstream tsc when `gjs` is
37
- // absent both key on exactly that code. Resolving first keeps "not found" a
38
- // spawn-time error and routes only what really is a batch file through cmd.exe.
25
+ // Resolution happens HERE and not in cmd.exe because handing the bare name to `cmd.exe /c`
26
+ // would also find the `.cmd` but destroy the ENOENT contract: a missing command would return a
27
+ // generic exit 1 with an unparsable message, and both `SpawnToCompletionOptions.notFound` (the
28
+ // install hints) and `commands/tsc.ts`'s fall back to upstream tsc when `gjs` is absent key on
29
+ // that code. Resolving first keeps "not found" a spawn-time error and routes only a real batch
30
+ // file through cmd.exe.
39
31
  /** Default when `PATHEXT` is unset — the extensions `CreateProcess` users expect. */
40
32
  const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD';
41
33
  /** Only `.com`/`.exe` can be executed without an interpreter. Verbatim from cross-spawn. */
@@ -47,17 +39,16 @@ const META_CHARS_RE = /([()\][%!^"`<>&|;, *?])/g;
47
39
  /**
48
40
  * Read `name` from `env` case-insensitively.
49
41
  *
50
- * Windows environment variables are case-insensitive and `process.env` mirrors
51
- * that, but a plain object handed in as `env` (which is what every spawn call
52
- * site builds) does not — a child env carrying `Path` rather than `PATH` would
53
- * otherwise silently resolve nothing.
42
+ * Windows env vars are case-insensitive and `process.env` mirrors that, but a plain object —
43
+ * what every spawn call site builds — does not, so a child env carrying `Path` rather than
44
+ * `PATH` would silently resolve nothing.
54
45
  */
55
46
  export function lookupEnv(env, name) {
56
47
  const wanted = name.toLowerCase();
57
48
  for (const key of Object.keys(env)) {
58
- // A key present but explicitly `undefined` means NOT SET — `spawn`
59
- // drops it from the child env. Returning it here would shadow a later
60
- // spelling (`{ PATH: undefined, Path: 'C:\\bin' }`) and resolve nothing.
49
+ // A key present but explicitly `undefined` means NOT SET — `spawn` drops it from the
50
+ // child env. Returning it would shadow a later spelling (`{ PATH: undefined, Path:
51
+ // 'C:\\bin' }`) and resolve nothing.
61
52
  if (key.toLowerCase() === wanted && env[key] !== undefined)
62
53
  return env[key];
63
54
  }
@@ -84,12 +75,8 @@ export function escapeCmdArgument(arg, doubleEscapeMetaChars = false) {
84
75
  return out;
85
76
  }
86
77
  /**
87
- * Build the `cmd.exe /d /s /c "…"` invocation for a resolved, non-directly-
88
- * executable target. Pure — the unit tests drive this from any host.
89
- *
90
- * @param commandFile Resolved absolute path of the target (e.g. the `.cmd` shim).
91
- * @param args The caller's arguments, unescaped.
92
- * @param comspec The interpreter, normally `%COMSPEC%`.
78
+ * Build the `cmd.exe /d /s /c "…"` invocation for a resolved, non-directly-executable target.
79
+ * `args` are the caller's UNESCAPED arguments; `comspec` is normally `%COMSPEC%`.
93
80
  */
94
81
  export function buildCmdExeInvocation(commandFile, args, comspec = 'cmd.exe') {
95
82
  // A cmd-shim under `node_modules/.bin/` is itself a batch file that re-invokes
@@ -121,10 +108,9 @@ function lookupOnPath(cmd, ctx) {
121
108
  /**
122
109
  * Rewrite a bare command name into something Windows can actually spawn.
123
110
  *
124
- * Returns `undefined` when nothing needs rewriting — not win32, not a bare
125
- * name, or not found on `PATH`. The last case is deliberate: letting `spawn`
126
- * fail with `ENOENT` preserves the contract callers key their install hints on
127
- * (see the module header).
111
+ * `undefined` when nothing needs rewriting — not win32, not a bare name, or not found on
112
+ * `PATH`. The last case is deliberate: letting `spawn` fail with `ENOENT` preserves the
113
+ * contract callers key their install hints on (see the module header).
128
114
  */
129
115
  export function resolveWin32Command(cmd, args, ctx) {
130
116
  if (ctx.platform !== 'win32' || !isBareName(cmd))
@@ -132,8 +118,8 @@ export function resolveWin32Command(cmd, args, ctx) {
132
118
  const resolved = lookupOnPath(cmd, ctx);
133
119
  if (!resolved)
134
120
  return undefined;
135
- // `.exe`/`.com` run without an interpreter; spawning the resolved absolute
136
- // path skips a second PATH search and cannot be shadowed in between.
121
+ // `.exe`/`.com` run without an interpreter; spawning the resolved absolute path skips a
122
+ // second PATH search and cannot be shadowed in between.
137
123
  if (DIRECTLY_EXECUTABLE_RE.test(resolved))
138
124
  return { cmd: resolved, args: [...args] };
139
125
  return buildCmdExeInvocation(resolved, args, lookupEnv(ctx.env, 'COMSPEC') ?? 'cmd.exe');
@@ -1,16 +1,11 @@
1
- // Walk up from a starting directory to the first ancestor whose
2
- // `package.json` declares a `workspaces` field — the monorepo root.
1
+ // Walk up from a starting directory to the first ancestor whose `package.json`
2
+ // declares a `workspaces` field — the monorepo root that `gjsify
3
+ // run`/`workspace`/`foreach` need to discover siblings, resolve `workspace:^`
4
+ // deps and walk the dep graph. They can be invoked from anywhere, because a
5
+ // chained script call puts the child CLI's cwd at the inner workspace.
3
6
  //
4
- // Used by `gjsify run`, `gjsify workspace`, and `gjsify foreach`: each
5
- // can be invoked from inside any workspace (chained script calls put the
6
- // child CLI invocation's cwd at the inner workspace's location), and
7
- // every one of them needs the monorepo root to discover sibling
8
- // workspaces, resolve `workspace:^` deps, and walk the dep graph.
9
- //
10
- // Sanity-checked via `discoverWorkspaces(candidate)`: the candidate
11
- // monorepo must actually contain `start` as one of its workspaces.
12
- // Without this guard, a grand-parent monorepo unrelated to `start`
13
- // could be picked up.
7
+ // The candidate must also CONTAIN `start` (`discoverWorkspaces`), or an unrelated
8
+ // grand-parent monorepo gets picked up.
14
9
  import { existsSync } from 'node:fs';
15
10
  import { join, resolve } from 'node:path';
16
11
  import { discoverWorkspaces } from '@gjsify/workspace';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -134,20 +134,20 @@
134
134
  "cli"
135
135
  ],
136
136
  "dependencies": {
137
- "@gjsify/buffer": "^0.36.0",
138
- "@gjsify/create-app": "^0.36.0",
139
- "@gjsify/node-globals": "^0.36.0",
140
- "@gjsify/node-polyfills": "^0.36.0",
141
- "@gjsify/npm-registry": "^0.36.0",
142
- "@gjsify/resolve-npm": "^0.36.0",
143
- "@gjsify/rolldown-plugin-gjsify": "^0.36.0",
144
- "@gjsify/vite-plugin-blueprint": "^0.36.0",
145
- "@gjsify/rolldown-plugin-pnp": "^0.36.0",
146
- "@gjsify/semver": "^0.36.0",
147
- "@gjsify/tar": "^0.36.0",
148
- "@gjsify/tsc": "^0.36.0",
149
- "@gjsify/web-polyfills": "^0.36.0",
150
- "@gjsify/workspace": "^0.36.0",
137
+ "@gjsify/buffer": "^0.38.0",
138
+ "@gjsify/create-app": "^0.38.0",
139
+ "@gjsify/node-globals": "^0.38.0",
140
+ "@gjsify/node-polyfills": "^0.38.0",
141
+ "@gjsify/npm-registry": "^0.38.0",
142
+ "@gjsify/resolve-npm": "^0.38.0",
143
+ "@gjsify/rolldown-plugin-gjsify": "^0.38.0",
144
+ "@gjsify/vite-plugin-blueprint": "^0.38.0",
145
+ "@gjsify/rolldown-plugin-pnp": "^0.38.0",
146
+ "@gjsify/semver": "^0.38.0",
147
+ "@gjsify/tar": "^0.38.0",
148
+ "@gjsify/tsc": "^0.38.0",
149
+ "@gjsify/web-polyfills": "^0.38.0",
150
+ "@gjsify/workspace": "^0.38.0",
151
151
  "cosmiconfig": "^9.0.2",
152
152
  "get-tsconfig": "^4.14.0",
153
153
  "pkg-types": "^2.3.1",
@@ -155,13 +155,13 @@
155
155
  "yargs": "^18.0.0"
156
156
  },
157
157
  "devDependencies": {
158
- "@gjsify/unit": "^0.36.0",
158
+ "@gjsify/unit": "^0.38.0",
159
159
  "@types/yargs": "^17.0.35",
160
160
  "typescript": "^6.0.3"
161
161
  },
162
162
  "peerDependencies": {
163
- "@gjsify/oxfmt-native": "^0.36.0",
164
- "@gjsify/rolldown-native": "^0.36.0"
163
+ "@gjsify/oxfmt-native": "^0.38.0",
164
+ "@gjsify/rolldown-native": "^0.38.0"
165
165
  },
166
166
  "peerDependenciesMeta": {
167
167
  "@gjsify/oxfmt-native": {