@gjsify/cli 0.35.0 → 0.37.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 +136 -126
- package/lib/actions/build.d.ts +19 -0
- package/lib/actions/build.js +2 -2
- package/lib/bundler-pick.js +72 -98
- package/lib/cli-fail.d.ts +4 -7
- package/lib/cli-fail.js +29 -55
- package/lib/commands/affected.js +161 -286
- package/lib/commands/install.d.ts +63 -66
- package/lib/commands/install.js +372 -536
- package/lib/commands/run.d.ts +1 -0
- package/lib/commands/run.js +37 -0
- package/lib/templates/install.mjs.tmpl +38 -68
- package/lib/types/cli-build-options.d.ts +33 -67
- package/lib/types/command.d.ts +9 -12
- package/lib/types/config-data.d.ts +125 -221
- package/lib/utils/auth-npmrc.d.ts +5 -10
- package/lib/utils/auth-npmrc.js +11 -21
- package/lib/utils/check-system-deps.d.ts +16 -0
- package/lib/utils/check-system-deps.js +49 -0
- package/lib/utils/clear-targets.d.ts +8 -11
- package/lib/utils/clear-targets.js +22 -29
- package/lib/utils/copy-targets.d.ts +13 -19
- package/lib/utils/copy-targets.js +31 -44
- package/lib/utils/dep-aggregation.d.ts +7 -15
- package/lib/utils/dep-aggregation.js +9 -22
- package/lib/utils/detect-native-packages.d.ts +83 -116
- package/lib/utils/detect-native-packages.js +111 -149
- package/lib/utils/dir-link.d.ts +29 -48
- package/lib/utils/dir-link.js +33 -56
- package/lib/utils/discover-showcases.d.ts +6 -7
- package/lib/utils/discover-showcases.js +11 -15
- package/lib/utils/dlx-cache.d.ts +29 -38
- package/lib/utils/dlx-cache.js +53 -63
- package/lib/utils/force-exit.d.ts +0 -3
- package/lib/utils/force-exit.js +13 -24
- package/lib/utils/gi-search-path.d.ts +16 -24
- package/lib/utils/gi-search-path.js +54 -82
- package/lib/utils/gjs-bundle-guard.d.ts +5 -13
- package/lib/utils/gjs-bundle-guard.js +35 -57
- package/lib/utils/gjs-source-escape.d.ts +2 -2
- package/lib/utils/gjs-source-escape.js +21 -39
- package/lib/utils/gjsify-shim.d.ts +26 -8
- package/lib/utils/gjsify-shim.js +128 -80
- package/lib/utils/html-entry.d.ts +8 -12
- package/lib/utils/html-entry.js +19 -28
- package/lib/utils/install-backend-native.d.ts +123 -161
- package/lib/utils/install-backend-native.js +536 -737
- package/lib/utils/install-backend.d.ts +42 -57
- package/lib/utils/install-backend.js +16 -21
- package/lib/utils/install-cache-fs.d.ts +10 -15
- package/lib/utils/install-cache-fs.js +18 -26
- package/lib/utils/install-lock.d.ts +5 -6
- package/lib/utils/install-lock.js +46 -65
- package/lib/utils/install-packument-cache.d.ts +27 -33
- package/lib/utils/install-packument-cache.js +44 -57
- package/lib/utils/install-progress.d.ts +2 -9
- package/lib/utils/install-progress.js +14 -39
- package/lib/utils/install-tarball-cache.d.ts +14 -20
- package/lib/utils/install-tarball-cache.js +41 -59
- package/lib/utils/library-output.d.ts +9 -22
- package/lib/utils/library-output.js +19 -37
- package/lib/utils/load-npmrc.d.ts +5 -6
- package/lib/utils/load-npmrc.js +18 -27
- package/lib/utils/node-bundle-guard.d.ts +4 -8
- package/lib/utils/node-bundle-guard.js +21 -35
- package/lib/utils/node-script.d.ts +12 -0
- package/lib/utils/node-script.js +95 -0
- package/lib/utils/node-version.d.ts +5 -14
- package/lib/utils/node-version.js +15 -24
- package/lib/utils/normalize-bundler-options.d.ts +7 -11
- package/lib/utils/normalize-bundler-options.js +24 -42
- package/lib/utils/npm-oidc.d.ts +23 -37
- package/lib/utils/npm-oidc.js +28 -53
- package/lib/utils/npm-otp-cache.d.ts +4 -4
- package/lib/utils/npm-otp-cache.js +20 -25
- package/lib/utils/npm-otp.d.ts +20 -30
- package/lib/utils/npm-otp.js +32 -51
- package/lib/utils/onboard-probe.d.ts +7 -13
- package/lib/utils/onboard-probe.js +16 -27
- package/lib/utils/parse-spec.d.ts +3 -3
- package/lib/utils/parse-spec.js +4 -5
- package/lib/utils/pkg-json-edit.d.ts +7 -12
- package/lib/utils/pkg-json-edit.js +13 -26
- package/lib/utils/pkg-json.d.ts +11 -18
- package/lib/utils/pkg-json.js +19 -28
- package/lib/utils/prefixed-output.d.ts +3 -4
- package/lib/utils/prefixed-output.js +13 -19
- package/lib/utils/prompt.d.ts +3 -4
- package/lib/utils/prompt.js +14 -18
- package/lib/utils/publish-diagnose.d.ts +11 -16
- package/lib/utils/publish-diagnose.js +15 -28
- package/lib/utils/publish-headers.d.ts +7 -11
- package/lib/utils/publish-headers.js +36 -65
- package/lib/utils/publishable-packages.d.ts +4 -6
- package/lib/utils/publishable-packages.js +14 -18
- package/lib/utils/resolve-gjs-entry.d.ts +5 -8
- package/lib/utils/resolve-gjs-entry.js +10 -27
- package/lib/utils/resolve-npm-package.d.ts +6 -11
- package/lib/utils/resolve-npm-package.js +29 -63
- package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
- package/lib/utils/resolve-plugin-by-name.js +9 -14
- package/lib/utils/run-gjs.d.ts +19 -17
- package/lib/utils/run-gjs.js +37 -51
- package/lib/utils/run-lifecycle-script.d.ts +5 -9
- package/lib/utils/run-lifecycle-script.js +23 -42
- package/lib/utils/run-node.d.ts +23 -33
- package/lib/utils/run-node.js +56 -60
- package/lib/utils/runtimes.d.ts +25 -35
- package/lib/utils/runtimes.js +34 -46
- package/lib/utils/spawn.d.ts +32 -48
- package/lib/utils/spawn.js +51 -84
- package/lib/utils/stdin.d.ts +2 -2
- package/lib/utils/stdin.js +10 -25
- package/lib/utils/suggest.d.ts +6 -7
- package/lib/utils/suggest.js +8 -10
- package/lib/utils/system-gi.d.ts +18 -27
- package/lib/utils/system-gi.js +60 -114
- package/lib/utils/trust-registry.d.ts +4 -8
- package/lib/utils/trust-registry.js +7 -15
- package/lib/utils/win32-command.d.ts +8 -14
- package/lib/utils/win32-command.js +29 -43
- package/lib/utils/workspace-root.js +7 -12
- package/package.json +18 -17
- package/showcases.json +6 -0
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
// Guard against the recurring "library build leaks into src/" footgun.
|
|
2
2
|
//
|
|
3
|
-
// `BuildAction.buildLibrary` derives its
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// tree
|
|
9
|
-
// `src/_virtual/` helper dir, and a nested `src/<pkg>/src/**` preserve-modules
|
|
10
|
-
// tree. These pure helpers let the build action refuse that up front instead
|
|
11
|
-
// of silently corrupting the source tree.
|
|
3
|
+
// `BuildAction.buildLibrary` derives its outdir from `dirname(package.json "module" ?? "main")`.
|
|
4
|
+
// When a package points `main` at its runtime SOURCE entry (`src/index.ts` — the intended entry
|
|
5
|
+
// for Vite/gjsify-compiled consumers, e.g. `@gjsify/adwaita-web`), that outdir is `src/`, so
|
|
6
|
+
// `gjsify build --library` writes the compiled tree next to the sources: `src/*.js` / `*2.js`
|
|
7
|
+
// duplicates, a `src/_virtual/` helper dir, and a nested `src/<pkg>/src/**` preserve-modules
|
|
8
|
+
// tree. These pure helpers let the build action refuse that up front.
|
|
12
9
|
import { dirname, extname, isAbsolute, relative, resolve } from 'node:path';
|
|
13
10
|
/**
|
|
14
|
-
* The literal (non-glob) base directories the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - `src/index.ts` → `<cwd>/src`
|
|
19
|
-
* - `src/**` + `/*.ts` → `<cwd>/src`
|
|
20
|
-
* - `src` → `<cwd>/src`
|
|
11
|
+
* The literal (non-glob) base directories the input source files live in, derived from the
|
|
12
|
+
* bundler `input` patterns: a file entry maps to its containing dir, a glob is reduced to the
|
|
13
|
+
* literal prefix before the first wildcard (`src/index.ts`, `src` and `src/**` + `/*.ts` all
|
|
14
|
+
* give `<cwd>/src`).
|
|
21
15
|
*
|
|
22
|
-
* The project root
|
|
23
|
-
*
|
|
24
|
-
* not the `src/`-subdir footgun this guard targets.
|
|
16
|
+
* The project root is intentionally excluded — a flat library keeping its source at the root and
|
|
17
|
+
* emitting there is an explicit layout, not the `src/`-subdir footgun this guard targets.
|
|
25
18
|
*/
|
|
26
19
|
export function inputSourceDirs(input, cwd) {
|
|
27
20
|
const patterns = [];
|
|
@@ -39,18 +32,16 @@ export function inputSourceDirs(input, cwd) {
|
|
|
39
32
|
const dirs = new Set();
|
|
40
33
|
for (const raw of patterns) {
|
|
41
34
|
const norm = raw.replace(/\\/g, '/');
|
|
42
|
-
//
|
|
43
|
-
// it is a literal path prefix.
|
|
35
|
+
// Everything before the first glob metacharacter is a literal path prefix.
|
|
44
36
|
const globIdx = norm.search(/[*?{}[\]()!+@]/);
|
|
45
37
|
let base = globIdx === -1 ? norm : norm.slice(0, globIdx);
|
|
46
38
|
if (globIdx === -1) {
|
|
47
|
-
//
|
|
48
|
-
// a bare directory stays as-is.
|
|
39
|
+
// Wholly literal: a file path collapses to its dirname, a bare directory stays.
|
|
49
40
|
base = extname(base) ? dirname(base) : base;
|
|
50
41
|
}
|
|
51
42
|
else {
|
|
52
|
-
// The glob cut mid-segment (`src
|
|
53
|
-
//
|
|
43
|
+
// The glob cut mid-segment (`src/fo*`) — drop the trailing partial segment so only
|
|
44
|
+
// the complete literal directory remains.
|
|
54
45
|
base = base.replace(/\/[^/]*$/, '');
|
|
55
46
|
}
|
|
56
47
|
const abs = resolve(root, base || '.');
|
|
@@ -65,23 +56,14 @@ function isInsideOrEqual(child, parent) {
|
|
|
65
56
|
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
66
57
|
}
|
|
67
58
|
/**
|
|
68
|
-
* `true` when
|
|
69
|
-
*
|
|
70
|
-
* equals, or is nested under, any input source directory.
|
|
71
|
-
*
|
|
72
|
-
* isOutdirInsideSource('src', ['<cwd>/src']) → true (leak)
|
|
73
|
-
* isOutdirInsideSource('src/nest', ['<cwd>/src']) → true (leak)
|
|
74
|
-
* isOutdirInsideSource('dist/esm', ['<cwd>/src']) → false (safe)
|
|
75
|
-
* isOutdirInsideSource('lib', ['<cwd>/src']) → false (safe)
|
|
59
|
+
* `true` when library output written to `outdir` would land inside (or on top of) the input
|
|
60
|
+
* source tree — i.e. `outdir` equals, or is nested under, any input source directory.
|
|
76
61
|
*/
|
|
77
62
|
export function isOutdirInsideSource(outdir, sourceDirs, cwd) {
|
|
78
63
|
const absOut = resolve(cwd, outdir);
|
|
79
64
|
return sourceDirs.some((dir) => isInsideOrEqual(absOut, dir));
|
|
80
65
|
}
|
|
81
|
-
/**
|
|
82
|
-
* The error thrown when a `--library` build would emit into its own source
|
|
83
|
-
* tree. Names both remedies so the fix is actionable at the point of failure.
|
|
84
|
-
*/
|
|
66
|
+
/** The error thrown when a `--library` build would emit into its own source tree. */
|
|
85
67
|
export function libraryOutputLeakError(outdir) {
|
|
86
68
|
return new Error(`gjsify build --library: refusing to write output into "${outdir}", which holds the ` +
|
|
87
69
|
`input source — a library build there produces src/*.js duplicates, *2.js, a ` +
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { type NpmrcConfig } from '@gjsify/npm-registry';
|
|
2
2
|
export declare function loadNpmrc(cwd: string): Promise<NpmrcConfig>;
|
|
3
3
|
/**
|
|
4
|
-
* True iff the parsed npmrc contains *any* `_authToken` entry
|
|
5
|
-
*
|
|
6
|
-
* instead of a generic 401/empty response.
|
|
4
|
+
* True iff the parsed npmrc contains *any* `_authToken` entry — lets a command
|
|
5
|
+
* say "no token configured" instead of relaying a generic 401.
|
|
7
6
|
*/
|
|
8
7
|
export declare function hasAnyAuthToken(npmrc: NpmrcConfig): boolean;
|
|
9
8
|
/**
|
|
10
|
-
* True iff the parsed npmrc has *any* credential (bearer token or basic
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* True iff the parsed npmrc has *any* credential (bearer token or basic auth).
|
|
10
|
+
* `gjsify whoami` uses it to tell "no token configured" apart from "token
|
|
11
|
+
* configured but rejected by the registry".
|
|
13
12
|
*/
|
|
14
13
|
export declare function hasAnyCredential(npmrc: NpmrcConfig): boolean;
|
package/lib/utils/load-npmrc.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
// Shared npmrc loader for
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Shared npmrc loader for the auth-aware commands (`gjsify publish`,
|
|
2
|
+
// `gjsify whoami`, …), so that both a maintainer's `~/.npmrc` token and a
|
|
3
|
+
// CI-injected `NPM_CONFIG_USERCONFIG` token reach the registry call.
|
|
4
|
+
// `actions/setup-node` writes its auth-token npmrc to `$RUNNER_TEMP/.npmrc` and
|
|
5
|
+
// exports `NPM_CONFIG_USERCONFIG` at it — it never touches `~/.npmrc`, so
|
|
6
|
+
// honouring that env var is what makes CI authentication work, and `${VAR}`
|
|
7
|
+
// placeholders must be expanded on read (npm's own behaviour):
|
|
8
|
+
// `_authToken=${NODE_AUTH_TOKEN}` arrives as a literal placeholder.
|
|
5
9
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// 2. userconfig: $NPM_CONFIG_USERCONFIG (overrides ~/.npmrc)
|
|
12
|
-
// or ~/.npmrc (default)
|
|
13
|
-
// 3. projectconfig: ./.npmrc (closest workspace)
|
|
14
|
-
//
|
|
15
|
-
// `actions/setup-node` writes the auth-token npmrc to $RUNNER_TEMP/.npmrc
|
|
16
|
-
// and exports NPM_CONFIG_USERCONFIG pointing at it — it does NOT touch
|
|
17
|
-
// ~/.npmrc. Honor the env var so CI authentication works end-to-end.
|
|
18
|
-
//
|
|
19
|
-
// `${VAR}` placeholders are expanded inline (npm CLI's expand-on-read
|
|
20
|
-
// behavior). The auth-token npmrc from actions/setup-node ships
|
|
21
|
-
// `_authToken=${NODE_AUTH_TOKEN}` as a literal placeholder; the env var is
|
|
22
|
-
// set on the publish step.
|
|
10
|
+
// `/etc/npmrc` (globalconfig) is deliberately NOT read — these commands operate
|
|
11
|
+
// on per-user credentials, matching `npm whoami`. The remaining sources are
|
|
12
|
+
// concatenated and re-parsed, so LAST WRITE WINS on a key collision: appended
|
|
13
|
+
// project-first then user, the user/CI config overrides the project `.npmrc`,
|
|
14
|
+
// which is the inverse of npm's own precedence.
|
|
23
15
|
import { existsSync, readFileSync } from 'node:fs';
|
|
24
16
|
import { homedir } from 'node:os';
|
|
25
17
|
import { join } from 'node:path';
|
|
@@ -44,17 +36,16 @@ export async function loadNpmrc(cwd) {
|
|
|
44
36
|
return parseNpmrc(merged);
|
|
45
37
|
}
|
|
46
38
|
/**
|
|
47
|
-
* True iff the parsed npmrc contains *any* `_authToken` entry
|
|
48
|
-
*
|
|
49
|
-
* instead of a generic 401/empty response.
|
|
39
|
+
* True iff the parsed npmrc contains *any* `_authToken` entry — lets a command
|
|
40
|
+
* say "no token configured" instead of relaying a generic 401.
|
|
50
41
|
*/
|
|
51
42
|
export function hasAnyAuthToken(npmrc) {
|
|
52
43
|
return Object.keys(npmrc.authTokens).length > 0;
|
|
53
44
|
}
|
|
54
45
|
/**
|
|
55
|
-
* True iff the parsed npmrc has *any* credential (bearer token or basic
|
|
56
|
-
*
|
|
57
|
-
*
|
|
46
|
+
* True iff the parsed npmrc has *any* credential (bearer token or basic auth).
|
|
47
|
+
* `gjsify whoami` uses it to tell "no token configured" apart from "token
|
|
48
|
+
* configured but rejected by the registry".
|
|
58
49
|
*/
|
|
59
50
|
export function hasAnyCredential(npmrc) {
|
|
60
51
|
return hasAnyAuthToken(npmrc) || Object.keys(npmrc.basicAuth).length > 0;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
3
3
|
*
|
|
4
|
-
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
5
|
-
*
|
|
6
|
-
* ignores it for exactly that reason.
|
|
4
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard shape — that
|
|
5
|
+
* form is a runtime probe, not a dependency, and the detector ignores it for that reason.
|
|
7
6
|
*/
|
|
8
7
|
export declare function findUnshimmedGjsGlobals(code: string): string[];
|
|
9
8
|
/**
|
|
10
|
-
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @param code the emitted bundle source
|
|
14
|
-
* @param outfile path of the bundle, for the error message
|
|
9
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built without
|
|
10
|
+
* the `@gjsify/node-gi/globals` shim.
|
|
15
11
|
*/
|
|
16
12
|
export declare function assertNodeBundleGlobalsShimmed(code: string, outfile: string): void;
|
|
@@ -1,35 +1,25 @@
|
|
|
1
1
|
// Build-time guard: a `--app node` bundle that USES the GJS ambient globals
|
|
2
2
|
// must also have been given `@gjsify/node-gi/globals`.
|
|
3
3
|
//
|
|
4
|
-
// `print`/`printerr`/`log`/`logError`/`ARGV`/`imports` exist implicitly under
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// so a cross-platform package's node bundle would stop loading on plain Node.
|
|
4
|
+
// `print`/`printerr`/`log`/`logError`/`ARGV`/`imports` exist implicitly under gjs and
|
|
5
|
+
// nowhere else. On the node target the side-effect shim `@gjsify/node-gi/globals` seeds
|
|
6
|
+
// them, injected only when `detectNodeGiGlobals` finds one in an analysis bundle —
|
|
7
|
+
// injecting unconditionally would eagerly load the native node-gi addon, so a
|
|
8
|
+
// cross-platform package's node bundle would stop loading on plain Node.
|
|
10
9
|
//
|
|
11
|
-
// That makes the decision a PREDICTION about an artifact that does not exist
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
10
|
+
// That makes the decision a PREDICTION about an artifact that does not exist yet, and it
|
|
11
|
+
// was wrong once: the analysis pass ran before the explicit-`--globals` register stub was
|
|
12
|
+
// resolved, and that stub is one of the two signals `isGjsSourceBuild()` reads, so the
|
|
13
|
+
// analysis bundle routed `/register` subpaths to `@gjsify/empty` and emptied `@girs/*`
|
|
14
|
+
// while the REAL build resolved them — making every ambient global reachable only through
|
|
15
|
+
// a register module invisible to the detector. The build exited 0 and the bundle died at
|
|
16
|
+
// its first canvas op with `ReferenceError: imports is not defined` on node AND bun (the
|
|
17
|
+
// excalibur-jelly-jumper showcase), while gjs ran fine.
|
|
19
18
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
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.
|
|
19
|
+
// So this is a POST-condition on what was actually emitted, deliberately NOT a second
|
|
20
|
+
// detector: it runs the SAME `detectGjsAmbientGlobals` over the SAME kind of input
|
|
21
|
+
// (bundled, tree-shaken output, parsed by acorn, bare identifiers only). Two passes over
|
|
22
|
+
// one oracle that must agree; a disagreement is a bug in the build pipeline, not user code.
|
|
33
23
|
//
|
|
34
24
|
// Scope: only when the CLI decided NOT to inject (`--globals auto`, no shim).
|
|
35
25
|
// `--globals none` opts out of the whole mechanism explicitly and is skipped.
|
|
@@ -37,19 +27,15 @@ import { detectGjsAmbientGlobals } from '@gjsify/rolldown-plugin-gjsify/globals'
|
|
|
37
27
|
/**
|
|
38
28
|
* The GJS ambient globals referenced as BARE identifiers in `code`, sorted.
|
|
39
29
|
*
|
|
40
|
-
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard
|
|
41
|
-
*
|
|
42
|
-
* ignores it for exactly that reason.
|
|
30
|
+
* Empty for a bundle that only uses the `globalThis.imports` isomorphic-guard shape — that
|
|
31
|
+
* form is a runtime probe, not a dependency, and the detector ignores it for that reason.
|
|
43
32
|
*/
|
|
44
33
|
export function findUnshimmedGjsGlobals(code) {
|
|
45
34
|
return [...detectGjsAmbientGlobals(code)].sort();
|
|
46
35
|
}
|
|
47
36
|
/**
|
|
48
|
-
* Throw when a `--app node` bundle references GJS ambient globals but was built
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* @param code the emitted bundle source
|
|
52
|
-
* @param outfile path of the bundle, for the error message
|
|
37
|
+
* Throw when a `--app node` bundle references GJS ambient globals but was built without
|
|
38
|
+
* the `@gjsify/node-gi/globals` shim.
|
|
53
39
|
*/
|
|
54
40
|
export function assertNodeBundleGlobalsShimmed(code, outfile) {
|
|
55
41
|
const offenders = findUnshimmedGjsGlobals(code);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RunNodeScriptOptions {
|
|
2
|
+
/** Exit the process when the script succeeds. See `RunGjsBundleOptions.exitOnSuccess`. */
|
|
3
|
+
exitOnSuccess?: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Run `file` — an unbundled ESM script that imports `node:` builtins — on the
|
|
7
|
+
* CLI's own host runtime, bundling it for GJS first when that is where we are.
|
|
8
|
+
*
|
|
9
|
+
* Terminal callers pass `exitOnSuccess` (a GJS main loop armed by the spawn
|
|
10
|
+
* parks the process otherwise); see {@link runGjsBundle}.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runNodeScript(file: string, extraArgs?: string[], options?: RunNodeScriptOptions): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// `gjsify run --node-script <file>` — run an UNBUNDLED `.mjs`/`.js` that imports
|
|
2
|
+
// `node:*` builtins, on whatever runtime the CLI itself is executing on.
|
|
3
|
+
//
|
|
4
|
+
// A repo script that imports only `node:fs`/`node:path`/`node:url` — all polyfilled — is not
|
|
5
|
+
// intrinsically Node-bound; what was missing is a way to RUN one under GJS, whose ESM loader
|
|
6
|
+
// has no `node:` URI scheme and no node_modules walker, so `gjs -m <script>.mjs` dies on the
|
|
7
|
+
// first import. That gap is why a Node-less host could install gjsify, resolve every native
|
|
8
|
+
// prebuild, and then fail `build:infra` at `node scripts/process-template.mjs` (see
|
|
9
|
+
// `status/open-todos.md`).
|
|
10
|
+
//
|
|
11
|
+
// Rejected alternative: commit a `dist/*.gjs.mjs` per script — one more artifact each under
|
|
12
|
+
// the committed-bundle freshness gate, scaling with the number of scripts. This is ONE
|
|
13
|
+
// mechanism serving every script in the repo and every consumer's too.
|
|
14
|
+
//
|
|
15
|
+
// node / bun / deno host → spawn the file on that runtime; no bundling, no cost.
|
|
16
|
+
// gjs host → bundle `--app gjs` (which maps `node:fs` → `@gjsify/fs`,
|
|
17
|
+
// injects the globals the script reads, and resolves bare
|
|
18
|
+
// specifiers), then run the bundle.
|
|
19
|
+
//
|
|
20
|
+
// `import.meta.url` is the whole trick: the bundle does not live where the source lives, and
|
|
21
|
+
// a script's own location is how it finds what it operates on — all four scripts this was
|
|
22
|
+
// built for derive their package root from `dirname(fileURLToPath(import.meta.url))`. Left
|
|
23
|
+
// alone that is the BUNDLE's URL, so every one would write its output into the cache
|
|
24
|
+
// directory; the source file's URL is therefore baked in as a compile-time define, exactly
|
|
25
|
+
// as `config.ts`'s `loadConfigViaGjsBundle` does for a `gjsify.config.js` that reads its own
|
|
26
|
+
// siblings. `process.argv` needs no such care: the `--app gjs` bundle already exposes
|
|
27
|
+
// `[gjs, <bundle>, ...args]`.
|
|
28
|
+
import { existsSync, statSync } from 'node:fs';
|
|
29
|
+
import { basename, resolve } from 'node:path';
|
|
30
|
+
import { pathToFileURL } from 'node:url';
|
|
31
|
+
import { hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
32
|
+
import { runGjsBundle } from './run-gjs.js';
|
|
33
|
+
import { runRuntimeBundle } from './run-node.js';
|
|
34
|
+
/**
|
|
35
|
+
* Whether the on-the-fly GJS bundle may be reused across runs.
|
|
36
|
+
*
|
|
37
|
+
* OFF by default: `bundleFileForGjsCached` invalidates on the ENTRY file's mtime plus the
|
|
38
|
+
* project lockfile, which covers a plugin or a config but NOT a repo script, whose imports
|
|
39
|
+
* are workspace packages that change without any lockfile write. A stale bundle there does
|
|
40
|
+
* not fail loudly — the script runs and EMITS BUILD OUTPUT from code that no longer exists
|
|
41
|
+
* in the tree, and a build script is the worst place to reintroduce that.
|
|
42
|
+
*
|
|
43
|
+
* The cost is one bundle per invocation, and only under GJS. `GJSIFY_NODE_SCRIPT_CACHE=1`
|
|
44
|
+
* opts in for a host that would rather have the seconds back.
|
|
45
|
+
*/
|
|
46
|
+
function scriptCacheEnabled() {
|
|
47
|
+
const v = process.env['GJSIFY_NODE_SCRIPT_CACHE'];
|
|
48
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run `file` — an unbundled ESM script that imports `node:` builtins — on the
|
|
52
|
+
* CLI's own host runtime, bundling it for GJS first when that is where we are.
|
|
53
|
+
*
|
|
54
|
+
* Terminal callers pass `exitOnSuccess` (a GJS main loop armed by the spawn
|
|
55
|
+
* parks the process otherwise); see {@link runGjsBundle}.
|
|
56
|
+
*/
|
|
57
|
+
export async function runNodeScript(file, extraArgs = [], options = {}) {
|
|
58
|
+
const scriptPath = resolve(file);
|
|
59
|
+
if (!existsSync(scriptPath) || !statSync(scriptPath).isFile()) {
|
|
60
|
+
console.error(`gjsify run --node-script: no such file: ${scriptPath}`);
|
|
61
|
+
// `return process.exit` — a bare exit falls through under GJS (see run.ts).
|
|
62
|
+
return process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const host = hostRuntime();
|
|
65
|
+
if (host !== 'gjs') {
|
|
66
|
+
// Node/bun/deno load the file as-is; bundling would only add a step and a way to
|
|
67
|
+
// differ from what the runtime would have done itself. `quiet` because the echo
|
|
68
|
+
// would print `node <file>` — what the package.json script already reads — behind
|
|
69
|
+
// ~2 kB of native-library env, a dozen times per build chain.
|
|
70
|
+
await runRuntimeBundle(host, scriptPath, extraArgs, { exitOnSuccess: options.exitOnSuccess, quiet: true });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Dynamic import keeps the bundler machinery out of the eager module graph
|
|
74
|
+
// of every `gjsify run`, matching how config.ts pulls it.
|
|
75
|
+
const { BuildAction } = await import('../actions/build.js');
|
|
76
|
+
let bundlePath;
|
|
77
|
+
try {
|
|
78
|
+
bundlePath = await BuildAction.bundleFileForGjsCached(scriptPath, {
|
|
79
|
+
cacheSubdir: 'node-scripts',
|
|
80
|
+
label: basename(scriptPath),
|
|
81
|
+
cache: scriptCacheEnabled(),
|
|
82
|
+
// A script is executed, never imported — no default export to preserve.
|
|
83
|
+
preserveDefaultExport: false,
|
|
84
|
+
define: { 'import.meta.url': JSON.stringify(pathToFileURL(scriptPath).href) },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
throw new Error(`gjsify run --node-script: failed to bundle ${scriptPath} for GJS. ` +
|
|
89
|
+
`The script must import only what gjsify can provide under GJS (node: builtins, ` +
|
|
90
|
+
`workspace packages, pure-JS npm deps). (${err.message})`);
|
|
91
|
+
}
|
|
92
|
+
// Quiet for the same reason as the node path above; the bundle's location is
|
|
93
|
+
// deterministic (`node_modules/.cache/gjsify/node-scripts/<name>-<hash>.mjs`).
|
|
94
|
+
await runGjsBundle(bundlePath, extraArgs, { exitOnSuccess: options.exitOnSuccess, quiet: true });
|
|
95
|
+
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimum Node.js major the native install backend supports — its prebuilt native
|
|
3
|
-
* modules are ABI-locked to this line (kept in sync with `.nvmrc`).
|
|
4
|
-
*/
|
|
1
|
+
/** Minimum Node major the native install backend supports — keep in sync with `.nvmrc`. */
|
|
5
2
|
export declare const MIN_NODE_MAJOR = 24;
|
|
6
3
|
/** Parse the major from a `process.versions.node`-style string (`'24.18.0'` → 24). */
|
|
7
4
|
export declare function parseNodeMajor(version: string): number;
|
|
8
5
|
/**
|
|
9
|
-
* The preflight verdict
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* supported Node, on GJS (which fakes `process.versions.node` and does not use
|
|
13
|
-
* these prebuilds), or on a version string that does not parse.
|
|
6
|
+
* The preflight verdict, factored out so it can be unit-tested without spawning a
|
|
7
|
+
* foreign Node. `null` (pass) on a supported Node, under GJS, and on a version
|
|
8
|
+
* string that does not parse — an unparseable version must not block an install.
|
|
14
9
|
*/
|
|
15
10
|
export declare function nativeBackendNodeError(version: string, onRealNode: boolean): string | null;
|
|
16
|
-
/**
|
|
17
|
-
* Guard the native install backend: throw a clear error on an unsupported Node
|
|
18
|
-
* major instead of letting the ABI-mismatched prebuilds SIGSEGV mid-install.
|
|
19
|
-
* No-op under GJS and on supported Node.
|
|
20
|
-
*/
|
|
11
|
+
/** Throw the preflight verdict as an error; no-op under GJS and on a supported Node. */
|
|
21
12
|
export declare function assertNativeBackendNodeVersion(): void;
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Node-version preflight for the native install backend.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// This preflight turns that into a clear, actionable error.
|
|
4
|
+
// Its prebuilt binaries are ABI-locked to the Node line gjsify targets (`.nvmrc`).
|
|
5
|
+
// On an older major they load a mismatched ABI and SIGSEGV mid-install instead of
|
|
6
|
+
// failing cleanly — measured: Node 22 in a Fedora container segfaults
|
|
7
|
+
// `gjsify install --backend native` during download/extract. This turns that into
|
|
8
|
+
// an actionable error.
|
|
10
9
|
import { isNode, gjsSystemVersion } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
11
|
-
/**
|
|
12
|
-
* Minimum Node.js major the native install backend supports — its prebuilt native
|
|
13
|
-
* modules are ABI-locked to this line (kept in sync with `.nvmrc`).
|
|
14
|
-
*/
|
|
10
|
+
/** Minimum Node major the native install backend supports — keep in sync with `.nvmrc`. */
|
|
15
11
|
export const MIN_NODE_MAJOR = 24;
|
|
16
12
|
/** Parse the major from a `process.versions.node`-style string (`'24.18.0'` → 24). */
|
|
17
13
|
export function parseNodeMajor(version) {
|
|
18
14
|
return Number.parseInt(String(version).split('.', 1)[0] ?? '', 10);
|
|
19
15
|
}
|
|
20
16
|
/**
|
|
21
|
-
* The preflight verdict
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* supported Node, on GJS (which fakes `process.versions.node` and does not use
|
|
25
|
-
* these prebuilds), or on a version string that does not parse.
|
|
17
|
+
* The preflight verdict, factored out so it can be unit-tested without spawning a
|
|
18
|
+
* foreign Node. `null` (pass) on a supported Node, under GJS, and on a version
|
|
19
|
+
* string that does not parse — an unparseable version must not block an install.
|
|
26
20
|
*/
|
|
27
21
|
export function nativeBackendNodeError(version, onRealNode) {
|
|
28
22
|
if (!onRealNode)
|
|
@@ -34,21 +28,18 @@ export function nativeBackendNodeError(version, onRealNode) {
|
|
|
34
28
|
`Its prebuilt native modules are ABI-locked to Node ${MIN_NODE_MAJOR} and crash on older majors. ` +
|
|
35
29
|
`Use Node ${MIN_NODE_MAJOR} (nvm/asdf locally, actions/setup-node in CI), or run with --backend npm.`);
|
|
36
30
|
}
|
|
37
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* True when the CLI runs on real Node. GJS must be ruled out FIRST: `@gjsify/process`
|
|
33
|
+
* fakes `process.versions.node = '20.0.0'` for compat, so reading that field would
|
|
34
|
+
* be a false Node positive there (and a false preflight failure, since 20 < 24).
|
|
35
|
+
*/
|
|
38
36
|
function onRealNode() {
|
|
39
|
-
// GJS exposes `imports.system.version`; @gjsify/process fakes
|
|
40
|
-
// `process.versions.node = '20.0.0'` for compat, so check GJS FIRST — a plain
|
|
41
|
-
// `process.versions.node` read would be a false Node positive under GJS.
|
|
42
37
|
const sysVersion = gjsSystemVersion();
|
|
43
38
|
if (sysVersion !== undefined)
|
|
44
39
|
return false;
|
|
45
40
|
return isNode();
|
|
46
41
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Guard the native install backend: throw a clear error on an unsupported Node
|
|
49
|
-
* major instead of letting the ABI-mismatched prebuilds SIGSEGV mid-install.
|
|
50
|
-
* No-op under GJS and on supported Node.
|
|
51
|
-
*/
|
|
42
|
+
/** Throw the preflight verdict as an error; no-op under GJS and on a supported Node. */
|
|
52
43
|
export function assertNativeBackendNodeVersion() {
|
|
53
44
|
const message = nativeBackendNodeError(process.versions.node, onRealNode());
|
|
54
45
|
if (message)
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { ConfigData, BundlerOptions } from '../types/config-data.js';
|
|
2
2
|
export declare function normalizeBundlerOptions(configData: ConfigData): BundlerOptions;
|
|
3
3
|
/**
|
|
4
|
-
* Shallow merge with deep-merge of `output`, `transform
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Shallow merge with deep-merge of `output`, `transform` and `resolve`; `overrides` wins,
|
|
5
|
+
* matching `merge(target, ...sources)` from `@gjsify/rolldown-plugin-gjsify/utils/merge`. `base`
|
|
6
|
+
* is the orchestrator's Rolldown-generic shape, `overrides` the user's config plus CLI flags.
|
|
7
7
|
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* The orchestrator-side `input` is authoritative — it's the post-glob-expansion
|
|
13
|
-
* value. Overriding it with the user's raw glob string would re-introduce
|
|
14
|
-
* unresolved glob patterns into the final Rolldown call. Same for `external`,
|
|
15
|
-
* which the orchestrator concatenates with platform defaults already.
|
|
8
|
+
* The orchestrator's `input` is authoritative because it is the post-glob-expansion value —
|
|
9
|
+
* taking the user's raw glob string would re-introduce unresolved patterns into the Rolldown
|
|
10
|
+
* call. Same for `external`, which the orchestrator has already concatenated with the platform
|
|
11
|
+
* defaults.
|
|
16
12
|
*/
|
|
17
13
|
export declare function mergeBundlerOptions(base: BundlerOptions, overrides: BundlerOptions): BundlerOptions;
|
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Deprecation shim: maps the legacy `esbuild?: BuildOptions` field on `.gjsifyrc.js` /
|
|
2
|
+
// `package.json#gjsify` into the equivalent `bundler?: RolldownOptions` shape, warning once per
|
|
3
|
+
// build. It was slated for removal in 0.5.0 and is still here at 0.35.0 — the warning text it
|
|
4
|
+
// prints still names 0.5.0, so removing the shim means fixing that string too.
|
|
4
5
|
//
|
|
5
|
-
// Also handles the top-level `bundler.define` alias: Rolldown reads
|
|
6
|
-
// `transform.define`,
|
|
7
|
-
// `
|
|
8
|
-
//
|
|
9
|
-
// `ReferenceError: <TOKEN> is not defined` at GJS load time. We auto-map
|
|
10
|
-
// top-level `bundler.define` into `bundler.transform.define` and emit a
|
|
11
|
-
// one-time warning so the user can correct their config.
|
|
12
|
-
//
|
|
13
|
-
// Drop `esbuild` shim in 0.5.0; the `bundler.define` alias is permanent.
|
|
6
|
+
// Also handles the top-level `bundler.define` alias, which is PERMANENT: Rolldown reads
|
|
7
|
+
// `transform.define`, so a user who flat-renames `esbuild: { define }` → `bundler: { define }`
|
|
8
|
+
// lands `define` at the top level where Rolldown silently ignores it, producing
|
|
9
|
+
// `ReferenceError: <TOKEN> is not defined` at GJS load time.
|
|
14
10
|
let warnedOnce = false;
|
|
15
11
|
let warnedDefineOnce = false;
|
|
16
12
|
export function normalizeBundlerOptions(configData) {
|
|
17
13
|
const raw = (configData.bundler ?? {});
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// ignored. Detect it, warn once, and move it into `transform.define` so the
|
|
21
|
-
// user's flat esbuild→bundler rename of `define` Just Works.
|
|
14
|
+
// Rolldown reads only `transform.define`, so a top-level `define` is moved there (warning
|
|
15
|
+
// once) rather than being silently ignored — see the header.
|
|
22
16
|
let fromBundler = raw;
|
|
23
17
|
if (typeof raw['define'] === 'object' &&
|
|
24
18
|
raw['define'] !== null) {
|
|
@@ -49,11 +43,9 @@ export function normalizeBundlerOptions(configData) {
|
|
|
49
43
|
"Rename it to 'bundler' (typed as RolldownOptions). See the migration notes in the gjsify CHANGELOG.");
|
|
50
44
|
}
|
|
51
45
|
const fromEsbuild = legacyEsbuildToRolldown(configData.esbuild);
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// `
|
|
55
|
-
// the override source and the user the base). Here both inputs are
|
|
56
|
-
// user-provided config and `input` must survive the merge.
|
|
46
|
+
// Deliberately NOT `mergeBundlerOptions`: that strips `input`/`external` from its overrides
|
|
47
|
+
// arg because it assumes the ORCHESTRATOR is the override source. Here both sides are user
|
|
48
|
+
// config, and `input` must survive the merge.
|
|
57
49
|
const out = { ...fromEsbuild, ...fromBundler };
|
|
58
50
|
if (fromEsbuild.output || fromBundler.output) {
|
|
59
51
|
out.output = { ...fromEsbuild.output, ...fromBundler.output };
|
|
@@ -111,33 +103,23 @@ function legacyEsbuildToRolldown(esb) {
|
|
|
111
103
|
out.transform = transform;
|
|
112
104
|
if (Object.keys(resolve).length > 0)
|
|
113
105
|
out.resolve = resolve;
|
|
114
|
-
// Discarded
|
|
115
|
-
//
|
|
116
|
-
// CLI layer instead, via input expansion.
|
|
117
|
-
// esb.loader — replaced by top-level `gjsify.loaders` (see ConfigData);
|
|
118
|
-
// migration: `esbuild.loader: { '.png': 'dataurl', '.glsl': 'text' }`
|
|
119
|
-
// → `loaders: { '.png': 'dataurl', '.glsl': 'text' }`.
|
|
106
|
+
// Discarded, handled elsewhere: `esb.inject` (surfaced at the CLI layer via input
|
|
107
|
+
// expansion) and `esb.loader` (replaced by top-level `gjsify.loaders`, see ConfigData).
|
|
120
108
|
return out;
|
|
121
109
|
}
|
|
122
110
|
/**
|
|
123
|
-
* Shallow merge with deep-merge of `output`, `transform
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* `base` is typically the Rolldown-generic shape returned by the orchestrator;
|
|
128
|
-
* `overrides` is the user's `BundlerOptions` from `.gjsifyrc.js` plus CLI
|
|
129
|
-
* flag merges. Single-output assumption matches `BundlerOptions['output']`.
|
|
111
|
+
* Shallow merge with deep-merge of `output`, `transform` and `resolve`; `overrides` wins,
|
|
112
|
+
* matching `merge(target, ...sources)` from `@gjsify/rolldown-plugin-gjsify/utils/merge`. `base`
|
|
113
|
+
* is the orchestrator's Rolldown-generic shape, `overrides` the user's config plus CLI flags.
|
|
130
114
|
*
|
|
131
|
-
* The orchestrator
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
115
|
+
* The orchestrator's `input` is authoritative because it is the post-glob-expansion value —
|
|
116
|
+
* taking the user's raw glob string would re-introduce unresolved patterns into the Rolldown
|
|
117
|
+
* call. Same for `external`, which the orchestrator has already concatenated with the platform
|
|
118
|
+
* defaults.
|
|
135
119
|
*/
|
|
136
120
|
export function mergeBundlerOptions(base, overrides) {
|
|
137
|
-
// Strip fields the orchestrator owns
|
|
138
|
-
//
|
|
139
|
-
// expansion; merging the raw values back on top would clobber the
|
|
140
|
-
// post-processing.
|
|
121
|
+
// Strip the fields the orchestrator owns: the user already had their say through
|
|
122
|
+
// `userExternal` / input expansion, and merging the raw values back would clobber it.
|
|
141
123
|
const { input: _ignoredInput, external: _ignoredExternal, ...overridesRest } = overrides;
|
|
142
124
|
const out = { ...base, ...overridesRest };
|
|
143
125
|
if (base.output || overrides.output) {
|