@gjsify/cli 0.30.0 → 0.32.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 (51) hide show
  1. package/dist/cli.gjs.mjs +194 -193
  2. package/lib/actions/build.d.ts +14 -0
  3. package/lib/actions/build.js +78 -3
  4. package/lib/actions/index.d.ts +2 -0
  5. package/lib/actions/index.js +2 -0
  6. package/lib/affected-entry.d.ts +1 -0
  7. package/lib/affected-entry.js +49 -0
  8. package/lib/bundler-pick.js +84 -19
  9. package/lib/cli-app.js +20 -0
  10. package/lib/cli-fail.d.ts +22 -0
  11. package/lib/cli-fail.js +67 -0
  12. package/lib/commands/affected.d.ts +2 -2
  13. package/lib/commands/affected.js +16 -0
  14. package/lib/commands/install.d.ts +55 -0
  15. package/lib/commands/install.js +38 -21
  16. package/lib/commands/run.js +25 -6
  17. package/lib/commands/self-update.d.ts +27 -0
  18. package/lib/commands/self-update.js +77 -2
  19. package/lib/commands/showcase.d.ts +14 -0
  20. package/lib/commands/showcase.js +130 -10
  21. package/lib/commands/test.js +4 -4
  22. package/lib/commands/tsc.js +59 -11
  23. package/lib/commands/workspace.js +13 -0
  24. package/lib/config.js +6 -2
  25. package/lib/index.js +18 -1
  26. package/lib/templates/install.mjs.tmpl +27 -2
  27. package/lib/types/command.d.ts +18 -0
  28. package/lib/utils/check-system-deps.d.ts +66 -1
  29. package/lib/utils/check-system-deps.js +139 -2
  30. package/lib/utils/detect-native-packages.js +12 -1
  31. package/lib/utils/discover-showcases.d.ts +0 -2
  32. package/lib/utils/discover-showcases.js +0 -1
  33. package/lib/utils/dlx-cache.d.ts +22 -2
  34. package/lib/utils/dlx-cache.js +62 -6
  35. package/lib/utils/force-exit.d.ts +10 -0
  36. package/lib/utils/force-exit.js +43 -0
  37. package/lib/utils/gi-search-path.d.ts +36 -0
  38. package/lib/utils/gi-search-path.js +146 -0
  39. package/lib/utils/gjs-source-escape.d.ts +10 -0
  40. package/lib/utils/gjs-source-escape.js +49 -0
  41. package/lib/utils/gjsify-shim.d.ts +29 -2
  42. package/lib/utils/gjsify-shim.js +131 -22
  43. package/lib/utils/install-global.d.ts +79 -2
  44. package/lib/utils/install-global.js +130 -8
  45. package/lib/utils/oxc-resolve.d.ts +1 -1
  46. package/lib/utils/oxc-resolve.js +10 -6
  47. package/lib/utils/stdin.d.ts +3 -13
  48. package/lib/utils/stdin.js +21 -100
  49. package/lib/utils/system-gi.js +10 -1
  50. package/package.json +22 -17
  51. package/showcases.json +8 -16
@@ -23,7 +23,7 @@
23
23
  import * as fs from 'node:fs';
24
24
  import * as os from 'node:os';
25
25
  import * as path from 'node:path';
26
- import { detectNativePackages } from './detect-native-packages.js';
26
+ import { detectHostLibc, detectNativePackages, hostPlatformTokens, platformPackageName, } from './detect-native-packages.js';
27
27
  import { buildLauncherShims, buildNativeEnvPreamble, buildShLauncher, isGjsBundlePath, parseShebang, pickBinMap, } from './bin-shim.js';
