@gjsify/cli 0.24.1 → 0.24.2
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 +85 -81
- package/lib/commands/dlx.d.ts +15 -0
- package/lib/commands/dlx.js +3 -2
- package/lib/commands/install.d.ts +1 -1
- package/lib/commands/install.js +10 -19
- package/lib/commands/showcase.js +45 -5
- package/lib/utils/bin-shim.d.ts +49 -0
- package/lib/utils/bin-shim.js +105 -0
- package/lib/utils/install-backend-native.js +0 -1
- package/lib/utils/install-global.d.ts +0 -16
- package/lib/utils/install-global.js +19 -36
- package/lib/utils/normalize-bundler-options.js +0 -2
- package/package.json +17 -17
package/lib/commands/dlx.d.ts
CHANGED
|
@@ -16,6 +16,21 @@ export interface EnsureOpts {
|
|
|
16
16
|
registry?: string;
|
|
17
17
|
cacheMaxAge: number;
|
|
18
18
|
frozen: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Extra specs to install into the SAME cache tree as the requested package.
|
|
21
|
+
*
|
|
22
|
+
* A dlx tree is the package plus its own `dependencies`, which is right for
|
|
23
|
+
* the GJS path — the showcase's `--app gjs` bundle needs nothing else. The
|
|
24
|
+
* `--app node` bundle does: its `gi://` imports resolve through
|
|
25
|
+
* `@gjsify/node-gi`, which is a build-time concern for the showcase (a
|
|
26
|
+
* devDependency) but a RUNTIME one for whoever launches it off GJS. Nobody
|
|
27
|
+
* can act on the resulting "add @gjsify/node-gi as a dependency" error —
|
|
28
|
+
* the tree belongs to the cache, not to the user — so the launcher that
|
|
29
|
+
* knows the runtime supplies it.
|
|
30
|
+
*
|
|
31
|
+
* Part of the cache key, so the gjs and node trees never share an entry.
|
|
32
|
+
*/
|
|
33
|
+
extraSpecs?: readonly string[];
|
|
19
34
|
}
|
|
20
35
|
/**
|
|
21
36
|
* Resolve (installing + caching when remote) the on-disk directory of a package
|
package/lib/commands/dlx.js
CHANGED
|
@@ -106,7 +106,8 @@ export async function ensurePkgDir(parsed, opts) {
|
|
|
106
106
|
if (parsed.kind === 'local') {
|
|
107
107
|
return { pkgDir: parsed.path, cachedPkgName: null };
|
|
108
108
|
}
|
|
109
|
-
const
|
|
109
|
+
const specs = [parsed.spec, ...(opts.extraSpecs ?? [])];
|
|
110
|
+
const cacheKey = createCacheKey({ packages: specs });
|
|
110
111
|
const cacheDir = cacheDirFor(cacheKey);
|
|
111
112
|
const cached = opts.cacheMaxAge > 0 ? getValidCachedPkg(cacheDir, opts.cacheMaxAge) : undefined;
|
|
112
113
|
if (cached) {
|
|
@@ -124,7 +125,7 @@ export async function ensurePkgDir(parsed, opts) {
|
|
|
124
125
|
const progress = makeProgressReporter({ enabled: !opts.verbose });
|
|
125
126
|
await installPackages({
|
|
126
127
|
prefix: prepareDir,
|
|
127
|
-
specs
|
|
128
|
+
specs,
|
|
128
129
|
verbose: opts.verbose,
|
|
129
130
|
registry: opts.registry,
|
|
130
131
|
// Cache-prepare dirs are scoped per cache key, so writing a lockfile
|
|
@@ -14,7 +14,7 @@ interface InstallOptions {
|
|
|
14
14
|
timeout: number;
|
|
15
15
|
}
|
|
16
16
|
export declare const installCommand: Command<unknown, InstallOptions>;
|
|
17
|
-
export declare function buildBinShim(wsLocation: string, nodeTarget?: string, gjsTarget?: string, nativePrebuildDirs?: string[], platform?: string): string;
|
|
17
|
+
export declare function buildBinShim(wsLocation: string, nodeTarget?: string, gjsTarget?: string, nativePrebuildDirs?: string[], platform?: string, scanRoot?: string): string;
|
|
18
18
|
/**
|
|
19
19
|
* `.cmd` / `.ps1` companions for {@link buildBinShim}.
|
|
20
20
|
*
|
package/lib/commands/install.js
CHANGED
|
@@ -34,8 +34,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
34
34
|
import { gjsExit } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
35
35
|
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
36
36
|
import { buildInstallCommand, detectPackageManager, runMinimalChecks } from '../utils/check-system-deps.js';
|
|
37
|
-
import { detectNativePackages
|
|
38
|
-
import { buildLauncherShims } from '../utils/bin-shim.js';
|
|
37
|
+
import { detectNativePackages } from '../utils/detect-native-packages.js';
|
|
38
|
+
import { buildLauncherShims, buildNativeEnvPreamble } from '../utils/bin-shim.js';
|
|
39
39
|
import { installPackages, makeProgressReporter } from '../utils/install-backend.js';
|
|
40
40
|
import { atomicWriteStrict } from '../utils/install-cache-fs.js';
|
|
41
41
|
import { acquireInstallLock } from '../utils/install-lock.js';
|
|
@@ -957,9 +957,7 @@ function writeWorkspaceBinShims(cwd, workspaces) {
|
|
|
957
957
|
/* fine */
|
|
958
958
|
}
|
|
959
959
|
}
|
|
960
|
-
writeFileSync(linkPath, buildBinShim(ws.location, nodeTarget, gjsTarget, nativePrebuildDirs), {
|
|
961
|
-
mode: 0o755,
|
|
962
|
-
});
|
|
960
|
+
writeFileSync(linkPath, buildBinShim(ws.location, nodeTarget, gjsTarget, nativePrebuildDirs, process.platform, cwd), { mode: 0o755 });
|
|
963
961
|
chmodSync(linkPath, 0o755);
|
|
964
962
|
// Windows executes neither an extension-less file nor a `#!` line —
|
|
965
963
|
// the sh shim above is only reachable from git-bash/MSYS/WSL. Write
|
|
@@ -1045,26 +1043,19 @@ function scopedOverrideFor(ws, depName, extracted) {
|
|
|
1045
1043
|
}
|
|
1046
1044
|
return undefined;
|
|
1047
1045
|
}
|
|
1048
|
-
export function buildBinShim(wsLocation, nodeTarget, gjsTarget, nativePrebuildDirs = [], platform = process.platform) {
|
|
1046
|
+
export function buildBinShim(wsLocation, nodeTarget, gjsTarget, nativePrebuildDirs = [], platform = process.platform, scanRoot = wsLocation) {
|
|
1049
1047
|
const nodeAbs = nodeTarget ? join(wsLocation, nodeTarget) : null;
|
|
1050
1048
|
const gjsAbs = gjsTarget ? join(wsLocation, gjsTarget) : null;
|
|
1051
1049
|
// GJS-only env preamble — Node ignores GI_TYPELIB_PATH so we scope the
|
|
1052
1050
|
// export to the gjs branch, keeping the shim minimal when no native pkgs
|
|
1053
1051
|
// exist or only the Node bin is in play.
|
|
1054
1052
|
//
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
|
|
1060
|
-
const gjsPreamble = nativePrebuildDirs
|
|
1061
|
-
? ''
|
|
1062
|
-
: (() => {
|
|
1063
|
-
const joined = `'${nativePrebuildDirs.join(':').replace(/'/g, `'\\''`)}'`;
|
|
1064
|
-
return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}\n` +
|
|
1065
|
-
`${libVar}=${joined}\${${libVar}:+":$${libVar}"}\n` +
|
|
1066
|
-
`export GI_TYPELIB_PATH ${libVar}\n`);
|
|
1067
|
-
})();
|
|
1053
|
+
// `scanRoot` is the INSTALL prefix (the tree holding `node_modules/.bin`),
|
|
1054
|
+
// not the workspace package dir: the preamble globs its `node_modules` at
|
|
1055
|
+
// launch time instead of embedding what was installed the day the shim was
|
|
1056
|
+
// written, so `gjsify install <some>-native` afterwards is picked up without
|
|
1057
|
+
// a re-link. See `buildNativeEnvPreamble`.
|
|
1058
|
+
const gjsPreamble = buildNativeEnvPreamble(scanRoot, nativePrebuildDirs, { platform });
|
|
1068
1059
|
if (nodeAbs && gjsAbs) {
|
|
1069
1060
|
// GJS-FIRST: prefer the committed Node-free GJS bundle when `gjs` is on
|
|
1070
1061
|
// PATH AND the bundle exists; fall back to the Node entry otherwise (a
|
package/lib/commands/showcase.js
CHANGED
|
@@ -18,6 +18,27 @@ function readCliVersion() {
|
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* `[gjsify <version>] ` for the banner — the CLI naming ITSELF.
|
|
23
|
+
*
|
|
24
|
+
* `showcase` pins the showcase package to the CLI's own version, so a stale CLI
|
|
25
|
+
* silently runs a stale showcase, and the only version in the output was the
|
|
26
|
+
* showcase's. That is not a hypothetical: every runner that caches an unpinned
|
|
27
|
+
* bin reuses whatever it resolved once (`npx @gjsify/cli` → a 0.4.x `_npx`
|
|
28
|
+
* entry; `deno run npm:@gjsify/cli` → whatever sits in `DENO_DIR`), so the same
|
|
29
|
+
* command produced 0.24.1 under bunx, 0.4.25 under npx and 0.23.0 under deno on
|
|
30
|
+
* ONE machine on ONE day — and the two old ones failed in ways that read as
|
|
31
|
+
* gjsify bugs. gjsify cannot flush somebody else's cache, but it can stop the
|
|
32
|
+
* output from hiding which gjsify is talking.
|
|
33
|
+
*/
|
|
34
|
+
function runningAs(cliVersion) {
|
|
35
|
+
return cliVersion ? `[gjsify ${cliVersion}] ` : '';
|
|
36
|
+
}
|
|
37
|
+
/** Pinned invocations for the three runners that cache an unpinned bin. */
|
|
38
|
+
const PIN_HINT = 'A package runner reuses a CACHED copy of an unpinned bin. Force the current one:\n' +
|
|
39
|
+
' npx @gjsify/cli@latest showcase <name>\n' +
|
|
40
|
+
' bunx @gjsify/cli@latest showcase <name>\n' +
|
|
41
|
+
' deno run -A --reload npm:@gjsify/cli@latest showcase <name>';
|
|
21
42
|
export const showcaseCommand = {
|
|
22
43
|
command: 'showcase [name]',
|
|
23
44
|
description: 'List or run curated gjsify showcase applications.',
|
|
@@ -146,7 +167,7 @@ export const showcaseCommand = {
|
|
|
146
167
|
// newer CLI assumes (the `@gjsify/http-soup-bridge` regression
|
|
147
168
|
// reported against `@gjsify/cli@0.3.17`).
|
|
148
169
|
const dlxSpec = cliVersion ? `${showcase.packageName}@${cliVersion}` : showcase.packageName;
|
|
149
|
-
console.log(`Running showcase: ${showcase.name} (via gjsify dlx ${dlxSpec})\n`);
|
|
170
|
+
console.log(`Running showcase: ${showcase.name} ${runningAs(cliVersion)}(via gjsify dlx ${dlxSpec})\n`);
|
|
150
171
|
// Dispatch `dlx` IN-PROCESS (not `spawn(process.execPath, [cliBin, …])`):
|
|
151
172
|
// under the committed GJS bundle `process.execPath` is `gjs` and
|
|
152
173
|
// `../index.js` is the Node entry, so the spawn ran `gjs cli.gjs.mjs
|
|
@@ -169,7 +190,7 @@ export const showcaseCommand = {
|
|
|
169
190
|
* in-tree build), falling back to `gjsify dlx`'s install-into-cache for a
|
|
170
191
|
* published showcase. Returns the directory containing the package.json.
|
|
171
192
|
*/
|
|
172
|
-
async function resolveShowcaseDir(showcase, cliVersion) {
|
|
193
|
+
async function resolveShowcaseDir(showcase, cliVersion, extraSpecs = []) {
|
|
173
194
|
// Local-first: a workspace showcase resolves via its own package name.
|
|
174
195
|
try {
|
|
175
196
|
const req = createRequire(import.meta.url);
|
|
@@ -185,6 +206,7 @@ async function resolveShowcaseDir(showcase, cliVersion) {
|
|
|
185
206
|
verbose: false,
|
|
186
207
|
cacheMaxAge: 60 * 24 * 7,
|
|
187
208
|
frozen: false,
|
|
209
|
+
extraSpecs,
|
|
188
210
|
});
|
|
189
211
|
return pkgDir;
|
|
190
212
|
}
|
|
@@ -197,7 +219,16 @@ async function resolveShowcaseDir(showcase, cliVersion) {
|
|
|
197
219
|
async function runShowcaseOnRuntime(showcase, runtime, cliVersion) {
|
|
198
220
|
let pkgDir;
|
|
199
221
|
try {
|
|
200
|
-
|
|
222
|
+
// `@gjsify/node-gi` is the `gi://` bridge the `--app node` bundle
|
|
223
|
+
// resolves at RUNTIME. Showcases carry it as a devDependency (it is a
|
|
224
|
+
// build input for them), so a dlx tree — the package plus its own
|
|
225
|
+
// `dependencies` — does not contain it and the launch died with "add
|
|
226
|
+
// @gjsify/node-gi as a dependency": advice the user cannot act on,
|
|
227
|
+
// since the tree is the CLI's cache. The launcher knows the runtime, so
|
|
228
|
+
// it supplies the bridge. Pinned to the CLI's version like the showcase
|
|
229
|
+
// itself, so the bridge and the bundle move together.
|
|
230
|
+
const nodeGiSpec = cliVersion ? `@gjsify/node-gi@${cliVersion}` : '@gjsify/node-gi';
|
|
231
|
+
pkgDir = await resolveShowcaseDir(showcase, cliVersion, [nodeGiSpec]);
|
|
201
232
|
}
|
|
202
233
|
catch (err) {
|
|
203
234
|
console.error(`Could not resolve showcase "${showcase.name}": ${err.message}`);
|
|
@@ -222,10 +253,19 @@ async function runShowcaseOnRuntime(showcase, runtime, cliVersion) {
|
|
|
222
253
|
entry = resolveNodeEntry(pkgDir);
|
|
223
254
|
}
|
|
224
255
|
catch (err) {
|
|
225
|
-
|
|
256
|
+
// The showcase promised this runtime and did not ship the bundle. Two
|
|
257
|
+
// causes, and the user can only act on one of them, so name both: the
|
|
258
|
+
// package version is genuinely broken (fixed by a newer one), or THIS
|
|
259
|
+
// CLI is a cached old copy that pinned a genuinely broken old showcase.
|
|
260
|
+
const version = typeof pkg.name === 'string' ? `${pkg.name}@${cliVersion ?? '?'}` : showcase.packageName;
|
|
261
|
+
console.error(`Cannot run showcase "${showcase.name}" on ${runtime}: ${err.message}\n\n` +
|
|
262
|
+
` ${version} declares --runtime ${runtime} but ships no \`--app node\` bundle, so that\n` +
|
|
263
|
+
` version of the showcase cannot run there. You are running gjsify ${cliVersion ?? '(unknown)'},\n` +
|
|
264
|
+
` and \`showcase\` pins the showcase to the CLI's own version.\n\n ${PIN_HINT}\n\n` +
|
|
265
|
+
` Or run it on GJS, which every showcase ships: gjsify showcase ${showcase.name} --runtime gjs`);
|
|
226
266
|
return process.exit(1);
|
|
227
267
|
}
|
|
228
|
-
console.log(`Running showcase: ${showcase.name} on ${runtime} (${entry})\n`);
|
|
268
|
+
console.log(`Running showcase: ${showcase.name} ${runningAs(cliVersion)}on ${runtime} (${entry})\n`);
|
|
229
269
|
// Terminal call — exit on success.
|
|
230
270
|
await runRuntimeBundle(runtime, entry, [], { exitOnSuccess: true });
|
|
231
271
|
}
|
package/lib/utils/bin-shim.d.ts
CHANGED
|
@@ -64,3 +64,52 @@ export declare function buildLauncherShims(opts: {
|
|
|
64
64
|
cmd: string;
|
|
65
65
|
ps1: string;
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* The `sh` preamble a GJS launcher needs so `imports.gi.X` resolves against the
|
|
69
|
+
* `@gjsify/*` native prebuilds installed next to it.
|
|
70
|
+
*
|
|
71
|
+
* WHY IT SCANS AT RUN TIME INSTEAD OF BAKING A LIST
|
|
72
|
+
*
|
|
73
|
+
* The typelib/library lookup happens inside the GJS runtime, before a single
|
|
74
|
+
* line of the CLI bundle runs, so the env MUST be set by the launcher — the CLI
|
|
75
|
+
* cannot repair it from the inside. The launcher used to bake the directories
|
|
76
|
+
* `detectNativePackages()` found at INSTALL time, which makes the env a SNAPSHOT
|
|
77
|
+
* of one moment:
|
|
78
|
+
*
|
|
79
|
+
* - a native package installed into the same prefix LATER (`gjsify install -g
|
|
80
|
+
* @gjsify/<x>-native`, a project `gjsify install` that adds one) is invisible
|
|
81
|
+
* until something happens to rewrite the launcher;
|
|
82
|
+
* - and if the scan comes back empty for any reason, the launcher is written
|
|
83
|
+
* with NO preamble at all and nothing says so. The failure then surfaces
|
|
84
|
+
* much later and somewhere else entirely — `gjsify build` dying with "no
|
|
85
|
+
* usable bundler engine under GJS" in an unrelated project, which reads as a
|
|
86
|
+
* broken install rather than a stale launcher. That is what shipped in
|
|
87
|
+
* v0.24.1: a `~/.local/bin/gjsify` with a bare `exec gjs -m …` line while
|
|
88
|
+
* `@gjsify/rolldown-native` sat correctly installed in the prefix.
|
|
89
|
+
*
|
|
90
|
+
* Deriving the env from disk on every launch removes both: the launcher states
|
|
91
|
+
* WHERE to look rather than WHAT was there, so it cannot go stale, and an empty
|
|
92
|
+
* result is a fact about the current prefix instead of a silently frozen one.
|
|
93
|
+
* The cost is a handful of shell globs per launch (they are builtins, and a
|
|
94
|
+
* pattern that matches nothing stays literal and is rejected by `[ -d ]`).
|
|
95
|
+
*
|
|
96
|
+
* `bakedDirs` covers what a run-time scan of `scanRoot` structurally cannot see:
|
|
97
|
+
* `detectNativePackages()` walks UP the directory chain, so in a hoisted layout
|
|
98
|
+
* a prebuild may live in an ancestor's `node_modules`. Those are still embedded
|
|
99
|
+
* verbatim; only the primary root is dynamic. Duplicates are harmless (both
|
|
100
|
+
* variables are search paths).
|
|
101
|
+
*
|
|
102
|
+
* Windows is deliberately excluded: there the `sh` file is only reachable from
|
|
103
|
+
* git-bash/MSYS, the real launchers are the `.cmd`/`.ps1` companions built by
|
|
104
|
+
* {@link buildLauncherShims} from the same baked list, and `PATH` doubles as the
|
|
105
|
+
* DLL search path with `;` separators. It keeps the snapshot behaviour.
|
|
106
|
+
*
|
|
107
|
+
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
108
|
+
* @param bakedDirs Prebuild dirs found at write time; entries outside `scanRoot`
|
|
109
|
+
* are embedded (entries inside it are dropped — the scan finds them).
|
|
110
|
+
* @returns the preamble, or `''` when there is nothing to export.
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildNativeEnvPreamble(scanRoot: string, bakedDirs?: readonly string[], target?: {
|
|
113
|
+
platform?: string;
|
|
114
|
+
arch?: string;
|
|
115
|
+
}): string;
|
package/lib/utils/bin-shim.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// All three re-derive the interpreter from the target's shebang, so a bin
|
|
25
25
|
// declaring `#!/usr/bin/env node` and one declaring `#!/usr/bin/env -S gjs -m`
|
|
26
26
|
// both work without the caller knowing which it is.
|
|
27
|
+
import { libraryPathVar, prebuildDirCandidates } from './detect-native-packages.js';
|
|
27
28
|
// Matches `#!<prog> <args>` and `#!/usr/bin/env [-S] [K=V ...] <prog> <args>`.
|
|
28
29
|
// Verbatim from cmd-shim's `shebangExpr`.
|
|
29
30
|
const SHEBANG_RE = /^#!\s*(?:\/usr\/bin\/env\s+(?:-S\s+)?((?:[^ \t=]+=[^ \t=]+\s+)*))?([^ \t]+)(.*)$/;
|
|
@@ -244,3 +245,107 @@ export function buildLauncherShims(opts) {
|
|
|
244
245
|
'exit $LASTEXITCODE\n';
|
|
245
246
|
return { cmd, ps1 };
|
|
246
247
|
}
|
|
248
|
+
/** POSIX `sh` single-quoting — the one place the escape lives. */
|
|
249
|
+
function shQuote(s) {
|
|
250
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The `sh` preamble a GJS launcher needs so `imports.gi.X` resolves against the
|
|
254
|
+
* `@gjsify/*` native prebuilds installed next to it.
|
|
255
|
+
*
|
|
256
|
+
* WHY IT SCANS AT RUN TIME INSTEAD OF BAKING A LIST
|
|
257
|
+
*
|
|
258
|
+
* The typelib/library lookup happens inside the GJS runtime, before a single
|
|
259
|
+
* line of the CLI bundle runs, so the env MUST be set by the launcher — the CLI
|
|
260
|
+
* cannot repair it from the inside. The launcher used to bake the directories
|
|
261
|
+
* `detectNativePackages()` found at INSTALL time, which makes the env a SNAPSHOT
|
|
262
|
+
* of one moment:
|
|
263
|
+
*
|
|
264
|
+
* - a native package installed into the same prefix LATER (`gjsify install -g
|
|
265
|
+
* @gjsify/<x>-native`, a project `gjsify install` that adds one) is invisible
|
|
266
|
+
* until something happens to rewrite the launcher;
|
|
267
|
+
* - and if the scan comes back empty for any reason, the launcher is written
|
|
268
|
+
* with NO preamble at all and nothing says so. The failure then surfaces
|
|
269
|
+
* much later and somewhere else entirely — `gjsify build` dying with "no
|
|
270
|
+
* usable bundler engine under GJS" in an unrelated project, which reads as a
|
|
271
|
+
* broken install rather than a stale launcher. That is what shipped in
|
|
272
|
+
* v0.24.1: a `~/.local/bin/gjsify` with a bare `exec gjs -m …` line while
|
|
273
|
+
* `@gjsify/rolldown-native` sat correctly installed in the prefix.
|
|
274
|
+
*
|
|
275
|
+
* Deriving the env from disk on every launch removes both: the launcher states
|
|
276
|
+
* WHERE to look rather than WHAT was there, so it cannot go stale, and an empty
|
|
277
|
+
* result is a fact about the current prefix instead of a silently frozen one.
|
|
278
|
+
* The cost is a handful of shell globs per launch (they are builtins, and a
|
|
279
|
+
* pattern that matches nothing stays literal and is rejected by `[ -d ]`).
|
|
280
|
+
*
|
|
281
|
+
* `bakedDirs` covers what a run-time scan of `scanRoot` structurally cannot see:
|
|
282
|
+
* `detectNativePackages()` walks UP the directory chain, so in a hoisted layout
|
|
283
|
+
* a prebuild may live in an ancestor's `node_modules`. Those are still embedded
|
|
284
|
+
* verbatim; only the primary root is dynamic. Duplicates are harmless (both
|
|
285
|
+
* variables are search paths).
|
|
286
|
+
*
|
|
287
|
+
* Windows is deliberately excluded: there the `sh` file is only reachable from
|
|
288
|
+
* git-bash/MSYS, the real launchers are the `.cmd`/`.ps1` companions built by
|
|
289
|
+
* {@link buildLauncherShims} from the same baked list, and `PATH` doubles as the
|
|
290
|
+
* DLL search path with `;` separators. It keeps the snapshot behaviour.
|
|
291
|
+
*
|
|
292
|
+
* @param scanRoot Directory whose `node_modules` is globbed at launch time.
|
|
293
|
+
* @param bakedDirs Prebuild dirs found at write time; entries outside `scanRoot`
|
|
294
|
+
* are embedded (entries inside it are dropped — the scan finds them).
|
|
295
|
+
* @returns the preamble, or `''` when there is nothing to export.
|
|
296
|
+
*/
|
|
297
|
+
export function buildNativeEnvPreamble(scanRoot, bakedDirs = [], target = {}) {
|
|
298
|
+
const platform = target.platform ?? process.platform;
|
|
299
|
+
const arch = target.arch ?? process.arch;
|
|
300
|
+
const { name: libVar, separator } = libraryPathVar(platform);
|
|
301
|
+
if (platform === 'win32') {
|
|
302
|
+
if (bakedDirs.length === 0)
|
|
303
|
+
return '';
|
|
304
|
+
const joined = shQuote(bakedDirs.join(separator));
|
|
305
|
+
return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+"${separator}$GI_TYPELIB_PATH"}\n` +
|
|
306
|
+
`${libVar}=${joined}\${${libVar}:+"${separator}$${libVar}"}\n` +
|
|
307
|
+
`export GI_TYPELIB_PATH ${libVar}\n`);
|
|
308
|
+
}
|
|
309
|
+
// Only the ancestors' hits need embedding — anything under `scanRoot` is
|
|
310
|
+
// what the loop below finds, and re-embedding it would reintroduce the
|
|
311
|
+
// snapshot for exactly the packages the scan exists to keep current.
|
|
312
|
+
const nmRoot = `${scanRoot.replace(/\/+$/, '')}/node_modules/`;
|
|
313
|
+
const external = bakedDirs.filter((d) => !d.startsWith(nmRoot));
|
|
314
|
+
// The same `<os>-<arch>` spellings `resolvePrebuildDirName()` probes, in the
|
|
315
|
+
// same order: the canonical `${platform}-${arch}` plus the legacy uname one
|
|
316
|
+
// a tarball published before the rename still ships. Passing no declared
|
|
317
|
+
// platforms is correct here — the shell cannot read a package.json, and a
|
|
318
|
+
// package declaring ONLY a non-canonical spelling of this host has not
|
|
319
|
+
// existed since the audit made that state impossible.
|
|
320
|
+
const candidates = prebuildDirCandidates(platform, arch);
|
|
321
|
+
const patterns = [];
|
|
322
|
+
for (const candidate of candidates) {
|
|
323
|
+
// Scoped (`@gjsify/x`) and unscoped packages, matching `scanNodeModules`.
|
|
324
|
+
patterns.push(`${shQuote(scanRoot)}/node_modules/@*/*/prebuilds/${candidate}`);
|
|
325
|
+
patterns.push(`${shQuote(scanRoot)}/node_modules/*/prebuilds/${candidate}`);
|
|
326
|
+
}
|
|
327
|
+
const seed = external.length > 0 ? shQuote(external.join(separator)) : `''`;
|
|
328
|
+
// A directory match is not enough: `prebuilds/<os>-<arch>/` is ALSO the
|
|
329
|
+
// prebuildify convention, so a plain glob sweeps in `bare-fs`, `bare-os`
|
|
330
|
+
// and friends — packages with no typelib at all, which `detectNativePackages`
|
|
331
|
+
// never returned because it keyed on `gjsify.prebuilds`. Onto
|
|
332
|
+
// GI_TYPELIB_PATH that is merely noise; onto the LOADER path it is a
|
|
333
|
+
// directory of foreign shared objects ahead of the system ones. The
|
|
334
|
+
// `.typelib` probe restores the old precision from the filesystem alone —
|
|
335
|
+
// and it is exactly what the artifact invariant already guarantees every
|
|
336
|
+
// gjsify GI bridge ships (a library AND the typelib without which
|
|
337
|
+
// GI_TYPELIB_PATH resolves nothing).
|
|
338
|
+
return (`gjsify_np=${seed}\n` +
|
|
339
|
+
`for gjsify_d in ${patterns.join(' ')}; do\n` +
|
|
340
|
+
` [ -d "$gjsify_d" ] || continue\n` +
|
|
341
|
+
` for gjsify_t in "$gjsify_d"/*.typelib; do\n` +
|
|
342
|
+
` if [ -f "$gjsify_t" ]; then gjsify_np="\${gjsify_np:+$gjsify_np${separator}}$gjsify_d"; break; fi\n` +
|
|
343
|
+
` done\n` +
|
|
344
|
+
`done\n` +
|
|
345
|
+
`if [ -n "$gjsify_np" ]; then\n` +
|
|
346
|
+
` GI_TYPELIB_PATH="$gjsify_np\${GI_TYPELIB_PATH:+${separator}$GI_TYPELIB_PATH}"\n` +
|
|
347
|
+
` ${libVar}="$gjsify_np\${${libVar}:+${separator}$${libVar}}"\n` +
|
|
348
|
+
` export GI_TYPELIB_PATH ${libVar}\n` +
|
|
349
|
+
`fi\n` +
|
|
350
|
+
`unset gjsify_np gjsify_d gjsify_t\n`);
|
|
351
|
+
}
|
|
@@ -571,7 +571,6 @@ function findVisible(requesterPath, name, byPath) {
|
|
|
571
571
|
candidates.push(`${requesterPath}/node_modules/${name}`);
|
|
572
572
|
// Walk up: strip the last `/node_modules/<pkg>` segment and try again.
|
|
573
573
|
let p = requesterPath;
|
|
574
|
-
// eslint-disable-next-line no-constant-condition
|
|
575
574
|
while (true) {
|
|
576
575
|
// Find the deepest `/node_modules/<pkg>` in `p`, strip it.
|
|
577
576
|
const idx = p.lastIndexOf('/node_modules/');
|
|
@@ -86,22 +86,6 @@ export interface LinkedBin {
|
|
|
86
86
|
* simplicity and so we don't need to discriminate by runtime here.
|
|
87
87
|
*/
|
|
88
88
|
export declare function linkGlobalBins(packageNames: string[], layout: GlobalLayout): LinkedBin[];
|
|
89
|
-
/**
|
|
90
|
-
* Build the POSIX-sh `export` lines that prepend the given prebuild
|
|
91
|
-
* directories to GI_TYPELIB_PATH and the host's shared-library search path.
|
|
92
|
-
* Any pre-existing value inherited from the user's environment is preserved as
|
|
93
|
-
* a suffix so user-installed typelibs/libraries still resolve.
|
|
94
|
-
*
|
|
95
|
-
* The library variable is host-dependent — `LD_LIBRARY_PATH` on Linux,
|
|
96
|
-
* `DYLD_LIBRARY_PATH` on macOS (dyld never reads the ELF one), so a launcher
|
|
97
|
-
* written on a Mac used to export a variable nothing consults, leaving every
|
|
98
|
-
* `darwin-arm64` prebuild unloadable. `platform` is a parameter so both
|
|
99
|
-
* branches are unit-testable from either host.
|
|
100
|
-
*
|
|
101
|
-
* Returns the empty string when no prebuilds were found — avoids emitting an
|
|
102
|
-
* inert assignment in the launcher.
|
|
103
|
-
*/
|
|
104
|
-
export declare function buildLauncherEnvPreamble(prebuildsDirs: string[], platform?: string): string;
|
|
105
89
|
/** Returns `true` if `binDir` is on the user's PATH. */
|
|
106
90
|
export declare function binDirOnPath(binDir: string): boolean;
|
|
107
91
|
/**
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
import * as fs from 'node:fs';
|
|
24
24
|
import * as os from 'node:os';
|
|
25
25
|
import * as path from 'node:path';
|
|
26
|
-
import { detectNativePackages
|
|
27
|
-
import { buildLauncherShims, parseShebang } from './bin-shim.js';
|
|
26
|
+
import { detectNativePackages } from './detect-native-packages.js';
|
|
27
|
+
import { buildLauncherShims, buildNativeEnvPreamble, parseShebang } from './bin-shim.js';
|
|
28
28
|
/**
|
|
29
29
|
* Compute the canonical global install layout for the current user. Honours
|
|
30
30
|
* `XDG_DATA_HOME` (per the XDG Base Directory Spec) plus
|
|
@@ -143,18 +143,25 @@ export async function installGjsEnginePackages(prefix, version, opts = {}) {
|
|
|
143
143
|
export function linkGlobalBins(packageNames, layout) {
|
|
144
144
|
fs.mkdirSync(layout.binDir, { recursive: true });
|
|
145
145
|
const created = [];
|
|
146
|
-
//
|
|
147
|
-
// + shared libs)
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
146
|
+
// The launcher exports GI_TYPELIB_PATH / <loader path> for the @gjsify/*
|
|
147
|
+
// native prebuilds (Vala/GObject typelibs + shared libs) installed in this
|
|
148
|
+
// prefix, so `imports.gi.X` resolves at CLI startup — required for the
|
|
149
|
+
// bundler engine `@gjsify/rolldown-native` (without which `gjsify build`
|
|
150
|
+
// hard-fails under GJS) and for e.g. `@gjsify/terminal-native`, without
|
|
151
|
+
// which isTTY / columns / colors fall back to conservative defaults.
|
|
152
152
|
//
|
|
153
|
-
// `runGjsBundle()` does the same dance for `gjsify run <bundle>` at
|
|
154
|
-
//
|
|
155
|
-
//
|
|
153
|
+
// `runGjsBundle()` does the same dance for `gjsify run <bundle>` at runtime;
|
|
154
|
+
// here it has to be the launcher, because the typelib lookup happens inside
|
|
155
|
+
// the GJS runtime before the bundle's first line runs.
|
|
156
|
+
//
|
|
157
|
+
// The preamble SCANS the prefix at launch time rather than embedding what
|
|
158
|
+
// was there at install time — see `buildNativeEnvPreamble` for why a
|
|
159
|
+
// snapshot is the wrong shape. `nativePrebuildDirs` is still computed: the
|
|
160
|
+
// Windows `.cmd`/`.ps1` shims take the baked list, and a hit in an
|
|
161
|
+
// ANCESTOR's node_modules (which the walk finds and a single-root scan
|
|
162
|
+
// cannot) is embedded verbatim.
|
|
156
163
|
const nativePrebuildDirs = detectNativePackages(layout.prefix).map((p) => p.prebuildsDir);
|
|
157
|
-
const envPreamble =
|
|
164
|
+
const envPreamble = buildNativeEnvPreamble(layout.prefix, nativePrebuildDirs);
|
|
158
165
|
for (const pkgName of packageNames) {
|
|
159
166
|
const pkgDir = path.join(layout.prefix, 'node_modules', pkgName);
|
|
160
167
|
const pkgJsonPath = path.join(pkgDir, 'package.json');
|
|
@@ -237,30 +244,6 @@ export function linkGlobalBins(packageNames, layout) {
|
|
|
237
244
|
function shQuote(s) {
|
|
238
245
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
239
246
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Build the POSIX-sh `export` lines that prepend the given prebuild
|
|
242
|
-
* directories to GI_TYPELIB_PATH and the host's shared-library search path.
|
|
243
|
-
* Any pre-existing value inherited from the user's environment is preserved as
|
|
244
|
-
* a suffix so user-installed typelibs/libraries still resolve.
|
|
245
|
-
*
|
|
246
|
-
* The library variable is host-dependent — `LD_LIBRARY_PATH` on Linux,
|
|
247
|
-
* `DYLD_LIBRARY_PATH` on macOS (dyld never reads the ELF one), so a launcher
|
|
248
|
-
* written on a Mac used to export a variable nothing consults, leaving every
|
|
249
|
-
* `darwin-arm64` prebuild unloadable. `platform` is a parameter so both
|
|
250
|
-
* branches are unit-testable from either host.
|
|
251
|
-
*
|
|
252
|
-
* Returns the empty string when no prebuilds were found — avoids emitting an
|
|
253
|
-
* inert assignment in the launcher.
|
|
254
|
-
*/
|
|
255
|
-
export function buildLauncherEnvPreamble(prebuildsDirs, platform = process.platform) {
|
|
256
|
-
if (prebuildsDirs.length === 0)
|
|
257
|
-
return '';
|
|
258
|
-
const { name: libVar } = libraryPathVar(platform);
|
|
259
|
-
const joined = shQuote(prebuildsDirs.join(':'));
|
|
260
|
-
return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}\n` +
|
|
261
|
-
`${libVar}=${joined}\${${libVar}:+":$${libVar}"}\n` +
|
|
262
|
-
`export GI_TYPELIB_PATH ${libVar}\n`);
|
|
263
|
-
}
|
|
264
247
|
/** Parse a bin file's `#!` line; `null` when unreadable or not a shebang. */
|
|
265
248
|
function readShebangOf(file) {
|
|
266
249
|
try {
|
|
@@ -24,7 +24,6 @@ export function normalizeBundlerOptions(configData) {
|
|
|
24
24
|
raw['define'] !== null) {
|
|
25
25
|
if (!warnedDefineOnce) {
|
|
26
26
|
warnedDefineOnce = true;
|
|
27
|
-
// eslint-disable-next-line no-console
|
|
28
27
|
console.warn("[gjsify] WARNING: 'bundler.define' is not a valid Rolldown option and would be " +
|
|
29
28
|
"silently ignored — it has been auto-mapped to 'bundler.transform.define'. " +
|
|
30
29
|
"Move 'define' under 'bundler.transform.define' in your config to suppress " +
|
|
@@ -46,7 +45,6 @@ export function normalizeBundlerOptions(configData) {
|
|
|
46
45
|
return fromBundler;
|
|
47
46
|
if (!warnedOnce) {
|
|
48
47
|
warnedOnce = true;
|
|
49
|
-
// eslint-disable-next-line no-console
|
|
50
48
|
console.warn("[gjsify] DEPRECATION: the 'esbuild' config key is deprecated and will be removed in 0.5.0. " +
|
|
51
49
|
"Rename it to 'bundler' (typed as RolldownOptions). See the migration notes in the gjsify CHANGELOG.");
|
|
52
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
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.24.
|
|
134
|
-
"@gjsify/create-app": "^0.24.
|
|
135
|
-
"@gjsify/node-globals": "^0.24.
|
|
136
|
-
"@gjsify/node-polyfills": "^0.24.
|
|
137
|
-
"@gjsify/npm-registry": "^0.24.
|
|
138
|
-
"@gjsify/resolve-npm": "^0.24.
|
|
139
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.24.
|
|
140
|
-
"@gjsify/rolldown-plugin-pnp": "^0.24.
|
|
141
|
-
"@gjsify/semver": "^0.24.
|
|
142
|
-
"@gjsify/tar": "^0.24.
|
|
143
|
-
"@gjsify/tsc": "^0.24.
|
|
144
|
-
"@gjsify/web-polyfills": "^0.24.
|
|
145
|
-
"@gjsify/workspace": "^0.24.
|
|
133
|
+
"@gjsify/buffer": "^0.24.2",
|
|
134
|
+
"@gjsify/create-app": "^0.24.2",
|
|
135
|
+
"@gjsify/node-globals": "^0.24.2",
|
|
136
|
+
"@gjsify/node-polyfills": "^0.24.2",
|
|
137
|
+
"@gjsify/npm-registry": "^0.24.2",
|
|
138
|
+
"@gjsify/resolve-npm": "^0.24.2",
|
|
139
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.24.2",
|
|
140
|
+
"@gjsify/rolldown-plugin-pnp": "^0.24.2",
|
|
141
|
+
"@gjsify/semver": "^0.24.2",
|
|
142
|
+
"@gjsify/tar": "^0.24.2",
|
|
143
|
+
"@gjsify/tsc": "^0.24.2",
|
|
144
|
+
"@gjsify/web-polyfills": "^0.24.2",
|
|
145
|
+
"@gjsify/workspace": "^0.24.2",
|
|
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.24.
|
|
153
|
+
"@gjsify/unit": "^0.24.2",
|
|
154
154
|
"@types/yargs": "^17.0.35",
|
|
155
155
|
"typescript": "^6.0.3"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@gjsify/oxfmt-native": "^0.24.
|
|
159
|
-
"@gjsify/rolldown-native": "^0.24.
|
|
158
|
+
"@gjsify/oxfmt-native": "^0.24.2",
|
|
159
|
+
"@gjsify/rolldown-native": "^0.24.2"
|
|
160
160
|
},
|
|
161
161
|
"peerDependenciesMeta": {
|
|
162
162
|
"@gjsify/oxfmt-native": {
|