@gjsify/cli 0.30.0 → 0.31.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.
- package/dist/cli.gjs.mjs +186 -185
- package/lib/actions/build.d.ts +14 -0
- package/lib/actions/build.js +78 -3
- package/lib/actions/index.d.ts +2 -0
- package/lib/actions/index.js +2 -0
- package/lib/affected-entry.d.ts +1 -0
- package/lib/affected-entry.js +49 -0
- package/lib/bundler-pick.js +84 -19
- package/lib/cli-app.js +20 -0
- package/lib/cli-fail.d.ts +22 -0
- package/lib/cli-fail.js +67 -0
- package/lib/commands/affected.d.ts +2 -2
- package/lib/commands/affected.js +16 -0
- package/lib/commands/install.d.ts +55 -0
- package/lib/commands/install.js +38 -21
- package/lib/commands/run.js +25 -6
- package/lib/commands/test.js +4 -4
- package/lib/commands/tsc.js +59 -11
- package/lib/commands/workspace.js +13 -0
- package/lib/config.js +6 -2
- package/lib/index.js +18 -1
- package/lib/templates/install.mjs.tmpl +27 -2
- package/lib/types/command.d.ts +18 -0
- package/lib/utils/check-system-deps.d.ts +66 -1
- package/lib/utils/check-system-deps.js +139 -2
- package/lib/utils/detect-native-packages.js +12 -1
- package/lib/utils/discover-showcases.d.ts +0 -2
- package/lib/utils/discover-showcases.js +0 -1
- package/lib/utils/dlx-cache.d.ts +22 -2
- package/lib/utils/dlx-cache.js +62 -6
- package/lib/utils/force-exit.d.ts +10 -0
- package/lib/utils/force-exit.js +43 -0
- package/lib/utils/gi-search-path.d.ts +36 -0
- package/lib/utils/gi-search-path.js +146 -0
- package/lib/utils/gjs-source-escape.d.ts +10 -0
- package/lib/utils/gjs-source-escape.js +49 -0
- package/lib/utils/gjsify-shim.d.ts +29 -2
- package/lib/utils/gjsify-shim.js +131 -22
- package/lib/utils/install-global.d.ts +30 -2
- package/lib/utils/install-global.js +38 -4
- package/lib/utils/oxc-resolve.d.ts +1 -1
- package/lib/utils/oxc-resolve.js +10 -6
- package/lib/utils/stdin.d.ts +3 -13
- package/lib/utils/stdin.js +21 -100
- package/lib/utils/system-gi.js +10 -1
- package/package.json +22 -17
- package/showcases.json +8 -16
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* True when nested `gjsify` calls in this process tree are being routed through
|
|
3
|
+
* a self-shim rather than through the workspace's `node_modules/.bin/gjsify`.
|
|
4
|
+
*
|
|
5
|
+
* `detectPackageManager()` reads this: a bootstrap CLI must run package scripts
|
|
6
|
+
* ITSELF, because npm re-prepends `node_modules/.bin` for every lifecycle
|
|
7
|
+
* script it starts — which puts the tree's dead shim back ahead of the shim dir
|
|
8
|
+
* this module placed on PATH, and undoes the whole repair one level down.
|
|
9
|
+
*/
|
|
10
|
+
export declare function usingSelfShim(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Does this invocation need its own `gjsify` on PATH, rather than the
|
|
13
|
+
* workspace's npm bin?
|
|
14
|
+
*
|
|
15
|
+
* Pure, and takes the host facts as PARAMETERS rather than reading them
|
|
16
|
+
* ambiently — the same shape `platform-check.ts` documents, and what lets the
|
|
17
|
+
* Windows-relevant branch be tested from a Linux host.
|
|
18
|
+
*
|
|
19
|
+
* @param gjs running under GJS (the npm bin is the Node entry there)
|
|
20
|
+
* @param selfEntry absolute path of the CLI entry currently executing
|
|
21
|
+
* @param workspaceRoot absolute path of the workspace the command will act on
|
|
22
|
+
*/
|
|
23
|
+
export declare function needsSelfShim(opts: {
|
|
24
|
+
gjs: boolean;
|
|
25
|
+
selfEntry: string;
|
|
26
|
+
workspaceRoot: string;
|
|
27
|
+
}): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Ensure a runnable `gjsify` is on `process.env.PATH` for child processes.
|
|
3
30
|
* Idempotent per process; children inherit the shim via `GJSIFY_SHIM_DIR` and
|
|
4
|
-
* reuse it instead of creating their own.
|
|
31
|
+
* reuse it instead of creating their own.
|
|
5
32
|
*/
|
|
6
33
|
export declare function ensureGjsifyShimOnPath(): void;
|
package/lib/utils/gjsify-shim.js
CHANGED
|
@@ -1,52 +1,161 @@
|
|
|
1
|
-
//
|
|
1
|
+
// A runnable `gjsify` on PATH, for the two trees where the npm bin is not one.
|
|
2
2
|
//
|
|
3
|
-
// The CLI's npm
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The CLI's npm bin (`node_modules/.bin/gjsify`) is a DISPATCHER written at
|
|
4
|
+
// install time: it prefers `packages/infra/cli/dist/cli.gjs.mjs` on a host that
|
|
5
|
+
// has `gjs`, and otherwise execs the Node entry `packages/infra/cli/lib/index.js`.
|
|
6
|
+
// Multi-package orchestration and compound package scripts resolve `gjsify`
|
|
7
|
+
// from PATH and go through it:
|
|
7
8
|
// - `gjsify workspace`/`foreach` spawn `<pm> run <script>` per package; under
|
|
8
9
|
// GJS the package manager is `gjsify` itself, and `spawn('gjsify', …)`
|
|
9
10
|
// searches PATH.
|
|
10
11
|
// - a compound script like `gjsify run a && gjsify run b` is executed via
|
|
11
|
-
// `/bin/sh -c
|
|
12
|
-
//
|
|
12
|
+
// `/bin/sh -c …` (or `%COMSPEC% /c …`), and the shell resolves each
|
|
13
|
+
// `gjsify` from PATH.
|
|
13
14
|
//
|
|
14
|
-
// Fix:
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Fix: write a tiny wrapper that re-invokes the RUNNING CLI, and prepend its
|
|
16
|
+
// directory to `process.env.PATH` so every child process (and grandchild, via
|
|
17
|
+
// the inherited `GJSIFY_SHIM_DIR` marker) resolves `gjsify` to a CLI that
|
|
18
|
+
// actually exists.
|
|
19
|
+
//
|
|
20
|
+
// TWO HOSTS NEED IT, FOR TWO DIFFERENT REASONS
|
|
21
|
+
//
|
|
22
|
+
// GJS — always. The npm bin is the NODE entry and is useless in a node-free
|
|
23
|
+
// environment (the Flatpak sandbox has no Node): without the shim the failures
|
|
24
|
+
// are `spawn npm ENOENT` and `gjsify: not found`.
|
|
25
|
+
//
|
|
26
|
+
// Node — only when the running CLI came from OUTSIDE the workspace, which is
|
|
27
|
+
// exactly the cold-tree bootstrap (`npx @gjsify/cli@latest …`, or a global
|
|
28
|
+
// install). ADR 0002 untracked BOTH bundles, so on a tree that is installed but
|
|
29
|
+
// not yet built, `node_modules/.bin/gjsify` exists and everything it dispatches
|
|
30
|
+
// to is missing. The bin shim is then a live landmine: it resolves, and then
|
|
31
|
+
// dies with `Cannot find module …/packages/infra/cli/lib/index.js` on the FIRST
|
|
32
|
+
// nested `gjsify` — which for `gjsify run build:infra` is its first clause,
|
|
33
|
+
// `gjsify workspace @gjsify/vite-plugin-blueprint build`.
|
|
34
|
+
//
|
|
35
|
+
// THE INCIDENT, because a rule without it gets simplified back into the bug.
|
|
36
|
+
//
|
|
37
|
+
// `windows-suites.yml` (added #1021) and `macos-suites.yml` (#1022) both landed
|
|
38
|
+
// during the 2026-08-06 GitHub Actions degradation with NO checks reported on
|
|
39
|
+
// their PRs, and neither leg had ever been green. Both ordered the cold-tree
|
|
40
|
+
// bootstrap as `run build:infra` BEFORE `install`, which cannot work either:
|
|
41
|
+
// `@gjsify/vite-plugin-blueprint` declares `"types": ["node"]`, so with no
|
|
42
|
+
// `node_modules` its `gjsify tsc` fails `TS2688: Cannot find type definition
|
|
43
|
+
// file for 'node'`. Correcting the order to install-then-build then exposed
|
|
44
|
+
// THIS gap, one step later.
|
|
45
|
+
//
|
|
46
|
+
// Reproduced on Linux at de7f5525 in a cold worktree — nothing about it is
|
|
47
|
+
// Windows-specific:
|
|
48
|
+
//
|
|
49
|
+
// npx --yes @gjsify/cli@latest install --immutable # ok
|
|
50
|
+
// npx --yes @gjsify/cli@latest run build:infra # Cannot find module …
|
|
51
|
+
//
|
|
52
|
+
// Linux CI never saw it because its bootstrap runs `gjs -m <published bundle>
|
|
53
|
+
// run build:infra` (`.github/actions/gjsify-setup`), which takes the GJS branch
|
|
54
|
+
// above. Windows is simply the only leg with no `gjs` to fall back on, so it is
|
|
55
|
+
// where the gap became load-bearing.
|
|
56
|
+
//
|
|
57
|
+
// Scoped to an outside-the-workspace CLI ON PURPOSE: when the invocation
|
|
58
|
+
// already came through the workspace's own `node_modules/.bin/gjsify`, that
|
|
59
|
+
// shim IS the CLI the tree wants, and today's dispatch (GJS bundle first on a
|
|
60
|
+
// host with `gjs`) stays byte-for-byte unchanged. Widening this to every Node
|
|
61
|
+
// invocation would silently move in-repo nested builds off the GJS bundle.
|
|
18
62
|
import { mkdtempSync, writeFileSync, chmodSync, existsSync } from 'node:fs';
|
|
19
63
|
import { tmpdir } from 'node:os';
|
|
20
|
-
import { join, delimiter } from 'node:path';
|
|
64
|
+
import { join, delimiter, relative, isAbsolute } from 'node:path';
|
|
21
65
|
import { fileURLToPath } from 'node:url';
|
|
22
66
|
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
67
|
+
import { buildLauncherShims } from './bin-shim.js';
|
|
68
|
+
import { findWorkspaceRoot } from './workspace-root.js';
|
|
23
69
|
let _ensured = false;
|
|
70
|
+
let _selfShimActive = false;
|
|
24
71
|
/**
|
|
25
|
-
*
|
|
72
|
+
* True when nested `gjsify` calls in this process tree are being routed through
|
|
73
|
+
* a self-shim rather than through the workspace's `node_modules/.bin/gjsify`.
|
|
74
|
+
*
|
|
75
|
+
* `detectPackageManager()` reads this: a bootstrap CLI must run package scripts
|
|
76
|
+
* ITSELF, because npm re-prepends `node_modules/.bin` for every lifecycle
|
|
77
|
+
* script it starts — which puts the tree's dead shim back ahead of the shim dir
|
|
78
|
+
* this module placed on PATH, and undoes the whole repair one level down.
|
|
79
|
+
*/
|
|
80
|
+
export function usingSelfShim() {
|
|
81
|
+
return _selfShimActive;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Does this invocation need its own `gjsify` on PATH, rather than the
|
|
85
|
+
* workspace's npm bin?
|
|
86
|
+
*
|
|
87
|
+
* Pure, and takes the host facts as PARAMETERS rather than reading them
|
|
88
|
+
* ambiently — the same shape `platform-check.ts` documents, and what lets the
|
|
89
|
+
* Windows-relevant branch be tested from a Linux host.
|
|
90
|
+
*
|
|
91
|
+
* @param gjs running under GJS (the npm bin is the Node entry there)
|
|
92
|
+
* @param selfEntry absolute path of the CLI entry currently executing
|
|
93
|
+
* @param workspaceRoot absolute path of the workspace the command will act on
|
|
94
|
+
*/
|
|
95
|
+
export function needsSelfShim(opts) {
|
|
96
|
+
if (opts.gjs)
|
|
97
|
+
return true;
|
|
98
|
+
// Outside the workspace ⇒ a bootstrap CLI (npx / global install), so the
|
|
99
|
+
// tree's own `.bin/gjsify` may well point at build outputs that do not
|
|
100
|
+
// exist yet. Inside ⇒ the tree's CLI is the one running; leave PATH alone.
|
|
101
|
+
const rel = relative(opts.workspaceRoot, opts.selfEntry);
|
|
102
|
+
return rel === '' || rel.startsWith('..') || isAbsolute(rel);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Ensure a runnable `gjsify` is on `process.env.PATH` for child processes.
|
|
26
106
|
* Idempotent per process; children inherit the shim via `GJSIFY_SHIM_DIR` and
|
|
27
|
-
* reuse it instead of creating their own.
|
|
107
|
+
* reuse it instead of creating their own.
|
|
28
108
|
*/
|
|
29
109
|
export function ensureGjsifyShimOnPath() {
|
|
30
|
-
if (_ensured
|
|
110
|
+
if (_ensured)
|
|
31
111
|
return;
|
|
32
112
|
_ensured = true;
|
|
33
|
-
// A parent gjsify
|
|
34
|
-
//
|
|
113
|
+
// A parent gjsify already created the shim — reuse it so nested
|
|
114
|
+
// orchestration doesn't pile up temp dirs / PATH entries. The
|
|
115
|
+
// extension-less member is written on every host, so it stays the probe.
|
|
35
116
|
const inherited = process.env.GJSIFY_SHIM_DIR;
|
|
36
117
|
if (inherited && existsSync(join(inherited, 'gjsify'))) {
|
|
118
|
+
_selfShimActive = true;
|
|
37
119
|
if (!(process.env.PATH ?? '').split(delimiter).includes(inherited)) {
|
|
38
120
|
process.env.PATH = inherited + delimiter + (process.env.PATH ?? '');
|
|
39
121
|
}
|
|
40
122
|
return;
|
|
41
123
|
}
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
124
|
+
const gjs = isGjs();
|
|
125
|
+
// Under GJS `import.meta.url` IS the entry — the whole CLI collapses into
|
|
126
|
+
// the single `cli.gjs.mjs` bundle, so this module's URL is it. Under Node
|
|
127
|
+
// this module is `lib/utils/gjsify-shim.js` and the npm bin is
|
|
128
|
+
// `lib/index.js` (package.json `bin`), one hop up.
|
|
129
|
+
const selfEntry = gjs ? fileURLToPath(import.meta.url) : fileURLToPath(new URL('../index.js', import.meta.url));
|
|
130
|
+
const workspaceRoot = findWorkspaceRoot(process.cwd()) ?? process.cwd();
|
|
131
|
+
if (!needsSelfShim({ gjs, selfEntry, workspaceRoot }))
|
|
132
|
+
return;
|
|
46
133
|
const dir = mkdtempSync(join(tmpdir(), 'gjsify-shim-'));
|
|
47
134
|
const shim = join(dir, 'gjsify');
|
|
48
|
-
|
|
135
|
+
// The interpreter is named, not pathed, on every member — matching
|
|
136
|
+
// `buildShLauncher`'s `exec gjs` / `exec node` and `buildLauncherShims`'
|
|
137
|
+
// documented contract, so a missing interpreter fails the same way on every
|
|
138
|
+
// OS. `process.execPath` is deliberately NOT used: the batch member cannot
|
|
139
|
+
// quote an interpreter argument (`<interpreter> "<target>" %*`), so a path
|
|
140
|
+
// like `C:\Program Files\nodejs\node.exe` would split, and a trio whose
|
|
141
|
+
// members disagree about which interpreter they re-enter is worse than one
|
|
142
|
+
// name resolved consistently.
|
|
143
|
+
const interpreter = gjs ? process.env.GJS_CONSOLE || 'gjs' : 'node';
|
|
144
|
+
const interpreterArgs = gjs ? ['-m'] : [];
|
|
145
|
+
const argv = interpreterArgs.length > 0 ? `${interpreterArgs.join(' ')} ` : '';
|
|
146
|
+
writeFileSync(shim, `#!/bin/sh\nexec "${interpreter}" ${argv}"${selfEntry}" "$@"\n`, { mode: 0o755 });
|
|
49
147
|
chmodSync(shim, 0o755);
|
|
148
|
+
// cmd.exe and pwsh cannot run the extension-less member: it is not on
|
|
149
|
+
// PATHEXT and Windows has no shebang handling. npm's three-sibling answer
|
|
150
|
+
// is the de-facto standard, and `buildLauncherShims` is this repo's
|
|
151
|
+
// reviewed port of it — including the IF/ELSE ERRORLEVEL handling that
|
|
152
|
+
// `&&`/`||` chaining gets wrong.
|
|
153
|
+
if (!gjs && process.platform === 'win32') {
|
|
154
|
+
const { cmd, ps1 } = buildLauncherShims({ interpreter, interpreterArgs, target: selfEntry });
|
|
155
|
+
writeFileSync(`${shim}.cmd`, cmd);
|
|
156
|
+
writeFileSync(`${shim}.ps1`, ps1);
|
|
157
|
+
}
|
|
158
|
+
_selfShimActive = true;
|
|
50
159
|
process.env.GJSIFY_SHIM_DIR = dir;
|
|
51
160
|
process.env.PATH = dir + delimiter + (process.env.PATH ?? '');
|
|
52
161
|
}
|
|
@@ -29,8 +29,36 @@ export declare function defaultGlobalLayout(): GlobalLayout;
|
|
|
29
29
|
* story already degrades on Node.
|
|
30
30
|
*/
|
|
31
31
|
export declare const GJS_ENGINE_PACKAGES: readonly ["@gjsify/rolldown-native", "@gjsify/lightningcss-native", "@gjsify/oxfmt-native"];
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The one engine whose absence is fatal rather than degrading: `gjsify build`
|
|
34
|
+
* has no fallback under GJS. Named separately because two different questions
|
|
35
|
+
* ("what do we install") and ("is a build possible") want different sets.
|
|
36
|
+
*/
|
|
37
|
+
export declare const BUNDLER_ENGINE_PACKAGE = "@gjsify/rolldown-native";
|
|
38
|
+
/**
|
|
39
|
+
* True when a bundler engine `gjsify build` can actually LOAD is reachable from
|
|
40
|
+
* `dir`.
|
|
41
|
+
*
|
|
42
|
+
* This used to `existsSync` the bridge's own `package.json` and stop there. That
|
|
43
|
+
* answer was correct before ADR 0017 and is wrong after it: the bridge is now
|
|
44
|
+
* pure TypeScript (`files: ["lib", "meson.build", "src/rust", "src/vala"]`) and
|
|
45
|
+
* the binary lives in a per-target child (`@gjsify/rolldown-native-<os>-<arch>`).
|
|
46
|
+
* So "bridge present, child missing" reported `true` — which is why
|
|
47
|
+
* `self-update`'s repair gate could say "Already up to date" about a prefix whose
|
|
48
|
+
* engine cannot load. A live defect, not a hypothetical one.
|
|
49
|
+
*
|
|
50
|
+
* Asks `detectNativePackages` instead, which is the same walk-up
|
|
51
|
+
* `findRolldownNativeDir` performs at BUILD time. That matters more than the
|
|
52
|
+
* shortcut it replaces: an installer that disagrees with the builder about
|
|
53
|
+
* whether the engine is present is worse than one that is merely slow. It also
|
|
54
|
+
* makes the gjsify monorepo a no-op — the workspace symlink resolves — which is
|
|
55
|
+
* what keeps ADR 0001's non-destructive invariant safe from a registry copy
|
|
56
|
+
* landing on top of `packages/infra/rolldown-native`.
|
|
57
|
+
*
|
|
58
|
+
* `dir` is a prefix for a global install and a project root for a project one;
|
|
59
|
+
* the walk handles both.
|
|
60
|
+
*/
|
|
61
|
+
export declare function hasBundlerEngineInstalled(dir: string): boolean;
|
|
34
62
|
/**
|
|
35
63
|
* Best-effort install of the GJS-native engine packages into a global prefix.
|
|
36
64
|
*
|
|
@@ -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
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
@@ -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
|
|
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.
|
package/lib/utils/oxc-resolve.js
CHANGED
|
@@ -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
|
|
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
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
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
|
package/lib/utils/stdin.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
/**
|
package/lib/utils/stdin.js
CHANGED
|
@@ -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"
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
//
|
|
16
|
-
//
|
|
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
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
-
|
|
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
|
package/lib/utils/system-gi.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
"version": "0.31.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.
|
|
133
|
-
"@gjsify/create-app": "^0.
|
|
134
|
-
"@gjsify/node-globals": "^0.
|
|
135
|
-
"@gjsify/node-polyfills": "^0.
|
|
136
|
-
"@gjsify/npm-registry": "^0.
|
|
137
|
-
"@gjsify/resolve-npm": "^0.
|
|
138
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
139
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
140
|
-
"@gjsify/semver": "^0.
|
|
141
|
-
"@gjsify/tar": "^0.
|
|
142
|
-
"@gjsify/tsc": "^0.
|
|
143
|
-
"@gjsify/web-polyfills": "^0.
|
|
144
|
-
"@gjsify/workspace": "^0.
|
|
137
|
+
"@gjsify/buffer": "^0.31.0",
|
|
138
|
+
"@gjsify/create-app": "^0.31.0",
|
|
139
|
+
"@gjsify/node-globals": "^0.31.0",
|
|
140
|
+
"@gjsify/node-polyfills": "^0.31.0",
|
|
141
|
+
"@gjsify/npm-registry": "^0.31.0",
|
|
142
|
+
"@gjsify/resolve-npm": "^0.31.0",
|
|
143
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.31.0",
|
|
144
|
+
"@gjsify/rolldown-plugin-pnp": "^0.31.0",
|
|
145
|
+
"@gjsify/semver": "^0.31.0",
|
|
146
|
+
"@gjsify/tar": "^0.31.0",
|
|
147
|
+
"@gjsify/tsc": "^0.31.0",
|
|
148
|
+
"@gjsify/web-polyfills": "^0.31.0",
|
|
149
|
+
"@gjsify/workspace": "^0.31.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.
|
|
157
|
+
"@gjsify/unit": "^0.31.0",
|
|
153
158
|
"@types/yargs": "^17.0.35",
|
|
154
159
|
"typescript": "^6.0.3"
|
|
155
160
|
},
|
|
156
161
|
"peerDependencies": {
|
|
157
|
-
"@gjsify/oxfmt-native": "^0.
|
|
158
|
-
"@gjsify/rolldown-native": "^0.
|
|
162
|
+
"@gjsify/oxfmt-native": "^0.31.0",
|
|
163
|
+
"@gjsify/rolldown-native": "^0.31.0"
|
|
159
164
|
},
|
|
160
165
|
"peerDependenciesMeta": {
|
|
161
166
|
"@gjsify/oxfmt-native": {
|