@gjsify/cli 0.36.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 +3 -3
- package/lib/bundler-pick.js +61 -87
- 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/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/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 +17 -20
- package/lib/utils/gjsify-shim.js +80 -120
- 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.js +33 -54
- 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 +16 -21
- package/lib/utils/run-gjs.js +31 -56
- 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 +21 -33
- package/lib/utils/run-node.js +43 -64
- 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 -18
|
@@ -1,88 +1,59 @@
|
|
|
1
1
|
// Multi-anchor npm-package resolver for the GJS-bundled CLI.
|
|
2
2
|
//
|
|
3
|
-
// GJS's native ESM loader has no node_modules walker — a bare
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// physically present in a node_modules above the caller's cwd.
|
|
3
|
+
// GJS's native ESM loader has no node_modules walker — a bare `await import('rolldown')`
|
|
4
|
+
// from inside the bundle throws `ImportError: Module not found: rolldown` even when the
|
|
5
|
+
// package is physically present in a node_modules above the caller's cwd.
|
|
7
6
|
//
|
|
8
|
-
// `createRequire(import.meta.url)` works
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// unrelated cwd (`/tmp/sandbox/`) whose `node_modules` is the only
|
|
15
|
-
// place rolldown is present, anchoring on the bundle URL misses it.
|
|
7
|
+
// `createRequire(import.meta.url)` works under Node, and under GJS with `@gjsify/module`'s
|
|
8
|
+
// polyfill it walks the node_modules chain from the URL it was anchored at — but only ONE
|
|
9
|
+
// chain, the one rooted at the anchor's parent. When the bundle lives at
|
|
10
|
+
// `<install>/dist/cli.gjs.mjs` and the user runs it from an unrelated cwd whose
|
|
11
|
+
// `node_modules` is the only place rolldown is present, anchoring on the bundle URL misses
|
|
12
|
+
// it. So a bare specifier is tried against several anchors, first hit wins:
|
|
16
13
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// 1. `GJSIFY_NODE_PATH` env override (colon-separated dir list,
|
|
21
|
-
// matches Node's NODE_PATH semantics — each entry is treated as a
|
|
22
|
-
// synthetic `node_modules` parent).
|
|
14
|
+
// 1. `GJSIFY_NODE_PATH` env override (NODE_PATH semantics — each entry is a synthetic
|
|
15
|
+
// `node_modules` parent).
|
|
23
16
|
// 2. Caller-supplied anchor dir (typically process.cwd()).
|
|
24
|
-
// 3. Workspace root from `findWorkspaceRoot(anchorDir)` —
|
|
25
|
-
//
|
|
17
|
+
// 3. Workspace root from `findWorkspaceRoot(anchorDir)` — the monorepo top when invoked
|
|
18
|
+
// from a sub-package directory.
|
|
26
19
|
// 4. Bundle path's parent chain (`import.meta.url` of THIS module).
|
|
27
|
-
// 5. Parent dirs of the anchor (
|
|
28
|
-
// setups — matches `oxc-resolve.ts`'s walker).
|
|
29
|
-
//
|
|
30
|
-
// Returns the absolute path on success (the caller usually wraps it in
|
|
31
|
-
// `pathToFileURL().href` before passing to dynamic `import(...)`).
|
|
32
|
-
// Returns null when every anchor failed.
|
|
20
|
+
// 5. Parent dirs of the anchor (nested-without-workspace layouts).
|
|
33
21
|
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// (parent-dir walker precedent).
|
|
22
|
+
// Precedents: `commands/tsc.ts` (workspace-root + cwd anchoring), `utils/oxc-resolve.ts`
|
|
23
|
+
// (parent-dir walker).
|
|
37
24
|
import { createRequire } from 'node:module';
|
|
38
25
|
import { pathToFileURL } from 'node:url';
|
|
39
26
|
import { delimiter, join, resolve } from 'node:path';
|
|
40
27
|
import { findWorkspaceRoot } from './workspace-root.js';
|
|
41
28
|
/**
|
|
42
|
-
* Resolve a bare npm specifier through
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Each anchor is tried in priority order — `GJSIFY_NODE_PATH` (highest)
|
|
47
|
-
* → caller cwd → workspace root → bundle URL → parent-dir walk from
|
|
48
|
-
* cwd. The first anchor whose `require.resolve()` succeeds wins.
|
|
29
|
+
* Resolve a bare npm specifier through the anchors listed above, returning the resolved
|
|
30
|
+
* module's absolute path or null when every anchor failed. Callers usually wrap the result in
|
|
31
|
+
* `pathToFileURL().href` before a dynamic `import(...)`.
|
|
49
32
|
*/
|
|
50
33
|
export function resolveNpmPackage(specifier, opts = {}) {
|
|
51
34
|
const cwd = opts.cwd ?? process.cwd();
|
|
52
|
-
// 1. GJSIFY_NODE_PATH env override (NODE_PATH-like) — each entry is
|
|
53
|
-
// treated as a directory whose own `node_modules` participates
|
|
54
|
-
// in the lookup. Mirrors Node's NODE_PATH semantics.
|
|
55
35
|
const envPath = process.env['GJSIFY_NODE_PATH'];
|
|
56
36
|
if (envPath) {
|
|
57
|
-
// `path.delimiter`, not `':'` —
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// becomes `['C', '\tools']`, neither of which resolves, so the documented
|
|
61
|
-
// override silently did nothing on Windows and fell through to anchor 2.
|
|
62
|
-
// `commands/run.ts` and `utils/run-lifecycle-script.ts` already get this
|
|
63
|
-
// right; this was the site that was missed.
|
|
37
|
+
// `path.delimiter`, not `':'` — NODE_PATH uses `;` on Windows, where splitting on a
|
|
38
|
+
// colon destroys even a SINGLE entry (`C:\tools` → `['C', '\tools']`), so the
|
|
39
|
+
// documented override silently did nothing there and fell through to anchor 2.
|
|
64
40
|
for (const dir of envPath.split(delimiter).filter(Boolean)) {
|
|
65
41
|
const hit = tryResolveFromDir(specifier, resolve(dir));
|
|
66
42
|
if (hit)
|
|
67
43
|
return hit;
|
|
68
44
|
}
|
|
69
45
|
}
|
|
70
|
-
// 2. Caller cwd → walk up for nearest node_modules/<pkg>.
|
|
71
46
|
const fromCwd = tryResolveFromDir(specifier, cwd);
|
|
72
47
|
if (fromCwd)
|
|
73
48
|
return fromCwd;
|
|
74
|
-
// 3. Workspace root (monorepo top, if any).
|
|
75
49
|
const wsRoot = findWorkspaceRoot(cwd);
|
|
76
50
|
if (wsRoot && wsRoot !== cwd) {
|
|
77
51
|
const fromWsRoot = tryResolveFromDir(specifier, wsRoot);
|
|
78
52
|
if (fromWsRoot)
|
|
79
53
|
return fromWsRoot;
|
|
80
54
|
}
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// `<install>/node_modules/rolldown` next to the CLI bundle)
|
|
84
|
-
// is reachable even when the user runs the bundle directly from
|
|
85
|
-
// an unrelated cwd.
|
|
55
|
+
// Anchored at the bundle URL so the install-time layout (`<install>/node_modules/rolldown`
|
|
56
|
+
// next to the CLI bundle) stays reachable when the bundle is run from an unrelated cwd.
|
|
86
57
|
if (opts.bundleUrl) {
|
|
87
58
|
try {
|
|
88
59
|
const req = createRequire(opts.bundleUrl);
|
|
@@ -92,11 +63,8 @@ export function resolveNpmPackage(specifier, opts = {}) {
|
|
|
92
63
|
// Fall through.
|
|
93
64
|
}
|
|
94
65
|
}
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// levels to avoid runaway filesystem walks on pathological
|
|
98
|
-
// layouts. Matches the depth `oxc-resolve.ts` uses for its
|
|
99
|
-
// equivalent walker.
|
|
66
|
+
// Last resort for nested layouts without a workspace marker. Depth-capped so a
|
|
67
|
+
// pathological layout cannot turn a failed resolve into a filesystem walk to `/`.
|
|
100
68
|
let dir = resolve(cwd, '..');
|
|
101
69
|
for (let i = 0; i < 8; i++) {
|
|
102
70
|
const hit = tryResolveFromDir(specifier, dir);
|
|
@@ -110,11 +78,9 @@ export function resolveNpmPackage(specifier, opts = {}) {
|
|
|
110
78
|
return null;
|
|
111
79
|
}
|
|
112
80
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* so seeding it inside `<dir>` makes `<dir>/node_modules/<pkg>` the
|
|
117
|
-
* first hit.
|
|
81
|
+
* Resolve `specifier` as if a fictional source file at `<dir>/__gjsify_resolve__.js` were
|
|
82
|
+
* doing the lookup: createRequire walks up from the anchor's PARENT for `node_modules/<pkg>`,
|
|
83
|
+
* so seeding it inside `<dir>` makes `<dir>/node_modules/<pkg>` the first hit.
|
|
118
84
|
*/
|
|
119
85
|
function tryResolveFromDir(specifier, dir) {
|
|
120
86
|
try {
|
|
@@ -10,32 +10,24 @@ export interface PluginByName {
|
|
|
10
10
|
/**
|
|
11
11
|
* Strategy for turning a resolved plugin file path into its module namespace.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* single self-contained ESM file (Rolldown resolves the exports map at
|
|
20
|
-
* bundle time), then imports that. See `BuildAction.loadPluginViaGjsBundle`.
|
|
13
|
+
* A plain dynamic `import()` on Node, but under GJS it cannot be: GJS's native ESM loader does not
|
|
14
|
+
* follow npm `package.json#exports` subpath maps for bare specifiers, so a plugin whose source
|
|
15
|
+
* does `import … from '@scope/pkg/internal-subpath'` throws `Module not found` even with the file
|
|
16
|
+
* on disk. The CLI injects a GJS strategy that first bundles the plugin to one self-contained ESM
|
|
17
|
+
* file (Rolldown resolves the exports map at bundle time), then imports that — see
|
|
18
|
+
* `BuildAction.loadPluginViaGjsBundle`.
|
|
21
19
|
*/
|
|
22
20
|
export type LoadPluginModule = (resolvedPath: string, pluginName: string) => Promise<Record<string, unknown>>;
|
|
23
21
|
export interface ResolveUserPluginsOptions {
|
|
24
|
-
/**
|
|
25
|
-
* Override how a resolved plugin path is turned into its module
|
|
26
|
-
* namespace. Defaults to a direct dynamic `import()` (correct on Node).
|
|
27
|
-
*/
|
|
22
|
+
/** Defaults to a direct dynamic `import()`, which is correct on Node. */
|
|
28
23
|
loadModule?: LoadPluginModule;
|
|
29
24
|
}
|
|
30
25
|
/** Type-guard: a `PluginByName` shape rather than a Rolldown plugin object. */
|
|
31
26
|
export declare function isPluginByName(value: unknown): value is PluginByName;
|
|
32
27
|
/**
|
|
33
|
-
* Resolve a list of mixed user plugins
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* the
|
|
37
|
-
*
|
|
38
|
-
* Throws when a name fails to resolve, when the chosen export is not a
|
|
39
|
-
* function, or when the factory returns nothing.
|
|
28
|
+
* Resolve a list of mixed user plugins, anchored at `projectDir`. Plugin objects pass through
|
|
29
|
+
* unchanged; `PluginByName` entries are imported, instantiated with their `options`, and returned
|
|
30
|
+
* in the same position. Throws when a name fails to resolve, the chosen export is not a function,
|
|
31
|
+
* or the factory returns nothing.
|
|
40
32
|
*/
|
|
41
33
|
export declare function resolveUserPlugins(plugins: ReadonlyArray<RolldownPluginOption | PluginByName>, projectDir: string, options?: ResolveUserPluginsOptions): Promise<RolldownPluginOption[]>;
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
// ]
|
|
9
9
|
// }
|
|
10
10
|
//
|
|
11
|
-
// Lets `package.json#gjsify` describe the full plugin chain without dropping
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// `node_modules` wins over the CLI's own dependencies.
|
|
11
|
+
// Lets `package.json#gjsify` describe the full plugin chain without dropping to a JS-form config
|
|
12
|
+
// file (`gjsify.config.mjs`). Resolution is anchored at the project root, so the project's own
|
|
13
|
+
// `node_modules` wins over the CLI's dependencies.
|
|
15
14
|
import { createRequire } from 'node:module';
|
|
16
15
|
import { join } from 'node:path';
|
|
17
16
|
import { pathToFileURL } from 'node:url';
|
|
@@ -22,9 +21,8 @@ export function isPluginByName(value) {
|
|
|
22
21
|
return (typeof value === 'object' &&
|
|
23
22
|
value !== null &&
|
|
24
23
|
typeof value.name === 'string' &&
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// with our shape, so we additionally require absence of plugin-shape fields.
|
|
24
|
+
// `name` alone does not discriminate — a real Rolldown plugin has it too — so the
|
|
25
|
+
// absence of every plugin-shape hook is what separates the two.
|
|
28
26
|
!('apply' in value) &&
|
|
29
27
|
!('resolveId' in value) &&
|
|
30
28
|
!('load' in value) &&
|
|
@@ -33,13 +31,10 @@ export function isPluginByName(value) {
|
|
|
33
31
|
!('generateBundle' in value));
|
|
34
32
|
}
|
|
35
33
|
/**
|
|
36
|
-
* Resolve a list of mixed user plugins
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the
|
|
40
|
-
*
|
|
41
|
-
* Throws when a name fails to resolve, when the chosen export is not a
|
|
42
|
-
* function, or when the factory returns nothing.
|
|
34
|
+
* Resolve a list of mixed user plugins, anchored at `projectDir`. Plugin objects pass through
|
|
35
|
+
* unchanged; `PluginByName` entries are imported, instantiated with their `options`, and returned
|
|
36
|
+
* in the same position. Throws when a name fails to resolve, the chosen export is not a function,
|
|
37
|
+
* or the factory returns nothing.
|
|
43
38
|
*/
|
|
44
39
|
export async function resolveUserPlugins(plugins, projectDir, options = {}) {
|
|
45
40
|
const loadModule = options.loadModule ?? defaultLoadModule;
|
package/lib/utils/run-gjs.d.ts
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
import { type NativeEnv } from './detect-native-packages.js';
|
|
2
2
|
/**
|
|
3
|
-
* Pure env computation
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see `buildNativeEnv`.
|
|
3
|
+
* Pure env computation: the typelib + shared-library search paths {@link runGjsBundle} would
|
|
4
|
+
* inject into the spawned `gjs`, plus the formatted prefix for the `$ …` echo. Which library
|
|
5
|
+
* variable is set is host-dependent (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see
|
|
6
|
+
* `buildNativeEnv`.
|
|
8
7
|
*/
|
|
9
8
|
export declare function computeNativeEnvForBundle(bundlePath: string, cwd?: string, inherited?: Record<string, string | undefined>): {
|
|
10
9
|
env: NativeEnv;
|
|
11
10
|
envPrefix: string;
|
|
12
11
|
};
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
* for any
|
|
16
|
-
*
|
|
13
|
+
* Options for {@link runGjsBundle}, which runs a GJS bundle with `GI_TYPELIB_PATH` and the
|
|
14
|
+
* host's library variable set for any native gjsify packages discoverable from the CWD or the
|
|
15
|
+
* bundle's own node_modules tree.
|
|
17
16
|
*/
|
|
18
17
|
export interface RunGjsBundleOptions {
|
|
19
18
|
/**
|
|
20
|
-
* Exit this process with code 0 once the child succeeds. Under GJS,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* continue after the bundle (e.g. `gjsify test`'s multi-runtime
|
|
27
|
-
* aggregation loop) MUST leave this false — an unconditional exit here
|
|
28
|
-
* truncated `gjsify test` after the first gjs bundle.
|
|
19
|
+
* Exit this process with code 0 once the child succeeds. Under GJS, `ensureMainLoop()`
|
|
20
|
+
* (armed by spawn) keeps the parent's GLib loop alive after the child exits, so a TERMINAL
|
|
21
|
+
* caller (`gjsify run <file>`, `dlx`) must opt in or it parks forever — this gap hung CI's
|
|
22
|
+
* WebGL conformance job for 83 min on a suite that finished in 1.35 s. Callers that
|
|
23
|
+
* continue after the bundle (`gjsify test`'s multi-runtime loop) MUST leave this false; an
|
|
24
|
+
* unconditional exit truncated `gjsify test` after the first gjs bundle.
|
|
29
25
|
*/
|
|
30
26
|
exitOnSuccess?: boolean;
|
|
31
27
|
/**
|
|
32
|
-
* Suppress the `$ <env> gjs -m …` echo.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* chain invokes a dozen times (`utils/node-script.ts`).
|
|
28
|
+
* Suppress the `$ <env> gjs -m …` echo. Worth its ~2 kB of env prefix for a one-shot
|
|
29
|
+
* `gjsify run <bundle>`; pure noise for a build script invoked a dozen times per chain
|
|
30
|
+
* (`utils/node-script.ts`).
|
|
36
31
|
*/
|
|
37
32
|
quiet?: boolean;
|
|
38
33
|
}
|
package/lib/utils/run-gjs.js
CHANGED
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
// Shared utility for running a GJS bundle with native package env vars.
|
|
2
2
|
// Used by `gjsify run`, `gjsify dlx`, and the showcase command (via dlx).
|
|
3
3
|
//
|
|
4
|
-
// Detection runs
|
|
5
|
-
//
|
|
4
|
+
// Detection runs `detectNativePackages` from two starting points and merges by package name
|
|
5
|
+
// (CWD shadows bundle): `process.cwd()` for native deps in the user's project, and
|
|
6
|
+
// `dirname(bundlePath)` for those in whatever node_modules the bundle lives in — critical for
|
|
7
|
+
// `gjsify dlx`, where the bundle sits under `~/.cache/gjsify/dlx/<sha>/…` and the user's CWD
|
|
8
|
+
// is unrelated. The bundle-side walk also catches transitive deps' typelibs.
|
|
6
9
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// 2. dirname(bundlePath) — picks up native deps in whatever node_modules
|
|
10
|
-
// the bundle lives in. Critical for `gjsify dlx`
|
|
11
|
-
// where the bundle resides in
|
|
12
|
-
// `~/.cache/gjsify/dlx/<sha>/.../node_modules/<pkg>/dist/`
|
|
13
|
-
// and the user's CWD is unrelated. The bundle-side
|
|
14
|
-
// walk also catches transitive deps' typelibs.
|
|
15
|
-
//
|
|
16
|
-
// Env composition is split out as `computeNativeEnvForBundle()` — a pure
|
|
17
|
-
// function that takes a bundle path + cwd and returns the env it would inject.
|
|
18
|
-
// This lets the e2e tests assert the env without spawning gjs.
|
|
10
|
+
// Env composition is split out as the pure `computeNativeEnvForBundle()` so the e2e tests can
|
|
11
|
+
// assert the env without spawning gjs.
|
|
19
12
|
import { spawn } from 'node:child_process';
|
|
20
13
|
import { dirname, resolve } from 'node:path';
|
|
21
14
|
import { detectNativePackages, buildNativeEnv } from './detect-native-packages.js';
|
|
22
15
|
/**
|
|
23
|
-
* Pure env computation
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see `buildNativeEnv`.
|
|
16
|
+
* Pure env computation: the typelib + shared-library search paths {@link runGjsBundle} would
|
|
17
|
+
* inject into the spawned `gjs`, plus the formatted prefix for the `$ …` echo. Which library
|
|
18
|
+
* variable is set is host-dependent (`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / `PATH`) — see
|
|
19
|
+
* `buildNativeEnv`.
|
|
28
20
|
*/
|
|
29
21
|
export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd(), inherited = process.env) {
|
|
30
22
|
const resolvedBundle = resolve(bundlePath);
|
|
@@ -32,23 +24,16 @@ export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd(), inher
|
|
|
32
24
|
const bundlePackages = detectNativePackages(dirname(resolvedBundle));
|
|
33
25
|
const seen = new Set(cwdPackages.map((p) => p.name));
|
|
34
26
|
const nativePackages = [...cwdPackages, ...bundlePackages.filter((p) => !seen.has(p.name))];
|
|
35
|
-
// `inherited` feeds BOTH halves —
|
|
36
|
-
//
|
|
37
|
-
// the
|
|
38
|
-
//
|
|
39
|
-
// look up a key that is not there and call every run a change.
|
|
27
|
+
// `inherited` feeds BOTH halves — composition and comparison. Split them and the win32
|
|
28
|
+
// branch disagrees with itself: `buildNativeEnv` writes the library variable back under
|
|
29
|
+
// the host's own spelling (`Path` in a stock env block), so a comparison keyed on the
|
|
30
|
+
// canonical `PATH` would look up a key that is not there and call every run a change.
|
|
40
31
|
const env = buildNativeEnv(nativePackages, { env: inherited });
|
|
41
|
-
// Show only what this CLI actually CHANGED
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// variable
|
|
45
|
-
//
|
|
46
|
-
// Harmless on Linux, where `LD_LIBRARY_PATH` is usually unset and the empty
|
|
47
|
-
// filter caught it. On Windows the library variable IS `PATH`, which is
|
|
48
|
-
// never empty, so every run printed a ~2 kB dump of the host PATH in front
|
|
49
|
-
// of a command the user is invited to copy — burying the command, and
|
|
50
|
-
// claiming a change that never happened. Comparing against the inherited
|
|
51
|
-
// value is what makes the echo honest on both.
|
|
32
|
+
// Show only what this CLI actually CHANGED: `buildNativeEnv` prepends to the inherited
|
|
33
|
+
// value unconditionally, so with no native packages it hands back the host's own variable
|
|
34
|
+
// verbatim. Harmless on Linux, where `LD_LIBRARY_PATH` is usually unset; on Windows the
|
|
35
|
+
// library variable IS `PATH`, never empty, so every run printed a ~2 kB dump of the host
|
|
36
|
+
// PATH in front of a command the user is invited to copy.
|
|
52
37
|
const envPrefix = Object.entries(env)
|
|
53
38
|
.filter(([key, value]) => value !== undefined && value !== '' && value !== inherited[key])
|
|
54
39
|
.map(([key, value]) => `${key}=${value}`)
|
|
@@ -62,24 +47,16 @@ export async function runGjsBundle(bundlePath, extraArgs = [], options = {}) {
|
|
|
62
47
|
...nativeEnv,
|
|
63
48
|
};
|
|
64
49
|
const gjsArgs = ['-m', bundlePath, ...extraArgs];
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// actually set any (i.e. native gjsify packages were detected).
|
|
68
|
-
// Use stderr so that children speaking a protocol on stdout (e.g. an
|
|
69
|
-
// MCP stdio server) receive an uncontaminated stdout stream.
|
|
50
|
+
// Echo the exact command so users can copy-paste it and run gjs without the wrapper.
|
|
51
|
+
// stderr, so children speaking a protocol on stdout (an MCP stdio server) keep a clean one.
|
|
70
52
|
const gjsCommand = ['gjs', ...gjsArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
|
|
71
53
|
if (!options.quiet)
|
|
72
54
|
console.error(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
|
|
73
55
|
const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
|
|
74
56
|
let failed = false;
|
|
75
|
-
// The CHILD's own exit code,
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
// Collapsing every failure to 1 lost information a caller acts on: a script
|
|
79
|
-
// run through `gjsify run --node-script` reports ITS status (the bootstrap
|
|
80
|
-
// scripts all end `process.exit(r.status ?? 1)`, forwarding a child's code),
|
|
81
|
-
// and a chain that reads `$?` to distinguish "tool failed" from "tool says
|
|
82
|
-
// no" saw the same 1 for both.
|
|
57
|
+
// The CHILD's own exit code, re-raised verbatim; a spawn error (no `gjs` on PATH) has none
|
|
58
|
+
// of its own and falls back to 1. Collapsing every failure to 1 lost information callers
|
|
59
|
+
// act on — a chain reading `$?` to tell "tool failed" from "tool says no" saw 1 for both.
|
|
83
60
|
let childCode = 1;
|
|
84
61
|
await new Promise((resolvePromise, reject) => {
|
|
85
62
|
child.on('close', (code) => {
|
|
@@ -97,14 +74,12 @@ export async function runGjsBundle(bundlePath, extraArgs = [], options = {}) {
|
|
|
97
74
|
failed = true;
|
|
98
75
|
});
|
|
99
76
|
if (failed) {
|
|
100
|
-
// Set `process.exitCode` synchronously BEFORE exiting: under GJS
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// `test:gjs` in the `test` script chain (and, via `gjsify foreach test`,
|
|
107
|
-
// in CI). The `return` guards against a fall-through second `exit`.
|
|
77
|
+
// Set `process.exitCode` synchronously BEFORE exiting: under GJS `process.exit()` is
|
|
78
|
+
// deferred (the GLib main loop is armed), so under run.ts's in-process script dispatch
|
|
79
|
+
// execution returns to the caller, which propagates by reading `process.exitCode`.
|
|
80
|
+
// Without it, `gjsify run <script>` wrapping `gjsify run <bundle>` swallowed a non-zero
|
|
81
|
+
// gjs exit and returned 0 — masking a failing `test:gjs` in the `test` chain, and via
|
|
82
|
+
// `gjsify foreach test` in CI. The `return` guards a fall-through second `exit`.
|
|
108
83
|
process.exitCode = childCode;
|
|
109
84
|
return process.exit(childCode);
|
|
110
85
|
}
|
|
@@ -2,20 +2,16 @@ export interface RunLifecycleScriptOptions {
|
|
|
2
2
|
/** When true, do not throw on missing scripts — return `false` instead. */
|
|
3
3
|
optional?: boolean;
|
|
4
4
|
/**
|
|
5
|
-
* Stdio
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `gjsify pack --json` and `gjsify publish` so the parent's stdout stays
|
|
9
|
-
* a clean JSON stream (script log lines would otherwise corrupt the
|
|
10
|
-
* machine-readable output that callers `JSON.parse`).
|
|
5
|
+
* Stdio for the spawned script; default `'inherit'`. `'inherit-stderr'` redirects the
|
|
6
|
+
* child's stdout → parent's stderr, used by `gjsify pack --json` and `gjsify publish` so
|
|
7
|
+
* script log lines cannot corrupt the JSON stream callers `JSON.parse`.
|
|
11
8
|
*/
|
|
12
9
|
stdio?: 'inherit' | 'inherit-stderr' | 'pipe' | 'ignore';
|
|
13
10
|
/** Extra environment variables layered on top of the defaults. */
|
|
14
11
|
env?: Record<string, string>;
|
|
15
12
|
}
|
|
16
13
|
/**
|
|
17
|
-
* Run
|
|
18
|
-
*
|
|
19
|
-
* `optional: true` and the script is missing. Throws on non-zero exit.
|
|
14
|
+
* Run the lifecycle script `pkg.scripts[name]` from `wsDir`. `true` when it existed and exited
|
|
15
|
+
* 0, `false` when it is missing and `optional` is set; throws on a non-zero exit.
|
|
20
16
|
*/
|
|
21
17
|
export declare function runLifecycleScript(wsDir: string, pkg: Record<string, unknown>, name: string, opts?: RunLifecycleScriptOptions): Promise<boolean>;
|
|
@@ -2,47 +2,31 @@
|
|
|
2
2
|
// `postpack`, `postpublish`, …) from inside a `gjsify pack` / `gjsify
|
|
3
3
|
// publish` flow.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// case where pack/publish needs the lifecycle script to finish, then keep
|
|
9
|
-
// running its own logic. This helper resolves to a Promise that settles
|
|
10
|
-
// when the child exits.
|
|
5
|
+
// Separate from `gjsify run` because that ends with `process.exit(<code>)` so it can act as a
|
|
6
|
+
// CLI entrypoint like `yarn run`. Pack/publish instead needs the lifecycle script to finish and
|
|
7
|
+
// then keep running its own logic, so this settles a Promise when the child exits.
|
|
11
8
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// that spawns asynchronously and deliberately does NOT exit leaves the loop
|
|
22
|
-
// armed forever: `gjsify pack` on a package with a `prepack` wrote its tarball,
|
|
23
|
-
// printed its complete JSON, and then parked at 0% CPU until killed at 5m30s.
|
|
24
|
-
// A package with no lifecycle script exited in under a second, and the same
|
|
25
|
-
// pack under Node exited in 0.68s.
|
|
26
|
-
//
|
|
27
|
-
// `utils/spawn.ts` already owns that contract and makes every call site DECLARE
|
|
28
|
-
// which side of it they are on. This helper was the one spawn in the CLI that
|
|
29
|
-
// bypassed it — the seventh of the "six near-identical wrappers" that file was
|
|
30
|
-
// written to consolidate. It now goes through it with `completion: 'return'`,
|
|
31
|
-
// which is what "settle, then keep running our own logic" means.
|
|
9
|
+
// NOT calling `process.exit()` is what ARMS a hang, not what avoids it (#1010):
|
|
10
|
+
// `@gjsify/child_process`'s async `spawn()` calls `ensureMainLoop()` unconditionally — the
|
|
11
|
+
// caller has no say — and that main-loop hook's blocking `g_main_loop_run()` is torn down only
|
|
12
|
+
// by `process.exit()`. A helper that spawns asynchronously and deliberately does not exit
|
|
13
|
+
// therefore leaves the loop armed forever: `gjsify pack` on a package with a `prepack` wrote
|
|
14
|
+
// its tarball, printed its complete JSON, then parked at 0% CPU until killed at 5m30s (no
|
|
15
|
+
// lifecycle script: under a second; the same pack under Node: 0.68s). `utils/spawn.ts` owns
|
|
16
|
+
// that contract and makes every call site DECLARE which side of it it is on; this goes through
|
|
17
|
+
// it with `completion: 'return'`.
|
|
32
18
|
//
|
|
33
19
|
// Matches yarn / npm script semantics:
|
|
34
20
|
// - `shell: true` so `&&` / `|` / env-var refs work
|
|
35
21
|
// - PATH prepended with `<wsDir>/node_modules/.bin` + monorepo-root bin
|
|
36
|
-
// - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version`
|
|
37
|
-
// env vars set
|
|
22
|
+
// - `npm_lifecycle_event` / `npm_package_name` / `npm_package_version` set
|
|
38
23
|
// - FORCE_COLOR=1 default unless caller overrides
|
|
39
24
|
import { delimiter, join } from 'node:path';
|
|
40
25
|
import { describeExit, spawnToCompletion } from './spawn.js';
|
|
41
26
|
import { findWorkspaceRoot } from './workspace-root.js';
|
|
42
27
|
/**
|
|
43
|
-
* Run
|
|
44
|
-
*
|
|
45
|
-
* `optional: true` and the script is missing. Throws on non-zero exit.
|
|
28
|
+
* Run the lifecycle script `pkg.scripts[name]` from `wsDir`. `true` when it existed and exited
|
|
29
|
+
* 0, `false` when it is missing and `optional` is set; throws on a non-zero exit.
|
|
46
30
|
*/
|
|
47
31
|
export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
|
|
48
32
|
const scripts = pkg.scripts ?? {};
|
|
@@ -57,11 +41,9 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
|
|
|
57
41
|
if (monorepoRoot && monorepoRoot !== wsDir) {
|
|
58
42
|
binDirs.push(join(monorepoRoot, 'node_modules', '.bin'));
|
|
59
43
|
}
|
|
60
|
-
// Match yarn / npm color-forcing
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// (CI logs, redirected output) because `process.stdout.isTTY` is
|
|
64
|
-
// false for the spawned child.
|
|
44
|
+
// Match yarn / npm color-forcing — see `runScript()` in run.ts. Without it, scripts calling
|
|
45
|
+
// biome / esbuild / tsc lose ANSI color in piped contexts (CI logs, redirected output)
|
|
46
|
+
// because `process.stdout.isTTY` is false for the spawned child.
|
|
65
47
|
const colorEnv = process.env.FORCE_COLOR !== undefined || process.env.NO_COLOR !== undefined ? {} : { FORCE_COLOR: '1' };
|
|
66
48
|
const env = {
|
|
67
49
|
...process.env,
|
|
@@ -72,16 +54,15 @@ export async function runLifecycleScript(wsDir, pkg, name, opts = {}) {
|
|
|
72
54
|
npm_package_version: pkg.version ?? '',
|
|
73
55
|
...opts.env,
|
|
74
56
|
};
|
|
75
|
-
// `'ignore'` has no `spawnToCompletion` equivalent and no call site
|
|
76
|
-
//
|
|
77
|
-
//
|
|
57
|
+
// `'ignore'` has no `spawnToCompletion` equivalent and no call site; folding it onto
|
|
58
|
+
// `'inherit'` would start printing where a caller asked for silence, so it is rejected
|
|
59
|
+
// rather than approximated.
|
|
78
60
|
if (opts.stdio === 'ignore') {
|
|
79
61
|
throw new Error('gjsify lifecycle-script: stdio "ignore" is not supported — see utils/spawn.ts');
|
|
80
62
|
}
|
|
81
63
|
const result = await spawnToCompletion(literal, [], {
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// keeps the armed main loop from outliving us — see the header.
|
|
64
|
+
// pack/publish waits for the script, then keeps running. Under GJS that is the blocking
|
|
65
|
+
// path, which is what keeps the armed main loop from outliving us — see the header.
|
|
85
66
|
completion: 'return',
|
|
86
67
|
cwd: wsDir,
|
|
87
68
|
env: env,
|