@gjsify/cli 0.24.2 → 0.25.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/dist/cli.gjs.mjs +188 -186
- package/lib/actions/build.d.ts +13 -0
- package/lib/actions/build.js +57 -11
- package/lib/commands/showcase.js +13 -2
- package/lib/utils/node-bundle-guard.d.ts +16 -0
- package/lib/utils/node-bundle-guard.js +69 -0
- package/package.json +17 -17
package/lib/actions/build.d.ts
CHANGED
|
@@ -60,6 +60,19 @@ export declare class BuildAction {
|
|
|
60
60
|
* text scan cannot tell from a statement.
|
|
61
61
|
*/
|
|
62
62
|
private assertGjsOutputLoadable;
|
|
63
|
+
/**
|
|
64
|
+
* Post-bundle gate for `--app node`: throw when the emitted bundle still
|
|
65
|
+
* references GJS ambient globals (`print`/`imports`/…) although the
|
|
66
|
+
* pre-build detection decided no `@gjsify/node-gi/globals` shim was needed
|
|
67
|
+
* — see `utils/node-bundle-guard.ts` for the full rationale.
|
|
68
|
+
*
|
|
69
|
+
* Unlike the `--app gjs` gate, the oracle here CANNOT be the module graph:
|
|
70
|
+
* an ambient global is a free identifier, not an import, so nothing about it
|
|
71
|
+
* appears in a chunk's `imports` list. It is the same acorn pass the
|
|
72
|
+
* detector runs, re-applied to what was actually written — a post-condition
|
|
73
|
+
* on the prediction, not a second opinion.
|
|
74
|
+
*/
|
|
75
|
+
private assertNodeOutputGlobals;
|
|
63
76
|
/**
|
|
64
77
|
* Post-bundle step for a `--app browser` HTML entry: write the processed
|
|
65
78
|
* `index.html` beside the JS bundle, with the entry `<script>`'s `src`
|
package/lib/actions/build.js
CHANGED
|
@@ -11,6 +11,7 @@ import { normalizeBundlerOptions, mergeBundlerOptions } from '../utils/normalize
|
|
|
11
11
|
import { inputSourceDirs, isOutdirInsideSource, libraryOutputLeakError } from '../utils/library-output.js';
|
|
12
12
|
import { detectHtmlEntry, parseHtmlEntry, emitBrowserHtml, htmlOutPathFor } from '../utils/html-entry.js';
|
|
13
13
|
import { assertGjsBundleLoadable } from '../utils/gjs-bundle-guard.js';
|
|
14
|
+
import { assertNodeBundleGlobalsShimmed } from '../utils/node-bundle-guard.js';
|
|
14
15
|
const DEFAULT_GJS_SHEBANG = '#!/usr/bin/env -S gjs -m';
|
|
15
16
|
/**
|
|
16
17
|
* `true` when `path` points at a location that's unsafe to use as a build
|
|
@@ -291,6 +292,31 @@ export class BuildAction {
|
|
|
291
292
|
const chunks = (result.output ?? []).filter((item) => item.type === 'chunk');
|
|
292
293
|
assertGjsBundleLoadable(chunks, outfile ?? outdir ?? 'the GJS bundle');
|
|
293
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Post-bundle gate for `--app node`: throw when the emitted bundle still
|
|
297
|
+
* references GJS ambient globals (`print`/`imports`/…) although the
|
|
298
|
+
* pre-build detection decided no `@gjsify/node-gi/globals` shim was needed
|
|
299
|
+
* — see `utils/node-bundle-guard.ts` for the full rationale.
|
|
300
|
+
*
|
|
301
|
+
* Unlike the `--app gjs` gate, the oracle here CANNOT be the module graph:
|
|
302
|
+
* an ambient global is a free identifier, not an import, so nothing about it
|
|
303
|
+
* appears in a chunk's `imports` list. It is the same acorn pass the
|
|
304
|
+
* detector runs, re-applied to what was actually written — a post-condition
|
|
305
|
+
* on the prediction, not a second opinion.
|
|
306
|
+
*/
|
|
307
|
+
assertNodeOutputGlobals(result, outfile, outdir) {
|
|
308
|
+
const label = outfile ?? outdir ?? 'the node bundle';
|
|
309
|
+
for (const item of result.output ?? []) {
|
|
310
|
+
if (item.type !== 'chunk')
|
|
311
|
+
continue;
|
|
312
|
+
// `code` is present on a written chunk for both engines; skip
|
|
313
|
+
// defensively rather than fail a build over a missing field.
|
|
314
|
+
const code = item.code;
|
|
315
|
+
if (typeof code !== 'string' || code.length === 0)
|
|
316
|
+
continue;
|
|
317
|
+
assertNodeBundleGlobalsShimmed(code, item.fileName ? `${label} (${item.fileName})` : label);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
294
320
|
/**
|
|
295
321
|
* Post-bundle step for a `--app browser` HTML entry: write the processed
|
|
296
322
|
* `index.html` beside the JS bundle, with the entry `<script>`'s `src`
|
|
@@ -530,6 +556,30 @@ export class BuildAction {
|
|
|
530
556
|
// detection must run BEFORE the final build so the entry can be
|
|
531
557
|
// wrapped; nothing is injected when no globals are referenced (the
|
|
532
558
|
// eager-native-load regression guard).
|
|
559
|
+
//
|
|
560
|
+
// Explicit extras on a node build (`--globals auto,dom,…`) are the
|
|
561
|
+
// reverse bridge's DOM-surface request: inject the SAME register
|
|
562
|
+
// modules the gjs target would for those identifiers, so a genuine
|
|
563
|
+
// GJS source (an Excalibur/WebGLBridge app) gets document /
|
|
564
|
+
// HTMLCanvasElement / matchMedia / … on Node via node-gi. The
|
|
565
|
+
// orchestrator lifts the `@gjsify/empty` register routing only when
|
|
566
|
+
// this inject stub is present (see app/node.ts) — plain `auto`
|
|
567
|
+
// node builds are byte-unchanged.
|
|
568
|
+
//
|
|
569
|
+
// The stub is resolved BEFORE the detection pass, and that ordering
|
|
570
|
+
// is load-bearing: it is one of the two signals `isGjsSourceBuild`
|
|
571
|
+
// reads, so it decides whether `/register` subpaths resolve for real
|
|
572
|
+
// or route to `@gjsify/empty` and whether `@girs/*` bodies survive.
|
|
573
|
+
// Resolving it afterwards made the analysis bundle a DIFFERENT
|
|
574
|
+
// module graph from the one actually emitted — every GJS ambient
|
|
575
|
+
// global reachable only THROUGH a register module was invisible, so
|
|
576
|
+
// the shim was not injected and the emitted bundle threw
|
|
577
|
+
// `ReferenceError: imports is not defined` at runtime (the
|
|
578
|
+
// excalibur-jelly-jumper showcase on node/bun, via
|
|
579
|
+
// `@gjsify/canvas2d-core`'s `_toDataURL` and `@gjsify/utils`).
|
|
580
|
+
if (extras) {
|
|
581
|
+
pluginOpts.autoGlobalsInject = await this.resolveGlobalsInject(app, extras, verbose);
|
|
582
|
+
}
|
|
533
583
|
const gjsifyPluginFactory = async (opts) => {
|
|
534
584
|
const cfg = await gjsifyPlugin({
|
|
535
585
|
input: userBundler.input,
|
|
@@ -550,17 +600,6 @@ export class BuildAction {
|
|
|
550
600
|
}, pluginOpts, gjsifyPluginFactory, bundleToChunks);
|
|
551
601
|
if (needsGiGlobals)
|
|
552
602
|
pluginOpts.nodeGiGlobalsInject = true;
|
|
553
|
-
// Explicit extras on a node build (`--globals auto,dom,…`) are the
|
|
554
|
-
// reverse bridge's DOM-surface request: inject the SAME register
|
|
555
|
-
// modules the gjs target would for those identifiers, so a genuine
|
|
556
|
-
// GJS source (an Excalibur/WebGLBridge app) gets document /
|
|
557
|
-
// HTMLCanvasElement / matchMedia / … on Node via node-gi. The
|
|
558
|
-
// orchestrator lifts the `@gjsify/empty` register routing only when
|
|
559
|
-
// this inject stub is present (see app/node.ts) — plain `auto`
|
|
560
|
-
// node builds are byte-unchanged.
|
|
561
|
-
if (extras) {
|
|
562
|
-
pluginOpts.autoGlobalsInject = await this.resolveGlobalsInject(app, extras, verbose);
|
|
563
|
-
}
|
|
564
603
|
}
|
|
565
604
|
else if (extras) {
|
|
566
605
|
pluginOpts.autoGlobalsInject = await this.resolveGlobalsInject(app, extras, verbose);
|
|
@@ -596,6 +635,13 @@ export class BuildAction {
|
|
|
596
635
|
if (app === 'gjs') {
|
|
597
636
|
this.assertGjsOutputLoadable(writeResult, outfile, outdir);
|
|
598
637
|
}
|
|
638
|
+
// Refuse to hand back a `--app node` bundle that reaches for GJS ambient
|
|
639
|
+
// globals nothing seeds. Only meaningful when the shim was NOT injected:
|
|
640
|
+
// with it, the globals are installed and the references are correct.
|
|
641
|
+
// `--globals none` opts out of the mechanism, so it opts out of the gate.
|
|
642
|
+
if (app === 'node' && autoMode && !pluginOpts.nodeGiGlobalsInject) {
|
|
643
|
+
this.assertNodeOutputGlobals(writeResult, outfile, outdir);
|
|
644
|
+
}
|
|
599
645
|
// Browser HTML entry: emit the processed `index.html` beside the bundle
|
|
600
646
|
// (engine-portable post-step — the native rolldown engine has no
|
|
601
647
|
// `emitFile`, so this can't live in a plugin). `outfile` is guaranteed
|
package/lib/commands/showcase.js
CHANGED
|
@@ -266,6 +266,17 @@ async function runShowcaseOnRuntime(showcase, runtime, cliVersion) {
|
|
|
266
266
|
return process.exit(1);
|
|
267
267
|
}
|
|
268
268
|
console.log(`Running showcase: ${showcase.name} ${runningAs(cliVersion)}on ${runtime} (${entry})\n`);
|
|
269
|
-
// Terminal call — exit on success.
|
|
270
|
-
|
|
269
|
+
// Terminal call — exit on success. Failures are reported here rather than
|
|
270
|
+
// thrown: an exception escaping the handler makes yargs dump the command's
|
|
271
|
+
// full `--help` above the message, which buries it.
|
|
272
|
+
try {
|
|
273
|
+
await runRuntimeBundle(runtime, entry, [], { exitOnSuccess: true });
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
console.error(`${err.message}\n\n` +
|
|
277
|
+
` The showcase and the \`@gjsify/node-gi\` bridge it needs are both installed by THIS CLI\n` +
|
|
278
|
+
` (gjsify ${cliVersion ?? '(unknown)'}) into its own cache, so a missing bridge points at the CLI\n` +
|
|
279
|
+
` rather than at anything in your project.\n\n ${PIN_HINT}`);
|
|
280
|
+
return process.exit(1);
|
|
281
|
+
}
|
|
271
282
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
3
|
+
*
|
|
4
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
5
|
+
* shape — that form is a runtime probe, not a dependency, and the detector
|
|
6
|
+
* ignores it for exactly that reason.
|
|
7
|
+
*/
|
|
8
|
+
export declare function findUnshimmedGjsGlobals(code: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
11
|
+
* without the `@gjsify/node-gi/globals` shim.
|
|
12
|
+
*
|
|
13
|
+
* @param code the emitted bundle source
|
|
14
|
+
* @param outfile path of the bundle, for the error message
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertNodeBundleGlobalsShimmed(code: string, outfile: string): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Build-time guard: a `--app node` bundle that USES the GJS ambient globals
|
|
2
|
+
// must also have been given `@gjsify/node-gi/globals`.
|
|
3
|
+
//
|
|
4
|
+
// `print`/`printerr`/`log`/`logError`/`ARGV`/`imports` exist implicitly under
|
|
5
|
+
// gjs and nowhere else. On the node target they are seeded by the side-effect
|
|
6
|
+
// shim `@gjsify/node-gi/globals`, which the CLI injects when — and only when —
|
|
7
|
+
// `detectNodeGiGlobals` finds one of them in an analysis bundle. Injecting it
|
|
8
|
+
// unconditionally is not an option: it eagerly loads the native node-gi addon,
|
|
9
|
+
// so a cross-platform package's node bundle would stop loading on plain Node.
|
|
10
|
+
//
|
|
11
|
+
// That makes the decision a PREDICTION about an artifact that does not exist
|
|
12
|
+
// yet, and a prediction can be wrong. It was: the analysis pass used to run
|
|
13
|
+
// before the explicit-`--globals` register stub was resolved, and that stub is
|
|
14
|
+
// one of the two signals `isGjsSourceBuild()` reads — so the analysis bundle
|
|
15
|
+
// routed `/register` subpaths to `@gjsify/empty` and emptied `@girs/*` while the
|
|
16
|
+
// REAL build resolved them for real. Every ambient global reachable only through
|
|
17
|
+
// a register module was therefore invisible to the detector. The build exited 0
|
|
18
|
+
// and the emitted bundle died at its first canvas op with
|
|
19
|
+
//
|
|
20
|
+
// ReferenceError: imports is not defined
|
|
21
|
+
// at CanvasRenderingContext2D._toDataURL
|
|
22
|
+
//
|
|
23
|
+
// on node AND bun (the excalibur-jelly-jumper showcase), while gjs — where the
|
|
24
|
+
// globals are ambient — ran fine. Nothing between `gjsify build --app node` and
|
|
25
|
+
// a user running the bundle ever looked at the artifact.
|
|
26
|
+
//
|
|
27
|
+
// So this is a POST-condition on what was actually emitted: whatever the
|
|
28
|
+
// detector predicted, check it against the bundle. It is deliberately NOT a
|
|
29
|
+
// second detector — it runs the SAME `detectGjsAmbientGlobals` over the SAME
|
|
30
|
+
// kind of input (bundled, tree-shaken output, parsed by acorn, bare identifiers
|
|
31
|
+
// only). Two passes over one oracle that must agree; when they don't, the graph
|
|
32
|
+
// they saw differed, which is a bug in the build pipeline, not in user code.
|
|
33
|
+
//
|
|
34
|
+
// Scope: only when the CLI decided NOT to inject (`--globals auto`, no shim).
|
|
35
|
+
// `--globals none` opts out of the whole mechanism explicitly and is skipped.
|
|
36
|
+
import { detectGjsAmbientGlobals } from '@gjsify/rolldown-plugin-gjsify/globals';
|
|
37
|
+
/**
|
|
38
|
+
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
39
|
+
*
|
|
40
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
41
|
+
* shape — that form is a runtime probe, not a dependency, and the detector
|
|
42
|
+
* ignores it for exactly that reason.
|
|
43
|
+
*/
|
|
44
|
+
export function findUnshimmedGjsGlobals(code) {
|
|
45
|
+
return [...detectGjsAmbientGlobals(code)].sort();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
49
|
+
* without the `@gjsify/node-gi/globals` shim.
|
|
50
|
+
*
|
|
51
|
+
* @param code the emitted bundle source
|
|
52
|
+
* @param outfile path of the bundle, for the error message
|
|
53
|
+
*/
|
|
54
|
+
export function assertNodeBundleGlobalsShimmed(code, outfile) {
|
|
55
|
+
const offenders = findUnshimmedGjsGlobals(code);
|
|
56
|
+
if (offenders.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
throw new Error(`gjsify build --app node: ${outfile} references ${offenders.length} GJS ambient global(s) that ` +
|
|
59
|
+
'nothing installs on node/bun/deno, so the bundle throws `ReferenceError: <name> is not defined` ' +
|
|
60
|
+
'the first time that code runs:\n' +
|
|
61
|
+
offenders.map((s) => ` - ${s}`).join('\n') +
|
|
62
|
+
'\n\nThe `@gjsify/node-gi/globals` shim seeds them, but it is injected only when the pre-build ' +
|
|
63
|
+
'detection pass finds them — and that pass did not. The two passes therefore disagreed about the ' +
|
|
64
|
+
'module graph. Either the detection ran against a different graph than the one emitted (check that ' +
|
|
65
|
+
'anything feeding `isGjsSourceBuild()` — the `--globals` register stub, `nodeGiGlobalsInject` — is ' +
|
|
66
|
+
'settled BEFORE `detectNodeGiGlobals`), or the offending package should stop reaching for the ' +
|
|
67
|
+
'legacy `imports` object: `gi://<Ns>` and the bare `system`/`gettext`/`cairo` built-ins resolve on ' +
|
|
68
|
+
'both runtimes, `imports` does not.');
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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.25.1",
|
|
134
|
+
"@gjsify/create-app": "^0.25.1",
|
|
135
|
+
"@gjsify/node-globals": "^0.25.1",
|
|
136
|
+
"@gjsify/node-polyfills": "^0.25.1",
|
|
137
|
+
"@gjsify/npm-registry": "^0.25.1",
|
|
138
|
+
"@gjsify/resolve-npm": "^0.25.1",
|
|
139
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.25.1",
|
|
140
|
+
"@gjsify/rolldown-plugin-pnp": "^0.25.1",
|
|
141
|
+
"@gjsify/semver": "^0.25.1",
|
|
142
|
+
"@gjsify/tar": "^0.25.1",
|
|
143
|
+
"@gjsify/tsc": "^0.25.1",
|
|
144
|
+
"@gjsify/web-polyfills": "^0.25.1",
|
|
145
|
+
"@gjsify/workspace": "^0.25.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.25.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.25.1",
|
|
159
|
+
"@gjsify/rolldown-native": "^0.25.1"
|
|
160
160
|
},
|
|
161
161
|
"peerDependenciesMeta": {
|
|
162
162
|
"@gjsify/oxfmt-native": {
|