@gjsify/cli 0.23.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 +9 -5
- package/dist/cli.gjs.mjs +93 -85
- package/lib/actions/build.d.ts +12 -0
- package/lib/actions/build.js +26 -0
- package/lib/bundler-pick.js +54 -13
- package/lib/commands/affected.js +62 -4
- package/lib/commands/foreach.js +71 -1
- package/lib/commands/install.js +20 -4
- package/lib/commands/showcase.js +17 -13
- package/lib/commands/storybook.js +5 -2
- package/lib/utils/detect-native-packages.d.ts +26 -21
- package/lib/utils/detect-native-packages.js +53 -41
- package/lib/utils/gjs-bundle-guard.d.ts +32 -0
- package/lib/utils/gjs-bundle-guard.js +103 -0
- package/lib/utils/oxc-resolve.js +7 -2
- 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/package.json +17 -17
|
@@ -22,33 +22,42 @@ import { readdirSync, existsSync } from 'node:fs';
|
|
|
22
22
|
import { join, resolve } from 'node:path';
|
|
23
23
|
import { readPackageJson } from './pkg-json.js';
|
|
24
24
|
/**
|
|
25
|
-
* Map a Node `process.arch` value to the uname-style spelling
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Map a Node `process.arch` value to the uname-style spelling the Vala/meson
|
|
26
|
+
* bridges used to stage into (`prebuilds/linux-x86_64/`, the output of
|
|
27
|
+
* `uname -m` on the build host).
|
|
28
|
+
*
|
|
29
|
+
* LEGACY. Every `@gjsify/*` package now declares and stages the node spelling
|
|
30
|
+
* (`linux-x64`); this table exists only so the CLI can still load a prebuild
|
|
31
|
+
* out of a tarball published before the rename. See
|
|
32
|
+
* {@link prebuildDirCandidates}.
|
|
28
33
|
*/
|
|
29
|
-
const
|
|
34
|
+
const NODE_ARCH_TO_LEGACY_UNAME = {
|
|
30
35
|
x64: 'x86_64',
|
|
31
36
|
arm64: 'aarch64',
|
|
32
37
|
arm: 'armv7',
|
|
33
38
|
ia32: 'i686',
|
|
34
39
|
};
|
|
35
40
|
/**
|
|
36
|
-
* Arch spellings that name the same target
|
|
37
|
-
* `scripts/audit-runtimes.mjs` — that script
|
|
38
|
-
* `package.json#gjsify.platforms` the same way for the
|
|
39
|
-
* matrix, and the two MUST agree or a package could pass the
|
|
40
|
-
* CLI fails to find its artifact.
|
|
41
|
+
* Arch spellings that name the same target, folded onto the NODE spelling.
|
|
42
|
+
* Mirrors `ARCH_ALIASES` in `scripts/audit-runtimes.mjs` — that script
|
|
43
|
+
* canonicalises `package.json#gjsify.platforms` the same way for the
|
|
44
|
+
* platform-support matrix, and the two MUST agree or a package could pass the
|
|
45
|
+
* audit while the CLI fails to find its artifact.
|
|
46
|
+
*
|
|
47
|
+
* The direction matters: `${process.platform}-${process.arch}` is what a
|
|
48
|
+
* running process can compute about itself, so folding *onto* it means the
|
|
49
|
+
* host identity never has to be translated in the hot path.
|
|
41
50
|
*/
|
|
42
51
|
const ARCH_ALIASES = {
|
|
43
|
-
|
|
44
|
-
amd64: '
|
|
45
|
-
|
|
52
|
+
x86_64: 'x64',
|
|
53
|
+
amd64: 'x64',
|
|
54
|
+
aarch64: 'arm64',
|
|
46
55
|
};
|
|
47
56
|
/**
|
|
48
|
-
* Canonical `<os>-<arch>` form
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
57
|
+
* Canonical `<os>-<arch>` form — the node spelling — so `linux-x86_64` and
|
|
58
|
+
* `linux-x64` compare equal. Only the *arch* half is normalised; the OS half
|
|
59
|
+
* is always a `process.platform` token (`linux`/`darwin`/`win32`), which has
|
|
60
|
+
* no competing spelling.
|
|
52
61
|
*/
|
|
53
62
|
export function canonicalPlatformToken(token) {
|
|
54
63
|
const dash = token.indexOf('-');
|
|
@@ -61,36 +70,38 @@ export function canonicalPlatformToken(token) {
|
|
|
61
70
|
/**
|
|
62
71
|
* The prebuild directory names to probe for a host, most-specific first.
|
|
63
72
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
73
|
+
* The canonical name is `${process.platform}-${process.arch}` — `linux-x64`,
|
|
74
|
+
* `linux-arm64`, `darwin-arm64`, `win32-x64`. Every package in the release
|
|
75
|
+
* train declares it (`gjsify.platforms`), `scripts/stage-prebuild.mjs` is the
|
|
76
|
+
* only thing that creates it, and `scripts/audit-runtimes.mjs --check` rejects
|
|
77
|
+
* any other spelling, so the three can no longer drift.
|
|
66
78
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* * `@gjsify/node-gi` (and `@gjsify/napi`'s prebuild staging) use
|
|
70
|
-
* `prebuilds/${process.platform}-${process.arch}/` — node-style
|
|
71
|
-
* (`linux-x64`, `darwin-arm64`, `win32-x64`).
|
|
79
|
+
* Two probes remain BEHIND that canonical name, and both are backward
|
|
80
|
+
* compatibility with tarballs already on npm — not a second convention:
|
|
72
81
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
82
|
+
* 1. **The package's own declared spelling.** A tarball published before the
|
|
83
|
+
* rename declares (and ships) `linux-x86_64`; probing its declaration
|
|
84
|
+
* first loads it without the CLI having to guess. This is also what makes
|
|
85
|
+
* the rename work in the OTHER direction — an older CLI resolving a newer
|
|
86
|
+
* package — because `canonicalPlatformToken` folds both spellings onto
|
|
87
|
+
* one form at both ends.
|
|
88
|
+
* 2. **The legacy uname spelling.** Only reachable for a package that ships
|
|
89
|
+
* a prebuild dir and declares NO `gjsify.platforms` at all — i.e. a
|
|
90
|
+
* tarball predating the OS-axis audit, or a third-party package using
|
|
91
|
+
* `gjsify.prebuilds` without the declaration. Inside this repo the audit
|
|
92
|
+
* makes that state impossible.
|
|
77
93
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* (`darwin-arm64`, `win32-x64` — on Apple silicon `uname -m` also reports
|
|
82
|
-
* `arm64`, so there node-style *is* the native spelling).
|
|
94
|
+
* Both are pure array entries matched against an already-read directory
|
|
95
|
+
* listing — no extra I/O — so tolerance on the READ side costs nothing, while
|
|
96
|
+
* the single spelling is enforced everywhere a name is WRITTEN.
|
|
83
97
|
*
|
|
84
98
|
* @param platform `process.platform` value.
|
|
85
99
|
* @param arch `process.arch` value.
|
|
86
100
|
* @param declaredPlatforms `package.json#gjsify.platforms`, when present.
|
|
87
101
|
*/
|
|
88
102
|
export function prebuildDirCandidates(platform, arch, declaredPlatforms) {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const alternate = platform === 'linux' ? arch : unameArch;
|
|
92
|
-
const generic = [`${platform}-${preferred}`, `${platform}-${alternate}`];
|
|
93
|
-
const hostCanonical = canonicalPlatformToken(generic[0]);
|
|
103
|
+
const canonical = `${platform}-${arch}`;
|
|
104
|
+
const legacyArch = NODE_ARCH_TO_LEGACY_UNAME[arch] ?? arch;
|
|
94
105
|
const out = [];
|
|
95
106
|
const push = (name) => {
|
|
96
107
|
if (!out.includes(name))
|
|
@@ -100,12 +111,13 @@ export function prebuildDirCandidates(platform, arch, declaredPlatforms) {
|
|
|
100
111
|
for (const declared of declaredPlatforms ?? []) {
|
|
101
112
|
if (typeof declared !== 'string')
|
|
102
113
|
continue;
|
|
103
|
-
if (canonicalPlatformToken(declared) ===
|
|
114
|
+
if (canonicalPlatformToken(declared) === canonical)
|
|
104
115
|
push(declared);
|
|
105
116
|
}
|
|
106
|
-
// 2. The
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
// 2. The canonical node spelling — what everything in the train stages.
|
|
118
|
+
push(canonical);
|
|
119
|
+
// 3. The legacy uname spelling, for undeclared pre-rename tarballs.
|
|
120
|
+
push(`${platform}-${legacyArch}`);
|
|
109
121
|
return out;
|
|
110
122
|
}
|
|
111
123
|
/**
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The one bundler-output field this guard needs, from either engine. */
|
|
2
|
+
export interface GuardedChunk {
|
|
3
|
+
/** Emitted chunk name, for the error message. */
|
|
4
|
+
fileName?: string;
|
|
5
|
+
/** External specifiers the chunk STATICALLY imports (rollup/rolldown contract). */
|
|
6
|
+
imports?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Bare `node:` specifiers a `--app gjs` bundle may keep as static imports.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately EMPTY. Every Node builtin has an `@gjsify/*` polyfill that
|
|
12
|
+
* `--app gjs` aliases to, so there is no specifier that both survives into the
|
|
13
|
+
* module graph and loads under GJS. Reading the graph instead of the text is
|
|
14
|
+
* what keeps this list empty — the entries an earlier text-scanning version
|
|
15
|
+
* needed (`node:module`, `node:process`) were quoted lookalikes, never imports.
|
|
16
|
+
*
|
|
17
|
+
* Add an entry ONLY with a comment naming why GJS can resolve it. Each one is a
|
|
18
|
+
* specifier this guard can no longer catch for real.
|
|
19
|
+
*/
|
|
20
|
+
export declare const GJS_ALLOWED_NODE_IMPORTS: readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* The distinct bare `node:` specifiers `chunks` statically import, minus the
|
|
23
|
+
* allowlist. Sorted, so the error message and any test are stable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findDisallowedNodeImports(chunks: readonly GuardedChunk[], allowed?: readonly string[]): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Throw when a built `--app gjs` bundle statically imports Node builtins.
|
|
28
|
+
*
|
|
29
|
+
* @param chunks the emitted chunks, as reported by the bundler
|
|
30
|
+
* @param outfile path of the bundle, for the error message
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertGjsBundleLoadable(chunks: readonly GuardedChunk[], outfile: string): void;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Build-time guard: a `--app gjs` bundle must not keep bare `node:` imports.
|
|
2
|
+
//
|
|
3
|
+
// Stock GJS has NO `node:` module loader. A single surviving
|
|
4
|
+
// `import … from "node:fs"` makes it abort the ENTIRE module graph before one
|
|
5
|
+
// line executes:
|
|
6
|
+
//
|
|
7
|
+
// Gjs-WARNING **: JS ERROR: ImportError: Unsupported URI scheme for importing: node
|
|
8
|
+
// Gjs-CRITICAL **: Failed to resolve imports for module: '…/dist/cli.gjs.mjs'
|
|
9
|
+
//
|
|
10
|
+
// `--app gjs` maps every Node builtin to its `@gjsify/*` polyfill, so a healthy
|
|
11
|
+
// bundle has ZERO of them. But when an alias TARGET cannot be resolved — the
|
|
12
|
+
// classic case being a workspace checkout whose `@gjsify/*` packages have no
|
|
13
|
+
// built `lib/`, so `exports["."]` points at a file that does not exist — the
|
|
14
|
+
// bundler falls back to treating the import as external and re-emits the
|
|
15
|
+
// ORIGINAL `node:*` specifier. That is a WARNING, not an error: the build
|
|
16
|
+
// "succeeds" and writes a bundle that cannot load anywhere. It cost three red
|
|
17
|
+
// CI jobs to notice, because nothing between `gjsify build` and CI ever
|
|
18
|
+
// executes the emitted GJS bundle (tsc, lint, format and the unit/e2e suites
|
|
19
|
+
// all run the Node entry).
|
|
20
|
+
//
|
|
21
|
+
// The pre-commit hook makes it worse rather than better: it rebuilds and
|
|
22
|
+
// AUTO-STAGES `dist/cli.gjs.mjs`, so a developer whose checkout cannot resolve
|
|
23
|
+
// the polyfills commits a broken artifact without ever seeing it.
|
|
24
|
+
//
|
|
25
|
+
// So the refusal belongs at build time, in `gjsify build --app gjs` itself —
|
|
26
|
+
// not in a git hook and not in one package's `build:gjs-bundle` script. The
|
|
27
|
+
// invariant is a property of the ARTIFACT, and every showcase, every downstream
|
|
28
|
+
// app (buchhaltung, bauplaner, Learn6502) and CI all produce it through this
|
|
29
|
+
// one code path. A hook would protect only commits in this repo; a per-package
|
|
30
|
+
// script would protect one package.
|
|
31
|
+
//
|
|
32
|
+
// ## The oracle is the module graph, NOT the emitted text
|
|
33
|
+
//
|
|
34
|
+
// The first cut of this guard grepped the output for `from "node:…"`. That is
|
|
35
|
+
// wrong, and CI caught it: `@gjsify/process`'s own test bundle names a case
|
|
36
|
+
//
|
|
37
|
+
// await it(`named-import hrtime (from "node:process") preserves .bigint`, …)
|
|
38
|
+
//
|
|
39
|
+
// so the phrase appears verbatim inside a STRING, in a bundle that loads and
|
|
40
|
+
// runs under GJS perfectly well. The same trap sits in `@gjsify/cli`'s own
|
|
41
|
+
// bundle, which embeds `giNodeShimSource()`'s `--app node` codegen template
|
|
42
|
+
// (`import { createRequire } from 'node:module';`). Telling a statement from a
|
|
43
|
+
// quoted lookalike needs a JS lexer — but the bundler already did that work:
|
|
44
|
+
// every chunk carries the list of external specifiers it actually imports. That
|
|
45
|
+
// list is exact, engine-independent (npm `rolldown` returns it directly,
|
|
46
|
+
// `@gjsify/rolldown-native` forwards it through `synthRolldownOutput`), and
|
|
47
|
+
// free.
|
|
48
|
+
//
|
|
49
|
+
// Scope is STATIC imports, which is precisely the failure mode above: they are
|
|
50
|
+
// resolved when GJS links the module, so one bad specifier kills the whole
|
|
51
|
+
// bundle. A dynamic `import('node:x')` throws lazily at its call site instead —
|
|
52
|
+
// a real bug, but not "the artifact cannot load", and not what this gate is
|
|
53
|
+
// for.
|
|
54
|
+
/**
|
|
55
|
+
* Bare `node:` specifiers a `--app gjs` bundle may keep as static imports.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately EMPTY. Every Node builtin has an `@gjsify/*` polyfill that
|
|
58
|
+
* `--app gjs` aliases to, so there is no specifier that both survives into the
|
|
59
|
+
* module graph and loads under GJS. Reading the graph instead of the text is
|
|
60
|
+
* what keeps this list empty — the entries an earlier text-scanning version
|
|
61
|
+
* needed (`node:module`, `node:process`) were quoted lookalikes, never imports.
|
|
62
|
+
*
|
|
63
|
+
* Add an entry ONLY with a comment naming why GJS can resolve it. Each one is a
|
|
64
|
+
* specifier this guard can no longer catch for real.
|
|
65
|
+
*/
|
|
66
|
+
export const GJS_ALLOWED_NODE_IMPORTS = [];
|
|
67
|
+
/**
|
|
68
|
+
* The distinct bare `node:` specifiers `chunks` statically import, minus the
|
|
69
|
+
* allowlist. Sorted, so the error message and any test are stable.
|
|
70
|
+
*/
|
|
71
|
+
export function findDisallowedNodeImports(chunks, allowed = GJS_ALLOWED_NODE_IMPORTS) {
|
|
72
|
+
const found = new Set();
|
|
73
|
+
for (const chunk of chunks) {
|
|
74
|
+
for (const specifier of chunk.imports ?? []) {
|
|
75
|
+
if (!specifier.startsWith('node:'))
|
|
76
|
+
continue;
|
|
77
|
+
if (allowed.includes(specifier))
|
|
78
|
+
continue;
|
|
79
|
+
found.add(specifier);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [...found].sort();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Throw when a built `--app gjs` bundle statically imports Node builtins.
|
|
86
|
+
*
|
|
87
|
+
* @param chunks the emitted chunks, as reported by the bundler
|
|
88
|
+
* @param outfile path of the bundle, for the error message
|
|
89
|
+
*/
|
|
90
|
+
export function assertGjsBundleLoadable(chunks, outfile) {
|
|
91
|
+
const offenders = findDisallowedNodeImports(chunks);
|
|
92
|
+
if (offenders.length === 0)
|
|
93
|
+
return;
|
|
94
|
+
throw new Error(`gjsify build --app gjs: ${outfile} still imports ${offenders.length} bare \`node:\` module(s), ` +
|
|
95
|
+
'which stock GJS cannot resolve — it would fail at load with ' +
|
|
96
|
+
'"ImportError: Unsupported URI scheme for importing: node".\n' +
|
|
97
|
+
offenders.map((s) => ` - ${s}`).join('\n') +
|
|
98
|
+
'\n\nThese should have been aliased to their `@gjsify/*` polyfills. The usual cause is that the ' +
|
|
99
|
+
'alias TARGET could not be resolved, so the bundler externalised the import instead (look for ' +
|
|
100
|
+
'UNRESOLVED_IMPORT warnings above): e.g. a workspace checkout whose `@gjsify/*` packages have no ' +
|
|
101
|
+
'built `lib/`. Build the workspace first (`gjsify run build:infra`), or fix the resolution, then ' +
|
|
102
|
+
'rebuild.');
|
|
103
|
+
}
|
package/lib/utils/oxc-resolve.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
// lives in the Node launcher, so a native oxlint bridge could only run the
|
|
47
47
|
// Rust rule subset. Lint stays Node-spawned until that trade-off is decided
|
|
48
48
|
// (tracked in STATUS.md Open TODOs).
|
|
49
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
49
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
50
50
|
import { join, resolve } from 'node:path';
|
|
51
51
|
import { createRequire } from 'node:module';
|
|
52
52
|
import { pathToFileURL } from 'node:url';
|
|
@@ -89,7 +89,12 @@ function oxcBindingSuffix() {
|
|
|
89
89
|
// glibc systems have `/lib/ld-linux-*`, musl has `/lib/ld-musl-*`.
|
|
90
90
|
let libc = 'gnu';
|
|
91
91
|
try {
|
|
92
|
-
|
|
92
|
+
// `readdirSync` is imported STATICALLY at the top of this file. It
|
|
93
|
+
// used to be a lazy `require('node:fs')` inside this try — a bare
|
|
94
|
+
// `require` in an ESM module, so it threw ReferenceError, the catch
|
|
95
|
+
// below swallowed it, and `libc` silently stayed 'gnu': a musl host
|
|
96
|
+
// would be told to install the WRONG binding package. Same defect
|
|
97
|
+
// class as commands/affected.ts, and even quieter.
|
|
93
98
|
const libEntries = readdirSync('/lib');
|
|
94
99
|
if (libEntries.some((e) => e.startsWith('ld-musl-'))) {
|
|
95
100
|
libc = 'musl';
|
package/lib/utils/run-node.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ import { type ExampleRuntime } from './runtimes.js';
|
|
|
6
6
|
* not available under the GJS-bundled CLI.
|
|
7
7
|
*/
|
|
8
8
|
export declare function resolveNodeGi(startDir: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
|
|
11
|
+
* BUNDLE's own directory first, then from `cwd`.
|
|
12
|
+
*
|
|
13
|
+
* The bundle's directory is the correct base and cwd is the fallback, for the
|
|
14
|
+
* same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
|
|
15
|
+
* kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
|
|
16
|
+
* bare specifier from inside the bundle — which Node performs relative to the
|
|
17
|
+
* importing file, never relative to cwd. A dlx-cache layout
|
|
18
|
+
* (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
|
|
19
|
+
* sitting right beside the bundle and nothing at all under the user's cwd, so a
|
|
20
|
+
* cwd-only probe rejected a run that would have worked. The cwd probe is kept
|
|
21
|
+
* because a project-local `node_modules` legitimately satisfies a bundle built
|
|
22
|
+
* into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveNodeGiForBundle(bundlePath: string, cwd: string): string | null;
|
|
9
25
|
/** Pick the node executable: the current one when on Node, else PATH `node`. */
|
|
10
26
|
export declare function nodeBinary(): string;
|
|
11
27
|
export interface RunNodeBundleOptions {
|
package/lib/utils/run-node.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
// through `@gjsify/node-gi` (the Axis-5 reverse bridge). Two things have to be
|
|
6
6
|
// true at runtime:
|
|
7
7
|
//
|
|
8
|
-
// 1. `@gjsify/node-gi` must resolve
|
|
9
|
-
// the bundler (a native addon), so it has to physically exist in
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// 1. `@gjsify/node-gi` must resolve FROM THE BUNDLE — it is kept EXTERNAL by
|
|
9
|
+
// the bundler (a native addon), so it has to physically exist in a
|
|
10
|
+
// node_modules the bundle's own directory can reach (that is where Node
|
|
11
|
+
// will look), with the CWD as a fallback. We verify this up front and emit
|
|
12
|
+
// a clear hint instead of letting Node throw `ERR_MODULE_NOT_FOUND` deep in
|
|
13
|
+
// the bundle. See `resolveNodeGiForBundle`.
|
|
12
14
|
// 2. `gi://Gtk` / `gi://Adw` etc. must resolve. System typelibs come from the
|
|
13
15
|
// default GI search path, but any `@gjsify/*` Vala bridge a story pulls in
|
|
14
16
|
// ships its typelib in `prebuilds/` — so we reuse the SAME
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
// `gjs`, which cannot run a `--app node` bundle).
|
|
22
24
|
import { spawn } from 'node:child_process';
|
|
23
25
|
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
-
import { join, resolve } from 'node:path';
|
|
26
|
+
import { dirname, join, resolve } from 'node:path';
|
|
25
27
|
import { isNode } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
26
28
|
import { computeNativeEnvForBundle } from './run-gjs.js';
|
|
27
29
|
import { RUNTIMES } from './runtimes.js';
|
|
@@ -43,6 +45,25 @@ export function resolveNodeGi(startDir) {
|
|
|
43
45
|
dir = parent;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
|
|
50
|
+
* BUNDLE's own directory first, then from `cwd`.
|
|
51
|
+
*
|
|
52
|
+
* The bundle's directory is the correct base and cwd is the fallback, for the
|
|
53
|
+
* same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
|
|
54
|
+
* kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
|
|
55
|
+
* bare specifier from inside the bundle — which Node performs relative to the
|
|
56
|
+
* importing file, never relative to cwd. A dlx-cache layout
|
|
57
|
+
* (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
|
|
58
|
+
* sitting right beside the bundle and nothing at all under the user's cwd, so a
|
|
59
|
+
* cwd-only probe rejected a run that would have worked. The cwd probe is kept
|
|
60
|
+
* because a project-local `node_modules` legitimately satisfies a bundle built
|
|
61
|
+
* into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
|
|
62
|
+
*/
|
|
63
|
+
export function resolveNodeGiForBundle(bundlePath, cwd) {
|
|
64
|
+
const resolvedBundle = resolve(bundlePath);
|
|
65
|
+
return resolveNodeGi(dirname(resolvedBundle)) ?? resolveNodeGi(cwd);
|
|
66
|
+
}
|
|
46
67
|
/** Pick the node executable: the current one when on Node, else PATH `node`. */
|
|
47
68
|
export function nodeBinary() {
|
|
48
69
|
return isNode() && process.execPath ? process.execPath : 'node';
|
|
@@ -55,7 +76,7 @@ export function nodeBinary() {
|
|
|
55
76
|
*/
|
|
56
77
|
export async function runNodeBundle(bundlePath, extraArgs = [], options = {}) {
|
|
57
78
|
const cwd = process.cwd();
|
|
58
|
-
if (!
|
|
79
|
+
if (!resolveNodeGiForBundle(bundlePath, cwd)) {
|
|
59
80
|
throw new Error('Cannot run the storybook on Node: `@gjsify/node-gi` is not installed.\n' +
|
|
60
81
|
'Add @gjsify/node-gi as a dependency to run the storybook on Node, e.g.:\n' +
|
|
61
82
|
' "devDependencies": { "@gjsify/node-gi": "^0.13.0" }');
|
|
@@ -113,7 +134,7 @@ export function bundleRequiresNodeGi(bundlePath) {
|
|
|
113
134
|
export async function runRuntimeBundle(runtime, bundlePath, extraArgs = [], options = {}) {
|
|
114
135
|
const cwd = process.cwd();
|
|
115
136
|
const resolvedBundle = resolve(bundlePath);
|
|
116
|
-
if (bundleRequiresNodeGi(resolvedBundle) && !
|
|
137
|
+
if (bundleRequiresNodeGi(resolvedBundle) && !resolveNodeGiForBundle(resolvedBundle, cwd)) {
|
|
117
138
|
throw new Error(`Cannot run this bundle on ${runtime}: it uses gi:// via \`@gjsify/node-gi\`, which is not installed.\n` +
|
|
118
139
|
'Add @gjsify/node-gi as a dependency to run gi:// code off GJS, e.g.:\n' +
|
|
119
140
|
' "dependencies": { "@gjsify/node-gi": "^0.13.0" }');
|
package/lib/utils/runtimes.d.ts
CHANGED
|
@@ -37,6 +37,27 @@ export declare function isRuntimeOnPath(probe: string): boolean;
|
|
|
37
37
|
export declare function isRuntimeAvailable(runtime: ExampleRuntime): boolean;
|
|
38
38
|
/** Which of gjs/node/bun/deno are runnable on this host. */
|
|
39
39
|
export declare function availableRuntimes(): ExampleRuntime[];
|
|
40
|
+
/**
|
|
41
|
+
* The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
|
|
42
|
+
* whenever gjs is runnable here, else the host runtime.
|
|
43
|
+
*
|
|
44
|
+
* This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
|
|
45
|
+
* artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
|
|
46
|
+
* showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
|
|
47
|
+
* docs call the default. Following the host instead means `npx @gjsify/cli
|
|
48
|
+
* showcase <name>` (host = node) silently asks for the `--app node` bundle,
|
|
49
|
+
* which most showcases do not ship and which additionally needs
|
|
50
|
+
* `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
|
|
51
|
+
* the primary documented entry point failed on a missing file.
|
|
52
|
+
*
|
|
53
|
+
* Same rule `gjsify run` already applies in its bare-file path, where a
|
|
54
|
+
* `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
|
|
55
|
+
* here the "is it a gjs artifact" question is answered by the command instead
|
|
56
|
+
* of by sniffing the file. `--runtime` still overrides explicitly, and a host
|
|
57
|
+
* WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
|
|
58
|
+
* node-capable showcases stay runnable there.
|
|
59
|
+
*/
|
|
60
|
+
export declare function defaultExampleRuntime(): ExampleRuntime;
|
|
40
61
|
interface ExampleGjsifyField {
|
|
41
62
|
example?: {
|
|
42
63
|
runtimes?: unknown;
|
package/lib/utils/runtimes.js
CHANGED
|
@@ -64,6 +64,29 @@ export function isRuntimeAvailable(runtime) {
|
|
|
64
64
|
export function availableRuntimes() {
|
|
65
65
|
return EXAMPLE_RUNTIMES.filter((rt) => isRuntimeAvailable(rt));
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
|
|
69
|
+
* whenever gjs is runnable here, else the host runtime.
|
|
70
|
+
*
|
|
71
|
+
* This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
|
|
72
|
+
* artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
|
|
73
|
+
* showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
|
|
74
|
+
* docs call the default. Following the host instead means `npx @gjsify/cli
|
|
75
|
+
* showcase <name>` (host = node) silently asks for the `--app node` bundle,
|
|
76
|
+
* which most showcases do not ship and which additionally needs
|
|
77
|
+
* `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
|
|
78
|
+
* the primary documented entry point failed on a missing file.
|
|
79
|
+
*
|
|
80
|
+
* Same rule `gjsify run` already applies in its bare-file path, where a
|
|
81
|
+
* `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
|
|
82
|
+
* here the "is it a gjs artifact" question is answered by the command instead
|
|
83
|
+
* of by sniffing the file. `--runtime` still overrides explicitly, and a host
|
|
84
|
+
* WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
|
|
85
|
+
* node-capable showcases stay runnable there.
|
|
86
|
+
*/
|
|
87
|
+
export function defaultExampleRuntime() {
|
|
88
|
+
return isRuntimeAvailable('gjs') ? 'gjs' : hostRuntime();
|
|
89
|
+
}
|
|
67
90
|
/**
|
|
68
91
|
* Read a package's declared example runtimes. Returns the (validated) list, or
|
|
69
92
|
* `null` when the package declares none — the "unconstrained / permissive" case.
|
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",
|
|
@@ -130,19 +130,19 @@
|
|
|
130
130
|
"cli"
|
|
131
131
|
],
|
|
132
132
|
"dependencies": {
|
|
133
|
-
"@gjsify/buffer": "^0.
|
|
134
|
-
"@gjsify/create-app": "^0.
|
|
135
|
-
"@gjsify/node-globals": "^0.
|
|
136
|
-
"@gjsify/node-polyfills": "^0.
|
|
137
|
-
"@gjsify/npm-registry": "^0.
|
|
138
|
-
"@gjsify/resolve-npm": "^0.
|
|
139
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
140
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
141
|
-
"@gjsify/semver": "^0.
|
|
142
|
-
"@gjsify/tar": "^0.
|
|
143
|
-
"@gjsify/tsc": "^0.
|
|
144
|
-
"@gjsify/web-polyfills": "^0.
|
|
145
|
-
"@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",
|
|
146
146
|
"cosmiconfig": "^9.0.2",
|
|
147
147
|
"get-tsconfig": "^4.14.0",
|
|
148
148
|
"pkg-types": "^2.3.1",
|
|
@@ -150,13 +150,13 @@
|
|
|
150
150
|
"yargs": "^18.0.0"
|
|
151
151
|
},
|
|
152
152
|
"devDependencies": {
|
|
153
|
-
"@gjsify/unit": "^0.
|
|
153
|
+
"@gjsify/unit": "^0.24.1",
|
|
154
154
|
"@types/yargs": "^17.0.35",
|
|
155
155
|
"typescript": "^6.0.3"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@gjsify/oxfmt-native": "^0.
|
|
159
|
-
"@gjsify/rolldown-native": "^0.
|
|
158
|
+
"@gjsify/oxfmt-native": "^0.24.1",
|
|
159
|
+
"@gjsify/rolldown-native": "^0.24.1"
|
|
160
160
|
},
|
|
161
161
|
"peerDependenciesMeta": {
|
|
162
162
|
"@gjsify/oxfmt-native": {
|