@gjsify/cli 0.22.0 → 0.24.1
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/README.md +28 -1
- package/dist/cli.gjs.mjs +265 -184
- package/lib/actions/build.d.ts +12 -0
- package/lib/actions/build.js +26 -0
- package/lib/bundler-pick.js +56 -7
- package/lib/commands/affected.js +84 -14
- package/lib/commands/check.js +38 -29
- package/lib/commands/flatpak/build.js +19 -18
- package/lib/commands/foreach.js +119 -42
- package/lib/commands/info.js +11 -5
- package/lib/commands/install.d.ts +16 -0
- package/lib/commands/install.js +123 -36
- package/lib/commands/onboard.js +6 -1
- package/lib/commands/pack.js +69 -4
- package/lib/commands/run.js +26 -14
- package/lib/commands/showcase.js +17 -13
- package/lib/commands/storybook.js +5 -2
- package/lib/commands/trust.js +4 -62
- package/lib/commands/workspace.js +49 -30
- package/lib/utils/bin-shim.d.ts +66 -0
- package/lib/utils/bin-shim.js +246 -0
- package/lib/utils/build-cache.d.ts +42 -6
- package/lib/utils/build-cache.js +114 -42
- package/lib/utils/detect-native-packages.d.ts +118 -7
- package/lib/utils/detect-native-packages.js +213 -29
- package/lib/utils/gjs-bundle-guard.d.ts +32 -0
- package/lib/utils/gjs-bundle-guard.js +103 -0
- package/lib/utils/install-backend-native.d.ts +24 -0
- package/lib/utils/install-backend-native.js +89 -16
- package/lib/utils/install-global.d.ts +16 -0
- package/lib/utils/install-global.js +58 -8
- package/lib/utils/oxc-resolve.js +25 -57
- package/lib/utils/publish-headers.d.ts +2 -3
- package/lib/utils/publish-headers.js +64 -3
- package/lib/utils/publishable-packages.d.ts +22 -0
- package/lib/utils/publishable-packages.js +93 -0
- package/lib/utils/run-gjs.d.ts +6 -7
- package/lib/utils/run-gjs.js +4 -3
- package/lib/utils/run-node.d.ts +16 -0
- package/lib/utils/run-node.js +28 -7
- package/lib/utils/runtimes.d.ts +21 -0
- package/lib/utils/runtimes.js +23 -0
- package/lib/utils/spawn.d.ts +82 -0
- package/lib/utils/spawn.js +139 -0
- package/lib/utils/stdin.d.ts +25 -0
- package/lib/utils/stdin.js +132 -0
- package/package.json +23 -18
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* What the calling command does once this spawn settles.
|
|
4
|
+
*
|
|
5
|
+
* - `'exit'` — the command terminates the process itself (`process.exit(…)`,
|
|
6
|
+
* `forceExit(…)`, …). The armed GJS main loop is quit by that exit, so the
|
|
7
|
+
* streaming async `spawn` is safe on both hosts.
|
|
8
|
+
* - `'return'` — the command returns normally and lets the CLI unwind. Under
|
|
9
|
+
* GJS that MUST NOT leave a main loop armed, so the child is run on the
|
|
10
|
+
* blocking `spawnSync` path instead.
|
|
11
|
+
*
|
|
12
|
+
* See the module header for the full rationale.
|
|
13
|
+
*/
|
|
14
|
+
export type SpawnCompletionContract = 'exit' | 'return';
|
|
15
|
+
export interface SpawnToCompletionOptions {
|
|
16
|
+
/** The caller's teardown contract. Required — see {@link SpawnCompletionContract}. */
|
|
17
|
+
completion: SpawnCompletionContract;
|
|
18
|
+
/** Working directory for the child. Defaults to the parent's. */
|
|
19
|
+
cwd?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Environment for the child. Omit to inherit the parent's environment
|
|
22
|
+
* verbatim (what `spawn` does with no `env`).
|
|
23
|
+
*/
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
/**
|
|
26
|
+
* Seed `FORCE_COLOR=1` (on top of `env` ?? `process.env`) unless the user
|
|
27
|
+
* explicitly set `FORCE_COLOR` or `NO_COLOR`, so tools that key on
|
|
28
|
+
* `process.stdout.isTTY` (chalk, picocolors, …) still emit ANSI when their
|
|
29
|
+
* stdout is a pipe. Mirrors yarn / npm. Default `false`.
|
|
30
|
+
*/
|
|
31
|
+
color?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `'inherit'` (default) forwards the parent's stdio. `'pipe'` gives the
|
|
34
|
+
* caller `child.stdout` / `child.stderr` via {@link onSpawn} — only
|
|
35
|
+
* meaningful together with `completion: 'exit'` (see {@link onSpawn}).
|
|
36
|
+
*/
|
|
37
|
+
stdio?: 'inherit' | 'pipe';
|
|
38
|
+
/**
|
|
39
|
+
* Invoked synchronously with the live `ChildProcess` right after a
|
|
40
|
+
* successful async spawn — the seam for kill-tracking registries and for
|
|
41
|
+
* wiring piped output forwarders.
|
|
42
|
+
*
|
|
43
|
+
* ONLY fires on the streaming path: the blocking `spawnSync` path has no
|
|
44
|
+
* `ChildProcess` handle at all. A site that needs the handle must
|
|
45
|
+
* therefore declare `completion: 'exit'`.
|
|
46
|
+
*/
|
|
47
|
+
onSpawn?: (child: ChildProcess) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Map an `ENOENT` spawn failure (command not on PATH) to a domain error
|
|
50
|
+
* carrying an install hint. Other spawn errors are rejected as-is.
|
|
51
|
+
*/
|
|
52
|
+
notFound?: (err: NodeJS.ErrnoException) => Error;
|
|
53
|
+
/**
|
|
54
|
+
* Capture ceiling for the blocking path. Defaults to 64 MiB — a
|
|
55
|
+
* full-workspace lint with many findings runs well past Node's 1 MB
|
|
56
|
+
* default, and an overflow would silently truncate diagnostics.
|
|
57
|
+
*/
|
|
58
|
+
maxBuffer?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface SpawnCompletion {
|
|
61
|
+
/** The child's exit code, or `null` when it was terminated by a signal. */
|
|
62
|
+
code: number | null;
|
|
63
|
+
/** The terminating signal, or `null` on a normal exit. */
|
|
64
|
+
signal: NodeJS.Signals | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run `cmd args…` to completion and resolve with its exit code and signal.
|
|
68
|
+
*
|
|
69
|
+
* Resolves for ANY child exit — a non-zero code is data, not an error, so each
|
|
70
|
+
* call site keeps its own failure message. Rejects only when the child could
|
|
71
|
+
* not be started at all (mapped through {@link SpawnToCompletionOptions.notFound}
|
|
72
|
+
* for ENOENT).
|
|
73
|
+
*
|
|
74
|
+
* Picks the streaming async path or the blocking captured path per the GJS
|
|
75
|
+
* teardown contract documented at the top of this file.
|
|
76
|
+
*/
|
|
77
|
+
export declare function spawnToCompletion(cmd: string, args: readonly string[], opts: SpawnToCompletionOptions): Promise<SpawnCompletion>;
|
|
78
|
+
/**
|
|
79
|
+
* Format the "child failed" suffix the way the call sites report it: an exit
|
|
80
|
+
* code when the child exited, the signal name when it was killed.
|
|
81
|
+
*/
|
|
82
|
+
export declare function describeExit(result: SpawnCompletion): string;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// One `spawn` → `Promise<{ code, signal }>` wrapper for the whole CLI.
|
|
2
|
+
//
|
|
3
|
+
// Six near-identical wrappers used to live in `commands/{foreach,check,
|
|
4
|
+
// workspace,install}.ts`, `flatpak/build.ts` and `utils/oxc-resolve.ts`. They
|
|
5
|
+
// diverged on the completion event (`close` vs `exit`), whether an ENOENT
|
|
6
|
+
// spawn failure got a human hint, whether `FORCE_COLOR` was seeded — and, the
|
|
7
|
+
// dangerous one, on how they handled the GJS teardown contract below.
|
|
8
|
+
//
|
|
9
|
+
// ## The GJS teardown contract — why this helper has a host branch
|
|
10
|
+
//
|
|
11
|
+
// `@gjsify/child_process`'s ASYNC `spawn()` calls `ensureMainLoop()`
|
|
12
|
+
// (`@gjsify/utils`) so the `Gio.Subprocess` `wait_async` exit callback has a
|
|
13
|
+
// running GLib main context to be dispatched on. `ensureMainLoop()` arms a
|
|
14
|
+
// GJS **main-loop hook** (`GLib.MainLoop.runAsync()` → `setMainLoopHook`), and
|
|
15
|
+
// GJS's `eval_module` runs that hook AFTER the entry module's top-level await
|
|
16
|
+
// settles — entering a blocking `g_main_loop_run()` that nothing ever quits.
|
|
17
|
+
// GJS has no atexit hook, so the only thing that tears it back down is
|
|
18
|
+
// `process.exit()` (whose `exitProcess` idle-schedules `quitMainLoop()` +
|
|
19
|
+
// `imports.system.exit()`).
|
|
20
|
+
//
|
|
21
|
+
// Consequence, measured on gjs 1.88 against the committed `dist/cli.gjs.mjs`:
|
|
22
|
+
// a command that async-spawns a child and then RETURNS NORMALLY parks at 0%
|
|
23
|
+
// CPU **after** the child has already exited and been reaped. The symptom —
|
|
24
|
+
// a live parent with no children, blocked in `poll`, `SNl` — is *identical*
|
|
25
|
+
// to a lost exit callback, which is how it was previously misdiagnosed as
|
|
26
|
+
// "the async `spawn`'s exit/close event is not delivered under the bundle".
|
|
27
|
+
// It IS delivered: instrumenting the exit handler shows it firing with the
|
|
28
|
+
// right code; the parent then parks in the armed loop on its way out.
|
|
29
|
+
//
|
|
30
|
+
// So the branch is NOT "GJS ⇒ spawnSync". It is exactly this:
|
|
31
|
+
//
|
|
32
|
+
// | host | caller ends with | path |
|
|
33
|
+
// |------|------------------------|-------------------------------------|
|
|
34
|
+
// | Node | anything | async `spawn` (streaming) |
|
|
35
|
+
// | GJS | `process.exit(…)` | async `spawn` (streaming) |
|
|
36
|
+
// | GJS | a normal `return` | blocking `spawnSync` (captured) |
|
|
37
|
+
//
|
|
38
|
+
// which is why `gjsify foreach` / `workspace` / `check` and `runGjsBundle` run
|
|
39
|
+
// hundreds of async-spawned children under GJS without ever hanging: every one
|
|
40
|
+
// of them ends its handler with `process.exit(…)`. `gjsify lint` did not, and
|
|
41
|
+
// that is the hang the `spawnSync` branch in `oxc-resolve.ts` was papering
|
|
42
|
+
// over.
|
|
43
|
+
//
|
|
44
|
+
// Callers therefore declare their teardown contract via the REQUIRED
|
|
45
|
+
// {@link SpawnToCompletionOptions.completion} field. It is required on
|
|
46
|
+
// purpose: a new call site must state which side of the table it is on rather
|
|
47
|
+
// than silently inherit a default that may be wrong for it.
|
|
48
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
49
|
+
import { isGjs } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
50
|
+
/** Default capture ceiling on the blocking path — 64 MiB. */
|
|
51
|
+
const DEFAULT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
52
|
+
/**
|
|
53
|
+
* Build the child's environment: `undefined` (inherit) unless the caller
|
|
54
|
+
* passed one or asked for colour seeding.
|
|
55
|
+
*/
|
|
56
|
+
function resolveEnv(opts) {
|
|
57
|
+
if (!opts.color)
|
|
58
|
+
return opts.env;
|
|
59
|
+
const base = opts.env ?? process.env;
|
|
60
|
+
const seed = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
|
|
61
|
+
return { ...base, ...seed };
|
|
62
|
+
}
|
|
63
|
+
/** Turn a spawn-time failure into the caller's domain error when it is ENOENT. */
|
|
64
|
+
function mapSpawnError(err, opts) {
|
|
65
|
+
if (err.code === 'ENOENT' && opts.notFound)
|
|
66
|
+
return opts.notFound(err);
|
|
67
|
+
return err;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether this call has to run on the blocking path.
|
|
71
|
+
*
|
|
72
|
+
* Only true under GJS for a caller that returns normally — see the module
|
|
73
|
+
* header's table. On Node the async path is always correct (and keeps the
|
|
74
|
+
* event loop free), and under GJS a caller that exits the process quits the
|
|
75
|
+
* armed main loop itself.
|
|
76
|
+
*/
|
|
77
|
+
function mustBlock(opts) {
|
|
78
|
+
return isGjs() && opts.completion === 'return';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Run `cmd args…` to completion and resolve with its exit code and signal.
|
|
82
|
+
*
|
|
83
|
+
* Resolves for ANY child exit — a non-zero code is data, not an error, so each
|
|
84
|
+
* call site keeps its own failure message. Rejects only when the child could
|
|
85
|
+
* not be started at all (mapped through {@link SpawnToCompletionOptions.notFound}
|
|
86
|
+
* for ENOENT).
|
|
87
|
+
*
|
|
88
|
+
* Picks the streaming async path or the blocking captured path per the GJS
|
|
89
|
+
* teardown contract documented at the top of this file.
|
|
90
|
+
*/
|
|
91
|
+
export function spawnToCompletion(cmd, args, opts) {
|
|
92
|
+
const env = resolveEnv(opts);
|
|
93
|
+
if (mustBlock(opts)) {
|
|
94
|
+
// `'inherit'` is NOT forwarded through `@gjsify/child_process`'s GJS
|
|
95
|
+
// spawnSync path (it drives a private main context via
|
|
96
|
+
// `communicateWithTimeout`), so the child's diagnostics would be
|
|
97
|
+
// silently dropped. Capture them and re-emit after the child exits.
|
|
98
|
+
// stdin stays inherited so an interactive child still reads the tty.
|
|
99
|
+
const r = spawnSync(cmd, [...args], {
|
|
100
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
101
|
+
cwd: opts.cwd,
|
|
102
|
+
env,
|
|
103
|
+
maxBuffer: opts.maxBuffer ?? DEFAULT_MAX_BUFFER,
|
|
104
|
+
});
|
|
105
|
+
if (r.error)
|
|
106
|
+
return Promise.reject(mapSpawnError(r.error, opts));
|
|
107
|
+
if (r.stdout && r.stdout.length > 0)
|
|
108
|
+
process.stdout.write(r.stdout);
|
|
109
|
+
if (r.stderr && r.stderr.length > 0)
|
|
110
|
+
process.stderr.write(r.stderr);
|
|
111
|
+
return Promise.resolve({ code: r.status, signal: r.signal ?? null });
|
|
112
|
+
}
|
|
113
|
+
return new Promise((resolvePromise, reject) => {
|
|
114
|
+
const spawnOpts = {
|
|
115
|
+
cwd: opts.cwd,
|
|
116
|
+
stdio: opts.stdio === 'pipe' ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
117
|
+
};
|
|
118
|
+
if (env)
|
|
119
|
+
spawnOpts.env = env;
|
|
120
|
+
const child = spawn(cmd, [...args], spawnOpts);
|
|
121
|
+
opts.onSpawn?.(child);
|
|
122
|
+
// `close` (not `exit`) so piped stdio is fully drained before the
|
|
123
|
+
// caller's flushers run. With `stdio: 'inherit'` the two are emitted
|
|
124
|
+
// back to back, so this is a strict superset of the old `exit` sites.
|
|
125
|
+
child.on('close', (code, signal) => {
|
|
126
|
+
resolvePromise({ code, signal });
|
|
127
|
+
});
|
|
128
|
+
child.on('error', (err) => {
|
|
129
|
+
reject(mapSpawnError(err, opts));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format the "child failed" suffix the way the call sites report it: an exit
|
|
135
|
+
* code when the child exited, the signal name when it was killed.
|
|
136
|
+
*/
|
|
137
|
+
export function describeExit(result) {
|
|
138
|
+
return result.signal !== null ? `signal ${result.signal}` : `code ${result.code}`;
|
|
139
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readStdinText(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Read stdin and split it into trimmed, non-empty lines. Accepts LF and CRLF
|
|
21
|
+
* so a `git diff --name-only` piped from any host works unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readStdinLines(): string[];
|
|
24
|
+
/** Pure line-splitter behind {@link readStdinLines} — unit-testable without a pipe. */
|
|
25
|
+
export declare function splitStdinLines(text: string): string[];
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Read ALL of stdin, on Node AND inside the committed GJS bundles.
|
|
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
|
|
9
|
+
//
|
|
10
|
+
// Error: ENOENT: … read '0' (readStdinLines → readFileSync)
|
|
11
|
+
//
|
|
12
|
+
// which silently disabled the ONE fixture-driven entry point AGENTS.md
|
|
13
|
+
// documents for testing the CI classifier (`--changed-from-stdin`).
|
|
14
|
+
//
|
|
15
|
+
// Under GJS we therefore read fd 0 through `GioUnix.InputStream`. Two
|
|
16
|
+
// deliberate choices:
|
|
17
|
+
//
|
|
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`).
|
|
30
|
+
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
|
+
/** The GJS `imports.gi` namespace, or `undefined` when not running on GJS. */
|
|
79
|
+
function giNamespace() {
|
|
80
|
+
try {
|
|
81
|
+
return globalThis.imports?.gi;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read the whole of stdin as UTF-8. Works on Node and under the GJS bundle.
|
|
89
|
+
*
|
|
90
|
+
* Branches on WHICH RUNTIME CAN WORK, not on which capability is present. The
|
|
91
|
+
* distinction matters because the two branches are not interchangeable:
|
|
92
|
+
* `readFileSync(0)` is correct on Node/Bun/Deno and can NEVER work under GJS —
|
|
93
|
+
* `@gjsify/fs` has no numeric-fd path, so the `0` is coerced to a relative
|
|
94
|
+
* PATH and the call opens `./0`. A GJS run that reaches that line is already
|
|
95
|
+
* lost, so using it as GJS's fallback only converts a clear failure into
|
|
96
|
+
* `ENOENT … read '0'`, which is what a capability probe returning a false
|
|
97
|
+
* negative did.
|
|
98
|
+
*
|
|
99
|
+
* So: the presence of `imports.gi` decides. On GJS both GJS readers are tried
|
|
100
|
+
* and a failure THROWS with what was attempted; the Node reader is never
|
|
101
|
+
* reached. Off GJS there is no gi namespace and `readFileSync(0)` is the only
|
|
102
|
+
* and correct answer.
|
|
103
|
+
*/
|
|
104
|
+
export function readStdinText() {
|
|
105
|
+
if (!giNamespace())
|
|
106
|
+
return readFileSync(0, 'utf-8');
|
|
107
|
+
const attempts = [];
|
|
108
|
+
for (const read of [readStdinTextGioUnix, readStdinTextGioFile]) {
|
|
109
|
+
try {
|
|
110
|
+
return read();
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
attempts.push(`${read.name}: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`gjsify: cannot read stdin under GJS. Tried ${attempts.join('; ')}. ` +
|
|
117
|
+
'Pass the input as an argument instead (e.g. --base <ref> rather than --changed-from-stdin).');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read stdin and split it into trimmed, non-empty lines. Accepts LF and CRLF
|
|
121
|
+
* so a `git diff --name-only` piped from any host works unchanged.
|
|
122
|
+
*/
|
|
123
|
+
export function readStdinLines() {
|
|
124
|
+
return splitStdinLines(readStdinText());
|
|
125
|
+
}
|
|
126
|
+
/** Pure line-splitter behind {@link readStdinLines} — unit-testable without a pipe. */
|
|
127
|
+
export function splitStdinLines(text) {
|
|
128
|
+
return text
|
|
129
|
+
.split('\n')
|
|
130
|
+
.map((s) => s.trim())
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -96,7 +96,12 @@
|
|
|
96
96
|
}
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
|
-
"tier": 1
|
|
99
|
+
"tier": 1,
|
|
100
|
+
"defineFromPackageJson": {
|
|
101
|
+
"__PACKAGE_VERSION__": {
|
|
102
|
+
"field": "version"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
100
105
|
},
|
|
101
106
|
"files": [
|
|
102
107
|
"lib",
|
|
@@ -125,19 +130,19 @@
|
|
|
125
130
|
"cli"
|
|
126
131
|
],
|
|
127
132
|
"dependencies": {
|
|
128
|
-
"@gjsify/buffer": "^0.
|
|
129
|
-
"@gjsify/create-app": "^0.
|
|
130
|
-
"@gjsify/node-globals": "^0.
|
|
131
|
-
"@gjsify/node-polyfills": "^0.
|
|
132
|
-
"@gjsify/npm-registry": "^0.
|
|
133
|
-
"@gjsify/resolve-npm": "^0.
|
|
134
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
135
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
136
|
-
"@gjsify/semver": "^0.
|
|
137
|
-
"@gjsify/tar": "^0.
|
|
138
|
-
"@gjsify/tsc": "^0.
|
|
139
|
-
"@gjsify/web-polyfills": "^0.
|
|
140
|
-
"@gjsify/workspace": "^0.
|
|
133
|
+
"@gjsify/buffer": "^0.24.1",
|
|
134
|
+
"@gjsify/create-app": "^0.24.1",
|
|
135
|
+
"@gjsify/node-globals": "^0.24.1",
|
|
136
|
+
"@gjsify/node-polyfills": "^0.24.1",
|
|
137
|
+
"@gjsify/npm-registry": "^0.24.1",
|
|
138
|
+
"@gjsify/resolve-npm": "^0.24.1",
|
|
139
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.24.1",
|
|
140
|
+
"@gjsify/rolldown-plugin-pnp": "^0.24.1",
|
|
141
|
+
"@gjsify/semver": "^0.24.1",
|
|
142
|
+
"@gjsify/tar": "^0.24.1",
|
|
143
|
+
"@gjsify/tsc": "^0.24.1",
|
|
144
|
+
"@gjsify/web-polyfills": "^0.24.1",
|
|
145
|
+
"@gjsify/workspace": "^0.24.1",
|
|
141
146
|
"cosmiconfig": "^9.0.2",
|
|
142
147
|
"get-tsconfig": "^4.14.0",
|
|
143
148
|
"pkg-types": "^2.3.1",
|
|
@@ -145,13 +150,13 @@
|
|
|
145
150
|
"yargs": "^18.0.0"
|
|
146
151
|
},
|
|
147
152
|
"devDependencies": {
|
|
148
|
-
"@gjsify/unit": "^0.
|
|
153
|
+
"@gjsify/unit": "^0.24.1",
|
|
149
154
|
"@types/yargs": "^17.0.35",
|
|
150
155
|
"typescript": "^6.0.3"
|
|
151
156
|
},
|
|
152
157
|
"peerDependencies": {
|
|
153
|
-
"@gjsify/oxfmt-native": "^0.
|
|
154
|
-
"@gjsify/rolldown-native": "^0.
|
|
158
|
+
"@gjsify/oxfmt-native": "^0.24.1",
|
|
159
|
+
"@gjsify/rolldown-native": "^0.24.1"
|
|
155
160
|
},
|
|
156
161
|
"peerDependenciesMeta": {
|
|
157
162
|
"@gjsify/oxfmt-native": {
|