28
28
  /**
29
29
  * Compute the canonical global install layout for the current user. Honours
@@ -63,9 +63,43 @@ export const GJS_ENGINE_PACKAGES = [
63
63
  '@gjsify/lightningcss-native',
64
64
  '@gjsify/oxfmt-native',
65
65
  ];
66
- /** True when `<prefix>/node_modules/@gjsify/rolldown-native` is laid down. */
67
- export function hasBundlerEngineInstalled(prefix) {
68
- return fs.existsSync(path.join(prefix, 'node_modules', '@gjsify/rolldown-native', 'package.json'));
66
+ /**
67
+ * The one engine whose absence is fatal rather than degrading: `gjsify build`
68
+ * has no fallback under GJS. Named separately because two different questions
69
+ * ("what do we install") and ("is a build possible") want different sets.
70
+ */
71
+ export const BUNDLER_ENGINE_PACKAGE = '@gjsify/rolldown-native';
72
+ /**
73
+ * True when a bundler engine `gjsify build` can actually LOAD is reachable from
74
+ * `dir`.
75
+ *
76
+ * This used to `existsSync` the bridge's own `package.json` and stop there. That
77
+ * answer was correct before ADR 0017 and is wrong after it: the bridge is now
78
+ * pure TypeScript (`files: ["lib", "meson.build", "src/rust", "src/vala"]`) and
79
+ * the binary lives in a per-target child (`@gjsify/rolldown-native-<os>-<arch>`).
80
+ * So "bridge present, child missing" reported `true` — which is why
81
+ * `self-update`'s repair gate could say "Already up to date" about a prefix whose
82
+ * engine cannot load. A live defect, not a hypothetical one.
83
+ *
84
+ * Asks `detectNativePackages` instead, which is the same walk-up
85
+ * `findRolldownNativeDir` performs at BUILD time. That matters more than the
86
+ * shortcut it replaces: an installer that disagrees with the builder about
87
+ * whether the engine is present is worse than one that is merely slow. It also
88
+ * makes the gjsify monorepo a no-op — the workspace symlink resolves — which is
89
+ * what keeps ADR 0001's non-destructive invariant safe from a registry copy
90
+ * landing on top of `packages/infra/rolldown-native`.
91
+ *
92
+ * `dir` is a prefix for a global install and a project root for a project one;
93
+ * the walk handles both.
94
+ */
95
+ export function hasBundlerEngineInstalled(dir) {
96
+ const names = new Set(detectNativePackages(dir).map((p) => p.name));
97
+ if (names.has(BUNDLER_ENGINE_PACKAGE))
98
+ return true;
99
+ // The per-target child is the thing that actually carries the binary, so its
100
+ // presence is the honest positive answer even when the umbrella is absent.
101
+ const libc = detectHostLibc(process.platform);
102
+ return hostPlatformTokens(process.platform, process.arch, libc).some((token) => names.has(platformPackageName(BUNDLER_ENGINE_PACKAGE, token)));
69
103
  }
70
104
  /**
71
105
  * Best-effort install of the GJS-native engine packages into a global prefix.
@@ -263,12 +297,100 @@ function readShebangOf(file) {
263
297
  function readJson(file) {
264
298
  return JSON.parse(fs.readFileSync(file, 'utf-8'));
265
299
  }
300
+ /**
301
+ * Is the global layout the user's own, or has a caller redirected it?
302
+ *
303
+ * `GJSIFY_GLOBAL_PREFIX` / `GJSIFY_GLOBAL_BIN_DIR` are the escape hatches
304
+ * {@link defaultGlobalLayout} documents for tests, and a caller that sets them
305
+ * has taken over placement — so "which `gjsify` will PATH resolve next" stops
306
+ * being a question about THEIR intent. `self-update` gates its fatal PATH check
307
+ * on this: without it, every harness installing into a temp prefix it never put
308
+ * on PATH is told its update did not take effect, which is true and useless.
309
+ * Caught by `tests/e2e/global-install-engine`.
310
+ *
311
+ * @param env injectable so both answers are testable without mutating the real
312
+ * environment mid-suite.
313
+ */
314
+ export function globalLayoutIsDefault(env = process.env) {
315
+ return !env.GJSIFY_GLOBAL_PREFIX && !env.GJSIFY_GLOBAL_BIN_DIR;
316
+ }
317
+ /**
318
+ * The path module for a TARGET platform, not for the host.
319
+ *
320
+ * Load-bearing for every function below that takes a `platform`. `path.resolve`
321
+ * is host-bound: on Linux `path.resolve('C:\\tools')` returns
322
+ * `<cwd>/C:\tools`, because posix has no notion of a drive letter. So a function
323
+ * that accepts `platform` and then calls the host's `path` is only pretending to
324
+ * be portable — it silently mangles Windows input everywhere except Windows,
325
+ * which is exactly the class of bug an injected platform is supposed to expose.
326
+ * Caught by `resolve-bin-on-path.spec.ts`, which asserts real `C:\…` semantics
327
+ * from Linux.
328
+ */
329
+ function pathFor(platform) {
330
+ return platform === 'win32' ? path.win32 : path.posix;
331
+ }
332
+ /** PATH split into resolved directory entries, in search order. */
333
+ function searchPathEntries(pathValue, platform) {
334
+ const p = pathFor(platform);
335
+ const sep = platform === 'win32' ? ';' : ':';
336
+ return pathValue
337
+ .split(sep)
338
+ .filter(Boolean)
339
+ .map((entry) => p.resolve(entry));
340
+ }
266
341
  /** Returns `true` if `binDir` is on the user's PATH. */
267
342
  export function binDirOnPath(binDir) {
268
- const PATH = process.env.PATH ?? '';
269
- const sep = process.platform === 'win32' ? ';' : ':';
270
- const want = path.resolve(binDir);
271
- return PATH.split(sep).some((entry) => entry && path.resolve(entry) === want);
343
+ const want = pathFor(process.platform).resolve(binDir);
344
+ return searchPathEntries(process.env.PATH ?? '', process.platform).includes(want);
345
+ }
346
+ /** cmd.exe's default when PATHEXT is unset. `.CMD` is the one npm-style shims need. */
347
+ const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC';
348
+ /**
349
+ * Resolve `binName` the way the user's shell will: the FIRST hit in PATH order.
350
+ *
351
+ * WHY THIS IS NOT `binDirOnPath`. That answers "is our dir on PATH", which is
352
+ * necessary but not sufficient for anything that CHANGES which binary runs:
353
+ * `~/.local/bin` can be on PATH and still lose to an npm-global `gjsify` earlier
354
+ * in it. `self-update` reported "Updated to v0.31.0" on exactly that host while
355
+ * the shell kept resolving a v0.26.0 install five releases behind — and the user
356
+ * then re-hit a bug fixed three releases earlier and reported it as new (#1064).
357
+ * "Which one wins" is the question, so this returns the winning PATH.
358
+ *
359
+ * WINDOWS. An extension-less name is not executable there; cmd.exe and pwsh
360
+ * append each `PATHEXT` entry, which is how the `.cmd` shim `bin-shim.ts` writes
361
+ * gets found. The bare name is probed LAST so a git-bash/MSYS `sh` shim is still
362
+ * reported rather than missed. `platform`/`pathExt`/`exists` are injectable for
363
+ * the same reason `dirLinkTarget` takes `linkType`: nothing in CI resolves a bin
364
+ * on a Windows host, so an injected `'win32'` is the ONLY way that branch is ever
365
+ * EXECUTED — and an unexecutable Windows branch is exactly how the `'dir'` bug in
366
+ * `dir-link.ts` shipped.
367
+ *
368
+ * @returns the absolute path of the winning candidate, or `null` when PATH holds none.
369
+ */
370
+ export function resolveBinOnPath(binName, opts = {}) {
371
+ const platform = opts.platform ?? process.platform;
372
+ const pathValue = opts.pathValue ?? process.env.PATH ?? '';
373
+ const exists = opts.exists ?? fs.existsSync;
374
+ // On win32 the extensions come FIRST (what cmd.exe/pwsh actually launch) and
375
+ // the bare name last (git-bash). Elsewhere there is only the bare name.
376
+ const suffixes = platform === 'win32'
377
+ ? [
378
+ ...(opts.pathExt ?? process.env.PATHEXT ?? DEFAULT_PATHEXT)
379
+ .split(';')
380
+ .filter(Boolean)
381
+ .map((ext) => (ext.startsWith('.') ? ext : `.${ext}`)),
382
+ '',
383
+ ]
384
+ : [''];
385
+ const p = pathFor(platform);
386
+ for (const dir of searchPathEntries(pathValue, platform)) {
387
+ for (const suffix of suffixes) {
388
+ const candidate = p.join(dir, `${binName}${suffix}`);
389
+ if (exists(candidate))
390
+ return candidate;
391
+ }
392
+ }
393
+ return null;
272
394
  }
273
395
  /**
274
396
  * Extracts the package name from a spec like `name@1.2`, `@scope/name`,
@@ -69,7 +69,7 @@ export declare function shouldUseNativeOxfmt(): Promise<boolean>;
69
69
  *
70
70
  * oxlint: always spawned via its Node ESM launcher on a real Node host
71
71
  * (`nodeBinary()`: `process.execPath` under Node, PATH `node` under the GJS
72
- * bundle where `process.execPath` is the bundle itself) so the JS plugin host
72
+ * bundle where `process.execPath` is the `gjs` interpreter) so the JS plugin host
73
73
  * is available — required for the `jsPlugins`-wired `gjsify/register-class-order`
74
74
  * rule. Under GJS a `node` on PATH is required (oxlint has no native bridge
75
75
  * yet); its absence surfaces as a clear OxcNotFoundError, never a hang.
@@ -53,6 +53,7 @@ import { pathToFileURL } from 'node:url';
53
53
  import { hostLibc } from './platform-check.js';
54
54
  import { findWorkspaceRoot } from './workspace-root.js';
55
55
  import { resolveNpmPackage } from './resolve-npm-package.js';
56
+ import { activateNativePrebuilds } from './gi-search-path.js';
56
57
  import { nodeBinary } from './run-node.js';
57
58
  import { spawnToCompletion } from './spawn.js';
58
59
  import { isGjs, gjsExit } from '@gjsify/rolldown-plugin-gjsify/runtime';
@@ -245,6 +246,10 @@ async function tryLoadNativeOxfmt() {
245
246
  if (!isGjs())
246
247
  return null;
247
248
  try {
249
+ // Same reason as `bundler-pick.ts`'s `tryLoadNative()`: put the
250
+ // prebuild on girepository's own search paths so the bridge loads
251
+ // without the `gjsify` launcher having exported anything. ADR 0021.
252
+ activateNativePrebuilds();
248
253
  const specifier = '@gjsify/oxfmt-native';
249
254
  const resolved = resolveNpmPackage(specifier, { bundleUrl: import.meta.url }) ??
250
255
  createRequire(import.meta.url).resolve(specifier);
@@ -293,7 +298,7 @@ export async function shouldUseNativeOxfmt() {
293
298
  *
294
299
  * oxlint: always spawned via its Node ESM launcher on a real Node host
295
300
  * (`nodeBinary()`: `process.execPath` under Node, PATH `node` under the GJS
296
- * bundle where `process.execPath` is the bundle itself) so the JS plugin host
301
+ * bundle where `process.execPath` is the `gjs` interpreter) so the JS plugin host
297
302
  * is available — required for the `jsPlugins`-wired `gjsify/register-class-order`
298
303
  * rule. Under GJS a `node` on PATH is required (oxlint has no native bridge
299
304
  * yet); its absence surfaces as a clear OxcNotFoundError, never a hang.
@@ -323,11 +328,10 @@ export async function runOxc(tool, args, opts = {}) {
323
328
  /**
324
329
  * Spawn an oxc tool via its Node ESM launcher (`node_modules/<tool>/bin/…`) on
325
330
  * a real Node host. CRITICAL: use `nodeBinary()`, not `process.execPath`
326
- * directly — under the committed GJS bundle `process.execPath` is the CLI
327
- * bundle path (`gjs`/the `.mjs`), so spawning it re-executes the CLI with the
328
- * launcher as its first positional (yargs prints top-level help) and the parent
329
- * hangs waiting on a child that never does the intended work. `nodeBinary()`
330
- * resolves to PATH `node` under GJS, so the launcher runs on Node as required.
331
+ * directly — under GJS `process.execPath` is the `gjs` interpreter, so spawning
332
+ * it runs the Node ESM launcher under GJS, where the plugin host does not exist.
333
+ * `nodeBinary()` resolves to PATH `node` under GJS, so the launcher runs on Node
334
+ * as required.
331
335
  *
332
336
  * `completion: 'return'`: `lint` / `format` / `fix` hand the tool's exit code
333
337
  * to `setOxcExitCode`, which deliberately does NOT terminate the process on a
@@ -1,19 +1,9 @@
1
1
  /**
2
2
  * Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
3
3
  *
4
- * Branches on WHICH RUNTIME CAN WORK, not on which capability is present. The
5
- * distinction matters because the two branches are not interchangeable:
6
- * `readFileSync(0)` is correct on Node/Bun/Deno and can NEVER work under GJS —
7
- * `@gjsify/fs` has no numeric-fd path, so the `0` is coerced to a relative
8
- * PATH and the call opens `./0`. A GJS run that reaches that line is already
9
- * lost, so using it as GJS's fallback only converts a clear failure into
10
- * `ENOENT … read '0'`, which is what a capability probe returning a false
11
- * negative did.
12
- *
13
- * So: the presence of `imports.gi` decides. On GJS both GJS readers are tried
14
- * and a failure THROWS with what was attempted; the Node reader is never
15
- * reached. Off GJS there is no gi namespace and `readFileSync(0)` is the only
16
- * and correct answer.
4
+ * Synchronous on purpose: the callers are CLI argument-parsing paths that run
5
+ * before any main loop exists, and the async `process.stdin` route carries the
6
+ * documented resume/pause races (see `utils/prompt.ts`).
17
7
  */
18
8
  export declare function readStdinText(): string;
19
9
  /**
@@ -1,119 +1,40 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Read ALL of stdin, on Node AND inside the committed GJS bundles.
3
3
  //
4
- // `readFileSync(0, 'utf-8')` is the Node idiom for "slurp stdin", and it is
5
- // what `gjsify affected --changed-from-stdin` used. It does NOT survive the
6
- // GJS bundle: `@gjsify/fs` has no numeric-file-descriptor path, so the `0` is
7
- // coerced to a PATH and the call opens the relative file `./0` — every GJS
8
- // caller died with
4
+ // `readFileSync(0, 'utf-8')` is the Node idiom for "slurp stdin". It used NOT to
5
+ // survive the GJS bundle: `@gjsify/fs` had no numeric-file-descriptor path, so
6
+ // the `0` was coerced to a PATH and the call opened the relative file `./0` —
7
+ // every GJS caller died with
9
8
  //
10
9
  // Error: ENOENT: … read '0' (readStdinLines → readFileSync)
11
10
  //
12
11
  // which silently disabled the ONE fixture-driven entry point AGENTS.md
13
12
  // documents for testing the CI classifier (`--changed-from-stdin`).
14
13
  //
15
- // Under GJS we therefore read fd 0 through `GioUnix.InputStream`. Two
16
- // deliberate choices:
14
+ // THE GAP IS CLOSED AT THE CORE, so the workaround is gone: `@gjsify/fs` maps
15
+ // the parent-supplied descriptors 0/1/2 onto the process's own Unix streams
16
+ // (`packages/node/fs/src/std-fd.ts`, reached from `readFileSync` through
17
+ // `sync.ts`), which makes the Node idiom mean the same thing under GJS. This
18
+ // module used to carry two GJS-only readers for that gap — a
19
+ // `GioUnix.InputStream` chunk loop and a `/dev/stdin` `Gio.File` fallback —
20
+ // plus a runtime branch to choose between them. All three were consumer-side
21
+ // repair for a missing core capability.
17
22
  //
18
- // - `globalThis.imports.gi.GioUnix` rather than a `gi://GioUnix` import.
19
- // `packages/infra/cli/lib/**` is ALSO the Node entry point of
20
- // `@gjsify/cli` (`bin: lib/index.js`); a static `gi://` import anywhere
21
- // reachable from it breaks `gjsify` on Node, and a dynamic one would need
22
- // an `@girs/giounix-2.0` type dependency the CLI does not otherwise have.
23
- // The legacy `imports.gi` form is the sanctioned GJS-bootstrap escape
24
- // hatch (AGENTS.md "Reading globals", exception 4) and adds NO new typelib
25
- // to the classifier bundle — `gi://GioUnix?version=2.0` is already in its
26
- // import graph via `@gjsify/fs`.
27
- // - a synchronous read. The callers are CLI argument-parsing paths that run
28
- // before any main loop exists, and the async `process.stdin` route carries
29
- // the documented resume/pause races (see `utils/prompt.ts`).
23
+ // Anything re-adding a GJS branch HERE is re-adding the workaround. If stdin
24
+ // misbehaves under GJS the defect is in `@gjsify/fs` and belongs there, with a
25
+ // test — that is what made the branch removable in the first place
26
+ // (`packages/node/fs/src/fd-ops.spec.ts` + `tests/e2e/fs-std-fd`, which needs a
27
+ // real pipe and so cannot live in a unit spec).
30
28
  import { readFileSync } from 'node:fs';
31
- const CHUNK = 64 * 1024;
32
- /** Read fd 0 to EOF under GJS via GioUnix (no `@gjsify/fs` fd support needed). */
33
- function readStdinTextGioUnix() {
34
- const gi = giNamespace();
35
- const GioUnix = gi?.GioUnix;
36
- if (typeof GioUnix?.InputStream !== 'function') {
37
- throw new Error('GioUnix.InputStream is unavailable');
38
- }
39
- const stream = new GioUnix.InputStream({ fd: 0, close_fd: false });
40
- const chunks = [];
41
- let total = 0;
42
- for (;;) {
43
- const bytes = stream.read_bytes(CHUNK, null);
44
- if (bytes.get_size() === 0)
45
- break;
46
- const arr = bytes.toArray();
47
- chunks.push(arr);
48
- total += arr.length;
49
- }
50
- if (total === 0)
51
- return '';
52
- const buf = new Uint8Array(total);
53
- let offset = 0;
54
- for (const c of chunks) {
55
- buf.set(c, offset);
56
- offset += c.length;
57
- }
58
- return new TextDecoder().decode(buf);
59
- }
60
- /**
61
- * Read stdin under GJS through Gio ALONE, without GioUnix.
62
- *
63
- * `/dev/stdin` is fd 0 by another name, and `Gio` is present in every bundle
64
- * that can reach this code at all. This exists because the GioUnix route has
65
- * been observed to be unavailable on a host where it should have worked, and a
66
- * second independent mechanism is cheaper than another round of guessing.
67
- */
68
- function readStdinTextGioFile() {
69
- const gi = giNamespace();
70
- const Gio = gi?.Gio;
71
- if (!Gio?.File)
72
- throw new Error('Gio.File is unavailable');
73
- const [ok, contents] = Gio.File.new_for_path('/dev/stdin').load_contents(null);
74
- if (!ok)
75
- throw new Error('Gio.File.load_contents("/dev/stdin") returned false');
76
- return new TextDecoder().decode(contents);
77
- }
78
- /**
79
- * The GJS `imports.gi` namespace, or `undefined` when not running on GJS.
80
- * Same optional-chained probe as `isGjs()` — off GJS `imports` is simply
81
- * absent and the read yields `undefined`; there is no throw path.
82
- */
83
- function giNamespace() {
84
- return globalThis.imports?.gi;
85
- }
86
29
  /**
87
30
  * Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
88
31
  *
89
- * Branches on WHICH RUNTIME CAN WORK, not on which capability is present. The
90
- * distinction matters because the two branches are not interchangeable:
91
- * `readFileSync(0)` is correct on Node/Bun/Deno and can NEVER work under GJS —
92
- * `@gjsify/fs` has no numeric-fd path, so the `0` is coerced to a relative
93
- * PATH and the call opens `./0`. A GJS run that reaches that line is already
94
- * lost, so using it as GJS's fallback only converts a clear failure into
95
- * `ENOENT … read '0'`, which is what a capability probe returning a false
96
- * negative did.
97
- *
98
- * So: the presence of `imports.gi` decides. On GJS both GJS readers are tried
99
- * and a failure THROWS with what was attempted; the Node reader is never
100
- * reached. Off GJS there is no gi namespace and `readFileSync(0)` is the only
101
- * and correct answer.
32
+ * Synchronous on purpose: the callers are CLI argument-parsing paths that run
33
+ * before any main loop exists, and the async `process.stdin` route carries the
34
+ * documented resume/pause races (see `utils/prompt.ts`).
102
35
  */
103
36
  export function readStdinText() {
104
- if (!giNamespace())
105
- return readFileSync(0, 'utf-8');
106
- const attempts = [];
107
- for (const read of [readStdinTextGioUnix, readStdinTextGioFile]) {
108
- try {
109
- return read();
110
- }
111
- catch (err) {
112
- attempts.push(`${read.name}: ${err.message}`);
113
- }
114
- }
115
- throw new Error(`gjsify: cannot read stdin under GJS. Tried ${attempts.join('; ')}. ` +
116
- 'Pass the input as an argument instead (e.g. --base <ref> rather than --changed-from-stdin).');
37
+ return readFileSync(0, 'utf-8');
117
38
  }
118
39
  /**
119
40
  * Read stdin and split it into trimmed, non-empty lines. Accepts LF and CRLF
@@ -87,7 +87,16 @@
87
87
  // bundle's first line runs.
88
88
  import { spawnSync } from 'node:child_process';
89
89
  import { statSync } from 'node:fs';
90
- import { basename, dirname, join } from 'node:path';
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.
98
+ import { posix } from 'node:path';
99
+ const { basename, dirname, join } = posix;
91
100
  /**
92
101
  * Prefixes a macOS GI stack is installed under in practice — probed, never
93
102
  * assumed: each is accepted only when it actually holds a `girepository-1.0/`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -101,6 +101,11 @@
101
101
  "__PACKAGE_VERSION__": {
102
102
  "field": "version"
103
103
  }
104
+ },
105
+ "os": {
106
+ "linux": "supported",
107
+ "darwin": "supported",
108
+ "win32": "supported"
104
109
  }
105
110
  },
106
111
  "files": [
@@ -129,19 +134,19 @@
129
134
  "cli"
130
135
  ],
131
136
  "dependencies": {
132
- "@gjsify/buffer": "^0.30.0",
133
- "@gjsify/create-app": "^0.30.0",
134
- "@gjsify/node-globals": "^0.30.0",
135
- "@gjsify/node-polyfills": "^0.30.0",
136
- "@gjsify/npm-registry": "^0.30.0",
137
- "@gjsify/resolve-npm": "^0.30.0",
138
- "@gjsify/rolldown-plugin-gjsify": "^0.30.0",
139
- "@gjsify/rolldown-plugin-pnp": "^0.30.0",
140
- "@gjsify/semver": "^0.30.0",
141
- "@gjsify/tar": "^0.30.0",
142
- "@gjsify/tsc": "^0.30.0",
143
- "@gjsify/web-polyfills": "^0.30.0",
144
- "@gjsify/workspace": "^0.30.0",
137
+ "@gjsify/buffer": "^0.32.0",
138
+ "@gjsify/create-app": "^0.32.0",
139
+ "@gjsify/node-globals": "^0.32.0",
140
+ "@gjsify/node-polyfills": "^0.32.0",
141
+ "@gjsify/npm-registry": "^0.32.0",
142
+ "@gjsify/resolve-npm": "^0.32.0",
143
+ "@gjsify/rolldown-plugin-gjsify": "^0.32.0",
144
+ "@gjsify/rolldown-plugin-pnp": "^0.32.0",
145
+ "@gjsify/semver": "^0.32.0",
146
+ "@gjsify/tar": "^0.32.0",
147
+ "@gjsify/tsc": "^0.32.0",
148
+ "@gjsify/web-polyfills": "^0.32.0",
149
+ "@gjsify/workspace": "^0.32.0",
145
150
  "cosmiconfig": "^9.0.2",
146
151
  "get-tsconfig": "^4.14.0",
147
152
  "pkg-types": "^2.3.1",
@@ -149,13 +154,13 @@
149
154
  "yargs": "^18.0.0"
150
155
  },
151
156
  "devDependencies": {
152
- "@gjsify/unit": "^0.30.0",
157
+ "@gjsify/unit": "^0.32.0",
153
158
  "@types/yargs": "^17.0.35",
154
159
  "typescript": "^6.0.3"
155
160
  },
156
161
  "peerDependencies": {
157
- "@gjsify/oxfmt-native": "^0.30.0",
158
- "@gjsify/rolldown-native": "^0.30.0"
162
+ "@gjsify/oxfmt-native": "^0.32.0",
163
+ "@gjsify/rolldown-native": "^0.32.0"
159
164
  },
160
165
  "peerDependenciesMeta": {
161
166
  "@gjsify/oxfmt-native": {
package/showcases.json CHANGED
@@ -5,57 +5,49 @@
5
5
  "name": "canvas2d-fireworks",
6
6
  "category": "dom",
7
7
  "package": "@gjsify/example-dom-canvas2d-fireworks",
8
- "description": "Colorful fireworks Canvas 2D example with Adwaita controls",
9
- "needsWebgl": false
8
+ "description": "Colorful fireworks Canvas 2D example with Adwaita controls"
10
9
  },
11
10
  {
12
11
  "name": "excalibur-jelly-jumper",
13
12
  "category": "dom",
14
13
  "package": "@gjsify/example-dom-excalibur-jelly-jumper",
15
- "description": "Excalibur.js jelly-jumper game",
16
- "needsWebgl": false
14
+ "description": "Excalibur.js jelly-jumper game"
17
15
  },
18
16
  {
19
17
  "name": "three-geometry-teapot",
20
18
  "category": "dom",
21
19
  "package": "@gjsify/example-dom-three-geometry-teapot",
22
- "description": "Three.js Utah teapot",
23
- "needsWebgl": true
20
+ "description": "Three.js Utah teapot"
24
21
  },
25
22
  {
26
23
  "name": "three-postprocessing-pixel",
27
24
  "category": "dom",
28
25
  "package": "@gjsify/example-dom-three-postprocessing-pixel",
29
- "description": "Three.js postprocessing pixel demo",
30
- "needsWebgl": true
26
+ "description": "Three.js postprocessing pixel demo"
31
27
  },
32
28
  {
33
29
  "name": "webrtc-loopback",
34
30
  "category": "dom",
35
31
  "package": "@gjsify/example-dom-webrtc-loopback",
36
- "description": "WebRTC data-channel loopback: two local peers exchange offer/answer + ICE, then echo messages over an RTCDataChannel",
37
- "needsWebgl": false
32
+ "description": "WebRTC data-channel loopback: two local peers exchange offer/answer + ICE, then echo messages over an RTCDataChannel"
38
33
  },
39
34
  {
40
35
  "name": "minimalist-browser",
41
36
  "category": "dom",
42
37
  "package": "@gjsify/example-dom-minimalist-browser",
43
- "description": "Minimalist browser with URL bar + back/forward + iframe content area; same code runs in browser (real iframe) and GJS (IFrameBridge over WebKit.WebView)",
44
- "needsWebgl": false
38
+ "description": "Minimalist browser with URL bar + back/forward + iframe content area; same code runs in browser (real iframe) and GJS (IFrameBridge over WebKit.WebView)"
45
39
  },
46
40
  {
47
41
  "name": "express-webserver",
48
42
  "category": "node",
49
43
  "package": "@gjsify/example-node-express-webserver",
50
- "description": "Express web server on GJS via @gjsify/http",
51
- "needsWebgl": false
44
+ "description": "Express web server on GJS via @gjsify/http"
52
45
  },
53
46
  {
54
47
  "name": "adwaita-storybook",
55
48
  "category": "gtk",
56
49
  "package": "@gjsify/example-gtk-adwaita-storybook",
57
- "description": "Interactive Libadwaita component browser — author one story per widget, rendered natively (GTK) and in the browser",
58
- "needsWebgl": false
50
+ "description": "Interactive Libadwaita component browser — author one story per widget, rendered natively (GTK) and in the browser"
59
51
  }
60
52
  ]
61
53
  }