@gjsify/cli 0.41.0 → 0.43.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/README.md +79 -33
- package/dist/cli.gjs.mjs +1068 -405
- package/lib/actions/build.d.ts +20 -0
- package/lib/actions/build.js +118 -15
- package/lib/bundler-pick.js +10 -2
- package/lib/cli-app.js +2 -1
- package/lib/commands/browse.js +1 -1
- package/lib/commands/debug.js +1 -1
- package/lib/commands/dev.d.ts +13 -0
- package/lib/commands/dev.js +132 -0
- package/lib/commands/dlx.js +1 -1
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/run.js +38 -85
- package/lib/commands/ship.d.ts +2 -0
- package/lib/commands/ship.js +285 -88
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/storybook.js +17 -74
- package/lib/commands/test.js +12 -16
- package/lib/commands/tsc.js +96 -45
- package/lib/config.js +4 -0
- package/lib/types/config-data.d.ts +59 -0
- package/lib/utils/app-metadata.js +27 -6
- package/lib/utils/check-system-deps.js +49 -7
- package/lib/utils/daemon-command.d.ts +7 -0
- package/lib/utils/daemon-command.js +30 -0
- package/lib/utils/dev-plan.d.ts +52 -0
- package/lib/utils/dev-plan.js +117 -0
- package/lib/utils/gi-runtime-paths.d.ts +27 -0
- package/lib/utils/gi-runtime-paths.js +84 -0
- package/lib/utils/gi-typelib.d.ts +49 -0
- package/lib/utils/gi-typelib.js +160 -0
- package/lib/utils/gjs-bundle-guard.d.ts +57 -3
- package/lib/utils/gjs-bundle-guard.js +122 -13
- package/lib/utils/jsx-config.d.ts +45 -0
- package/lib/utils/jsx-config.js +98 -0
- package/lib/utils/node-script.d.ts +4 -1
- package/lib/utils/node-script.js +27 -3
- package/lib/utils/npm-oidc.js +8 -5
- package/lib/utils/onboard-probe.d.ts +23 -0
- package/lib/utils/onboard-probe.js +65 -0
- package/lib/utils/run-gjs.d.ts +12 -1
- package/lib/utils/run-gjs.js +24 -31
- package/lib/utils/run-node.d.ts +9 -4
- package/lib/utils/run-node.js +34 -41
- package/lib/utils/ship/deb.d.ts +3 -3
- package/lib/utils/ship/deb.js +6 -2
- package/lib/utils/ship/depends.d.ts +14 -6
- package/lib/utils/ship/depends.js +45 -4
- package/lib/utils/ship/discover.js +178 -7
- package/lib/utils/ship/formats.js +9 -1
- package/lib/utils/ship/launcher.js +27 -4
- package/lib/utils/ship/mime.d.ts +19 -0
- package/lib/utils/ship/mime.js +89 -0
- package/lib/utils/ship/payload.d.ts +111 -0
- package/lib/utils/ship/payload.js +174 -0
- package/lib/utils/ship/plan.d.ts +30 -1
- package/lib/utils/ship/plan.js +74 -2
- package/lib/utils/ship/rpm.d.ts +3 -3
- package/lib/utils/ship/rpm.js +20 -3
- package/lib/utils/ship/scripts.d.ts +14 -4
- package/lib/utils/ship/scripts.js +26 -8
- package/lib/utils/ship/settings.d.ts +8 -1
- package/lib/utils/ship/settings.js +26 -1
- package/lib/utils/ship/stage-manifest.d.ts +154 -0
- package/lib/utils/ship/stage-manifest.js +408 -0
- package/lib/utils/ship/stage-writer.d.ts +30 -3
- package/lib/utils/ship/stage-writer.js +76 -9
- package/lib/utils/ship/types.d.ts +110 -18
- package/lib/utils/ship/types.js +5 -0
- package/lib/utils/simple-command.d.ts +16 -0
- package/lib/utils/simple-command.js +77 -0
- package/lib/utils/spawn.d.ts +7 -1
- package/lib/utils/spawn.js +34 -7
- package/lib/utils/system-gi.d.ts +24 -0
- package/lib/utils/system-gi.js +14 -3
- package/lib/utils/watch-loop.d.ts +91 -0
- package/lib/utils/watch-loop.js +207 -0
- package/package.json +19 -18
- package/lib/templates/app/desktop.tmpl +0 -10
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface DevPlanRequest {
|
|
2
|
+
/** The project's `package.json#scripts`. */
|
|
3
|
+
scripts: Readonly<Record<string, string>>;
|
|
4
|
+
/** Name of the build script to reuse, e.g. `build:gjs`. */
|
|
5
|
+
scriptName: string;
|
|
6
|
+
/** `gjsify build --app <app>` target the chosen runtime consumes. */
|
|
7
|
+
app: 'gjs' | 'node';
|
|
8
|
+
/** The bundle the package declares for `app` (`gjsify.main` / `gjsify.example.node`). */
|
|
9
|
+
declaredBundle?: string;
|
|
10
|
+
/** `gjsify dev [entry]` — overrides the entry point the script names. */
|
|
11
|
+
entry?: string;
|
|
12
|
+
/** `--globals` override. */
|
|
13
|
+
globals?: string;
|
|
14
|
+
/** `--outfile` override. */
|
|
15
|
+
outfile?: string;
|
|
16
|
+
/** `--watch-dir` override. */
|
|
17
|
+
watchDir?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DevPlan {
|
|
20
|
+
/** argv fed to `runCli` on every rebuild (starts with `build`). */
|
|
21
|
+
buildArgv: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Bundle launched after a successful build, or undefined when nothing names
|
|
24
|
+
* one. See {@link noBundleError}.
|
|
25
|
+
*/
|
|
26
|
+
bundle?: string;
|
|
27
|
+
/** Directory watched recursively. */
|
|
28
|
+
watchDir: string;
|
|
29
|
+
}
|
|
30
|
+
/** The value of `--name value` / `--name=value` in `argv`, if present. */
|
|
31
|
+
export declare function readFlag(argv: readonly string[], name: string): string | undefined;
|
|
32
|
+
/** `argv` with `--name value` set — replacing any existing occurrence, else appended. */
|
|
33
|
+
export declare function withFlag(argv: readonly string[], name: string, value: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* The entry point a `gjsify build` argv names positionally. Only the token
|
|
36
|
+
* DIRECTLY after `build` counts: further positionals are indistinguishable from
|
|
37
|
+
* the value of a preceding flag without knowing every flag's arity, and a wrong
|
|
38
|
+
* guess here would silently build the wrong file.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildEntryPoint(argv: readonly string[]): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the build argv, the bundle to launch and the directory to watch.
|
|
43
|
+
* Throws with both fixes named when the project declares neither.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planDev(req: DevPlanRequest): DevPlan;
|
|
46
|
+
/**
|
|
47
|
+
* Message for a launch that has no bundle to launch. Separate from `planDev`
|
|
48
|
+
* because only the LAUNCHING caller needs one: a build writing an `--outdir`
|
|
49
|
+
* legitimately has no single output file, and refusing inside the planner would
|
|
50
|
+
* fail `--build-only` for a reason that does not apply to it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function noBundleError(scriptName: string, app: 'gjs' | 'node'): Error;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// What `gjsify dev` builds, launches and watches — resolved from the project's
|
|
2
|
+
// OWN declarations rather than from a second set of flags.
|
|
3
|
+
//
|
|
4
|
+
// Re-declaring `--globals auto,dom,PointerEvent,…` here, beside the `build:gjs`
|
|
5
|
+
// script that already says it, would be exactly the drifting second copy this
|
|
6
|
+
// repo keeps paying for: the day the build script gains a flag, the dev loop
|
|
7
|
+
// builds a different bundle than `gjsify run build` does, and the difference
|
|
8
|
+
// surfaces as a runtime error in the app rather than as a diff. So the plan is
|
|
9
|
+
// DERIVED from the script, with the CLI flags as overrides on top.
|
|
10
|
+
//
|
|
11
|
+
// Pure by design — no filesystem, no process — so precedence is unit-tested
|
|
12
|
+
// instead of inferred from a running watch loop.
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
|
+
import { gjsifyCommandArgv } from './simple-command.js';
|
|
15
|
+
/** The value of `--name value` / `--name=value` in `argv`, if present. */
|
|
16
|
+
export function readFlag(argv, name) {
|
|
17
|
+
const long = `--${name}`;
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const token = argv[i];
|
|
20
|
+
if (token === long)
|
|
21
|
+
return argv[i + 1];
|
|
22
|
+
if (token.startsWith(`${long}=`))
|
|
23
|
+
return token.slice(long.length + 1);
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/** `argv` with `--name value` set — replacing any existing occurrence, else appended. */
|
|
28
|
+
export function withFlag(argv, name, value) {
|
|
29
|
+
const long = `--${name}`;
|
|
30
|
+
const out = [...argv];
|
|
31
|
+
for (let i = 0; i < out.length; i++) {
|
|
32
|
+
const token = out[i];
|
|
33
|
+
if (token === long) {
|
|
34
|
+
out[i + 1] = value;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
if (token.startsWith(`${long}=`)) {
|
|
38
|
+
out[i] = `${long}=${value}`;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
out.push(long, value);
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The entry point a `gjsify build` argv names positionally. Only the token
|
|
47
|
+
* DIRECTLY after `build` counts: further positionals are indistinguishable from
|
|
48
|
+
* the value of a preceding flag without knowing every flag's arity, and a wrong
|
|
49
|
+
* guess here would silently build the wrong file.
|
|
50
|
+
*/
|
|
51
|
+
export function buildEntryPoint(argv) {
|
|
52
|
+
const candidate = argv[1];
|
|
53
|
+
if (candidate === undefined || candidate.startsWith('-'))
|
|
54
|
+
return undefined;
|
|
55
|
+
return candidate;
|
|
56
|
+
}
|
|
57
|
+
/** Message shown when neither a usable build script nor an explicit entry exists. */
|
|
58
|
+
function noEntryError(scriptName, hasScript) {
|
|
59
|
+
const why = hasScript
|
|
60
|
+
? `\`${scriptName}\` is not a plain \`gjsify build <entry> …\` command, so its entry point cannot be read`
|
|
61
|
+
: `this package declares no \`${scriptName}\` script`;
|
|
62
|
+
return new Error(`gjsify dev: nothing to build — ${why}.\n` +
|
|
63
|
+
` Name the entry point directly: gjsify dev src/index.ts\n` +
|
|
64
|
+
` or point at another script: gjsify dev --script <name>`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the build argv, the bundle to launch and the directory to watch.
|
|
68
|
+
* Throws with both fixes named when the project declares neither.
|
|
69
|
+
*/
|
|
70
|
+
export function planDev(req) {
|
|
71
|
+
const literal = req.scripts[req.scriptName];
|
|
72
|
+
const scriptArgv = typeof literal === 'string' ? gjsifyCommandArgv(literal) : null;
|
|
73
|
+
const derived = scriptArgv && scriptArgv[0] === 'build' ? scriptArgv : null;
|
|
74
|
+
const entry = req.entry ?? (derived ? buildEntryPoint(derived) : undefined);
|
|
75
|
+
if (entry === undefined)
|
|
76
|
+
throw noEntryError(req.scriptName, typeof literal === 'string');
|
|
77
|
+
// Start from the script's own argv so every flag it declares survives, then
|
|
78
|
+
// put the overrides on top. `--app` is SET rather than inherited: `--script`
|
|
79
|
+
// and `--runtime` can name different targets, and the runtime that will
|
|
80
|
+
// launch the bundle is the one that must decide what is built.
|
|
81
|
+
let argv;
|
|
82
|
+
if (derived) {
|
|
83
|
+
argv = [...derived];
|
|
84
|
+
// Replace the entry the script names, or splice one in when it names
|
|
85
|
+
// none (the script may take its entry from `gjsify.bundler.input`).
|
|
86
|
+
if (buildEntryPoint(derived) !== undefined)
|
|
87
|
+
argv[1] = entry;
|
|
88
|
+
else
|
|
89
|
+
argv.splice(1, 0, entry);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
argv = ['build', entry];
|
|
93
|
+
}
|
|
94
|
+
argv = withFlag(argv, 'app', req.app);
|
|
95
|
+
if (req.globals !== undefined)
|
|
96
|
+
argv = withFlag(argv, 'globals', req.globals);
|
|
97
|
+
if (req.outfile !== undefined)
|
|
98
|
+
argv = withFlag(argv, 'outfile', req.outfile);
|
|
99
|
+
return {
|
|
100
|
+
buildArgv: argv,
|
|
101
|
+
bundle: readFlag(argv, 'outfile') ?? req.declaredBundle,
|
|
102
|
+
watchDir: req.watchDir ?? dirname(entry),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Message for a launch that has no bundle to launch. Separate from `planDev`
|
|
107
|
+
* because only the LAUNCHING caller needs one: a build writing an `--outdir`
|
|
108
|
+
* legitimately has no single output file, and refusing inside the planner would
|
|
109
|
+
* fail `--build-only` for a reason that does not apply to it.
|
|
110
|
+
*/
|
|
111
|
+
export function noBundleError(scriptName, app) {
|
|
112
|
+
const declaration = app === 'gjs' ? '`gjsify.main`' : '`gjsify.example.node`';
|
|
113
|
+
return new Error(`gjsify dev: nothing to launch — neither \`${scriptName}\` nor --outfile names an output file, ` +
|
|
114
|
+
`and this package declares no ${declaration}.\n` +
|
|
115
|
+
` Pass one: gjsify dev --outfile dist/index.${app}.mjs\n` +
|
|
116
|
+
` or rebuild without launching: gjsify dev --build-only`);
|
|
117
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GiSystemProbe } from '@gjsify/rolldown-plugin-gjsify';
|
|
2
|
+
/**
|
|
3
|
+
* The system libdir candidates a `--app gjs` bundle carries — every platform the
|
|
4
|
+
* table names, not the building host's.
|
|
5
|
+
*
|
|
6
|
+
* A build-time platform gate would be exactly the snapshot the note above exists to
|
|
7
|
+
* avoid: `--app gjs` emits platform-neutral JavaScript, so the bundle a Linux runner
|
|
8
|
+
* produces is the bundle a Mac runs. What travels instead is the RUNTIME gate, and it
|
|
9
|
+
* is two markers, not one, because the `girepository-1.0` marker alone answers the
|
|
10
|
+
* wrong question. It says "a GI stack is installed under this prefix" — true on a
|
|
11
|
+
* Linux box built with `meson setup --prefix=/usr/local` or jhbuild, a normal shape
|
|
12
|
+
* for this project's own audience — while the rule this mirrors is about LOADERS:
|
|
13
|
+
* `systemGiLibraryDirs()` is empty off darwin because `ld.so`'s system-wide cache
|
|
14
|
+
* already resolves these leaves. Without the host marker every such Linux host would
|
|
15
|
+
* get `/usr/local/lib` prepended ahead of the distro's typelibs AND libraries, which
|
|
16
|
+
* is the two-stacks precedence ADR 0023 § 4 / #910 paid for — measured against a
|
|
17
|
+
* stand-in host in `gi-runtime-paths-banner.spec.ts`.
|
|
18
|
+
*
|
|
19
|
+
* A platform in the table with no {@link HOST_MARKERS} entry THROWS rather than
|
|
20
|
+
* shipping an ungated candidate: an unknown-host default of "prepend" is the bug
|
|
21
|
+
* above, on every platform at once.
|
|
22
|
+
*
|
|
23
|
+
* SORTED and deduplicated: the result is emitted into a bundle two machines must be
|
|
24
|
+
* able to build byte-for-byte identically, and `Object.entries()` order is the
|
|
25
|
+
* table's, which is a source-edit away from changing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function giSystemProbes(): GiSystemProbe[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// WHAT a `--app gjs` bundle's byte-1 GI prologue is told. The prologue itself is
|
|
3
|
+
// `rolldown-plugin-gjsify`'s `giRuntimePathsStub`; this is the only production caller
|
|
4
|
+
// that fills it, and until it did, every bundle carried the empty string that stub
|
|
5
|
+
// returns for nothing-to-say.
|
|
6
|
+
//
|
|
7
|
+
// WHY A BUNDLE NEEDS IT. On macOS a typelib names its library by bare leaf
|
|
8
|
+
// (`libgtk-4.1.dylib`), and SIP strips an inherited `DYLD_*` at the `/bin/sh` exec a
|
|
9
|
+
// launcher goes through, so the only repair that survives is one the process makes to
|
|
10
|
+
// girepository's own search paths from the inside. Measurements, and what this reaches
|
|
11
|
+
// (loads AFTER module evaluation starts, not a static `gi://` import):
|
|
12
|
+
// `status/open-todos.md` § "A globally installed GJS launcher still cannot load a
|
|
13
|
+
// system GTK on macOS".
|
|
14
|
+
//
|
|
15
|
+
// A SHIPPED BUNDLE RUNS ON A MACHINE THE BUILD NEVER SAW, which rules out both
|
|
16
|
+
// tempting sources. `systemGiLibraryDirs()` MEASURES the build host and answers `[]`
|
|
17
|
+
// on the Linux runner that builds most releases — so what travels is the CANDIDATE
|
|
18
|
+
// table it probes, with the probe moved into the bundle. `detectNativePackages()`
|
|
19
|
+
// answers with the build tree's `node_modules/@gjsify/<pkg>-<target>/prebuilds/
|
|
20
|
+
// <target>`: the build host's target twice over (ADR 0017), at the build tree's depth,
|
|
21
|
+
// so it names nothing on an installed macOS host — and baking it would make
|
|
22
|
+
// `dist/affected.gjs.mjs`, a `--app gjs` bundle `scripts/verify-committed-bundles.mjs`
|
|
23
|
+
// compares byte for byte, encode which platform siblings the committing machine had.
|
|
24
|
+
// `activateNativePrebuilds()` already covers those from inside the running process.
|
|
25
|
+
//
|
|
26
|
+
// DELIBERATELY NOT INCLUDED: a `@gjsify/gtk-runtime-<target>` bundle's `gtk/lib`. That
|
|
27
|
+
// is node-gi's mechanism, for a host process that brings no GTK of its own; a `--app
|
|
28
|
+
// gjs` bundle runs under a `gjs` already linked against a system GLib, so a second GTK
|
|
29
|
+
// stack in that process is the two-registries failure #910 paid for (ADR 0023 § 4).
|
|
30
|
+
import { PROBED_GI_LIBDIRS, TYPELIB_SUBDIR } from './system-gi.js';
|
|
31
|
+
/**
|
|
32
|
+
* A path that proves the RUNNING host is the platform whose candidates it keys, for
|
|
33
|
+
* every platform `PROBED_GI_LIBDIRS` names.
|
|
34
|
+
*
|
|
35
|
+
* The bundle cannot read `process.platform` — it is platform-neutral JavaScript, built
|
|
36
|
+
* on a machine that is not the host — so the darwin scope `systemGiLibraryDirs()`
|
|
37
|
+
* enforces with `PROBED_GI_LIBDIRS[platform]` has to travel as a marker path.
|
|
38
|
+
* Same probe, same spelling as `@gjsify/child_process`'s `detectPlatform()` and
|
|
39
|
+
* `@gjsify/v8`'s `detectHeapPlatform()`: macOS has no procfs, and this plist is
|
|
40
|
+
* present on every install.
|
|
41
|
+
*/
|
|
42
|
+
const HOST_MARKERS = {
|
|
43
|
+
darwin: '/System/Library/CoreServices/SystemVersion.plist',
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The system libdir candidates a `--app gjs` bundle carries — every platform the
|
|
47
|
+
* table names, not the building host's.
|
|
48
|
+
*
|
|
49
|
+
* A build-time platform gate would be exactly the snapshot the note above exists to
|
|
50
|
+
* avoid: `--app gjs` emits platform-neutral JavaScript, so the bundle a Linux runner
|
|
51
|
+
* produces is the bundle a Mac runs. What travels instead is the RUNTIME gate, and it
|
|
52
|
+
* is two markers, not one, because the `girepository-1.0` marker alone answers the
|
|
53
|
+
* wrong question. It says "a GI stack is installed under this prefix" — true on a
|
|
54
|
+
* Linux box built with `meson setup --prefix=/usr/local` or jhbuild, a normal shape
|
|
55
|
+
* for this project's own audience — while the rule this mirrors is about LOADERS:
|
|
56
|
+
* `systemGiLibraryDirs()` is empty off darwin because `ld.so`'s system-wide cache
|
|
57
|
+
* already resolves these leaves. Without the host marker every such Linux host would
|
|
58
|
+
* get `/usr/local/lib` prepended ahead of the distro's typelibs AND libraries, which
|
|
59
|
+
* is the two-stacks precedence ADR 0023 § 4 / #910 paid for — measured against a
|
|
60
|
+
* stand-in host in `gi-runtime-paths-banner.spec.ts`.
|
|
61
|
+
*
|
|
62
|
+
* A platform in the table with no {@link HOST_MARKERS} entry THROWS rather than
|
|
63
|
+
* shipping an ungated candidate: an unknown-host default of "prepend" is the bug
|
|
64
|
+
* above, on every platform at once.
|
|
65
|
+
*
|
|
66
|
+
* SORTED and deduplicated: the result is emitted into a bundle two machines must be
|
|
67
|
+
* able to build byte-for-byte identically, and `Object.entries()` order is the
|
|
68
|
+
* table's, which is a source-edit away from changing.
|
|
69
|
+
*/
|
|
70
|
+
export function giSystemProbes() {
|
|
71
|
+
const probes = new Map();
|
|
72
|
+
for (const [platform, dirs] of Object.entries(PROBED_GI_LIBDIRS)) {
|
|
73
|
+
const hostMarker = HOST_MARKERS[platform];
|
|
74
|
+
if (!hostMarker) {
|
|
75
|
+
throw new Error(`[gjsify] PROBED_GI_LIBDIRS names '${platform}' but HOST_MARKERS does not: ` +
|
|
76
|
+
'a bundle cannot ask process.platform, so an ungated candidate would be ' +
|
|
77
|
+
'prepended on every host that happens to have the directory.');
|
|
78
|
+
}
|
|
79
|
+
for (const dir of dirs) {
|
|
80
|
+
probes.set(`${dir}\u0000${hostMarker}`, [dir, `${dir}/${TYPELIB_SUBDIR}`, hostMarker]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return [...probes.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([, probe]) => probe);
|
|
84
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Injectable host facts — every platform branch is testable from any host. */
|
|
2
|
+
export interface GiTypelibOptions {
|
|
3
|
+
/** `process.platform` value; defaults to the running process. */
|
|
4
|
+
platform?: string;
|
|
5
|
+
/** Environment to read `GI_TYPELIB_PATH` / `PATH` from; defaults to `process.env`. */
|
|
6
|
+
env?: Record<string, string | undefined>;
|
|
7
|
+
/** Directory-existence predicate; defaults to a `statSync` probe. */
|
|
8
|
+
existsDir?: (dir: string) => boolean;
|
|
9
|
+
/** File-existence predicate; defaults to `existsSync`. */
|
|
10
|
+
existsFile?: (file: string) => boolean;
|
|
11
|
+
/** Immediate subdirectory names of `dir`, or `[]`; defaults to a `readdirSync` probe. */
|
|
12
|
+
listDirs?: (dir: string) => string[];
|
|
13
|
+
/** `pkg-config`'s `.pc` search directories; defaults to {@link pkgConfigSearchDirs}. */
|
|
14
|
+
searchDirs?: (env: Record<string, string | undefined>) => string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Directories girepository looks for typelibs in, most specific first,
|
|
18
|
+
* deduplicated, each verified on disk.
|
|
19
|
+
*
|
|
20
|
+
* Sources, in precedence order:
|
|
21
|
+
* 1. `GI_TYPELIB_PATH` — the host stating outright where typelibs live.
|
|
22
|
+
* Believed on existence alone; an explicit statement is not second-guessed.
|
|
23
|
+
* 2. `pkg-config`'s `.pc` search path → `<libdir>/girepository-1.0`. The
|
|
24
|
+
* general mechanism: it finds a jhbuild or otherwise bespoke prefix that no
|
|
25
|
+
* list here could name. Present only when pkg-config is, which on a
|
|
26
|
+
* runtime-only host it is not — hence 3.
|
|
27
|
+
* 3. Platform defaults, each believed only on the marker directory existing.
|
|
28
|
+
*
|
|
29
|
+
* Source 3 is where the platforms differ, and each branch is a statement about
|
|
30
|
+
* that platform's install layout:
|
|
31
|
+
* * **linux** — {@link LINUX_LIBDIRS} plus the discovered multiarch triplets.
|
|
32
|
+
* * **darwin** — {@link systemGiLibraryDirs}, which already probes Homebrew,
|
|
33
|
+
* MacPorts and pkg-config prefixes for exactly this marker. Reused rather
|
|
34
|
+
* than re-derived: the two must not be able to disagree about where the GI
|
|
35
|
+
* stack is.
|
|
36
|
+
* * **win32** — a GTK distribution ships `<prefix>/bin` on `PATH` and its
|
|
37
|
+
* typelibs in the sibling `<prefix>/lib/girepository-1.0`, so PATH is the
|
|
38
|
+
* prefix oracle. (Untested against a real Windows GTK; it costs a `stat` per
|
|
39
|
+
* `bin` entry and cannot make any host worse than the empty list it replaces.)
|
|
40
|
+
*/
|
|
41
|
+
export declare function systemGiTypelibDirs({ platform, env, existsDir, listDirs, searchDirs, }?: GiTypelibOptions): string[];
|
|
42
|
+
/**
|
|
43
|
+
* The installed `<namespace>-<version>.typelib`, or `null`.
|
|
44
|
+
*
|
|
45
|
+
* `version` is GI's API version as it appears in a `gi://` specifier (`4.0` for
|
|
46
|
+
* `gi://Gtk?version=4.0`, `1` for Adwaita) — the same string the filename
|
|
47
|
+
* carries, so the caller states it exactly once.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findSystemTypelib(namespace: string, version: string, { existsFile, ...options }?: GiTypelibOptions): string | null;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Is a GObject-Introspection namespace INSTALLED on this host — the question
|
|
3
|
+
// `gi_repository_require()` answers, asked without loading anything.
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS EXISTS. `gjsify system-check` used to answer it with `pkg-config
|
|
6
|
+
// --modversion libadwaita-1`, which is a question about the DEVELOPMENT package.
|
|
7
|
+
// Measured on postmarketOS v26.06 / aarch64 (OnePlus 6T, gjs 1.88.1, no node):
|
|
8
|
+
// libadwaita 1.9.2 installed, `Adw-1.typelib` present, every Adwaita app on the
|
|
9
|
+
// device running — and `system-check` printing `✗ libadwaita` under **Required**
|
|
10
|
+
// and exiting 1, because `libadwaita-dev` (which ships the `.pc` file and
|
|
11
|
+
// nothing a running app loads) was not installed. A check that fails on a host
|
|
12
|
+
// where the thing it checks demonstrably works is worse than no check: it sends
|
|
13
|
+
// people to install packages they do not need, and it teaches them to ignore the
|
|
14
|
+
// output — the same failure mode `checkTypeSkew`'s comment in
|
|
15
|
+
// `check-system-deps.ts` describes from the other direction.
|
|
16
|
+
//
|
|
17
|
+
// THE ORACLE is the typelib FILE on girepository's search path:
|
|
18
|
+
// * it is what GI itself resolves, so it cannot disagree with what an app does;
|
|
19
|
+
// * it is identical under GJS and Node, so the CLI's two entry points give the
|
|
20
|
+
// same answer (`pkg-config` at least had that property; `imports.gi` would
|
|
21
|
+
// not);
|
|
22
|
+
// * it needs no `dlopen`, and a presence probe must not have the side effect of
|
|
23
|
+
// loading GTK into the process asking the question.
|
|
24
|
+
//
|
|
25
|
+
// It answers PRESENCE only. The version still comes from `pkg-config` where that
|
|
26
|
+
// is installed — see `checkGiLibrary` in `check-system-deps.ts`, which reports a
|
|
27
|
+
// version when it can and a bare `✓` when it cannot. Presence without a version
|
|
28
|
+
// is the honest answer on a runtime-only host; `✗` was not.
|
|
29
|
+
//
|
|
30
|
+
// PURE FUNCTION of injected host facts (platform / env / fs / pkg-config), like
|
|
31
|
+
// `systemGiLibraryDirs()` and `resolvePrebuildDirName()` beside it, so the
|
|
32
|
+
// darwin and win32 branches are unit-tested from Linux.
|
|
33
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
34
|
+
import { posix, win32 } from 'node:path';
|
|
35
|
+
import { pkgConfigSearchDirs, splitSearchPath, systemGiLibraryDirs, TYPELIB_SUBDIR } from './system-gi.js';
|
|
36
|
+
/**
|
|
37
|
+
* Library prefixes a Linux GI stack is installed under, before multiarch.
|
|
38
|
+
* `/usr/lib64` is Fedora/openSUSE, `/usr/lib` everything else, `/usr/local/lib`
|
|
39
|
+
* a source install. Each is accepted only when it actually holds the marker
|
|
40
|
+
* directory, so naming one that does not exist costs a `stat` and nothing else.
|
|
41
|
+
*/
|
|
42
|
+
const LINUX_LIBDIRS = ['/usr/lib', '/usr/lib64', '/usr/local/lib'];
|
|
43
|
+
/**
|
|
44
|
+
* Where a Debian/Ubuntu multiarch layout hides the typelibs
|
|
45
|
+
* (`/usr/lib/x86_64-linux-gnu/girepository-1.0`). The triplet is DISCOVERED by
|
|
46
|
+
* listing the parent rather than derived from `process.arch`: the mapping from
|
|
47
|
+
* Node's arch names to Debian triplets is a table that would need a row per
|
|
48
|
+
* port, and one `readdir` of `/usr/lib` answers it for every architecture
|
|
49
|
+
* including the ones nobody has written down.
|
|
50
|
+
*/
|
|
51
|
+
const MULTIARCH_PARENTS = ['/usr/lib'];
|
|
52
|
+
function isDirectory(dir) {
|
|
53
|
+
try {
|
|
54
|
+
return statSync(dir).isDirectory();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ENOENT on an uninstalled prefix, EACCES on a path this process may not
|
|
58
|
+
// read: not a usable typelib directory either way.
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function subdirectories(dir) {
|
|
63
|
+
try {
|
|
64
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
65
|
+
.filter((entry) => entry.isDirectory())
|
|
66
|
+
.map((entry) => entry.name);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Directories girepository looks for typelibs in, most specific first,
|
|
74
|
+
* deduplicated, each verified on disk.
|
|
75
|
+
*
|
|
76
|
+
* Sources, in precedence order:
|
|
77
|
+
* 1. `GI_TYPELIB_PATH` — the host stating outright where typelibs live.
|
|
78
|
+
* Believed on existence alone; an explicit statement is not second-guessed.
|
|
79
|
+
* 2. `pkg-config`'s `.pc` search path → `<libdir>/girepository-1.0`. The
|
|
80
|
+
* general mechanism: it finds a jhbuild or otherwise bespoke prefix that no
|
|
81
|
+
* list here could name. Present only when pkg-config is, which on a
|
|
82
|
+
* runtime-only host it is not — hence 3.
|
|
83
|
+
* 3. Platform defaults, each believed only on the marker directory existing.
|
|
84
|
+
*
|
|
85
|
+
* Source 3 is where the platforms differ, and each branch is a statement about
|
|
86
|
+
* that platform's install layout:
|
|
87
|
+
* * **linux** — {@link LINUX_LIBDIRS} plus the discovered multiarch triplets.
|
|
88
|
+
* * **darwin** — {@link systemGiLibraryDirs}, which already probes Homebrew,
|
|
89
|
+
* MacPorts and pkg-config prefixes for exactly this marker. Reused rather
|
|
90
|
+
* than re-derived: the two must not be able to disagree about where the GI
|
|
91
|
+
* stack is.
|
|
92
|
+
* * **win32** — a GTK distribution ships `<prefix>/bin` on `PATH` and its
|
|
93
|
+
* typelibs in the sibling `<prefix>/lib/girepository-1.0`, so PATH is the
|
|
94
|
+
* prefix oracle. (Untested against a real Windows GTK; it costs a `stat` per
|
|
95
|
+
* `bin` entry and cannot make any host worse than the empty list it replaces.)
|
|
96
|
+
*/
|
|
97
|
+
export function systemGiTypelibDirs({ platform = process.platform, env = process.env, existsDir = isDirectory, listDirs = subdirectories, searchDirs = pkgConfigSearchDirs, } = {}) {
|
|
98
|
+
// POSIX semantics unless the TARGET is win32 — `platform` is a parameter, so
|
|
99
|
+
// this answers a question about a foreign filesystem from any host. The same
|
|
100
|
+
// trap `system-gi.ts` documents: a host-following `join` turns
|
|
101
|
+
// `/usr/lib/girepository-1.0` into a backslash path on Windows and matches
|
|
102
|
+
// nothing the caller holds.
|
|
103
|
+
const { join, dirname, basename } = platform === 'win32' ? win32 : posix;
|
|
104
|
+
const separator = platform === 'win32' ? ';' : ':';
|
|
105
|
+
const out = [];
|
|
106
|
+
const add = (dir) => {
|
|
107
|
+
if (dir && !out.includes(dir))
|
|
108
|
+
out.push(dir);
|
|
109
|
+
};
|
|
110
|
+
// 1. Explicit.
|
|
111
|
+
for (const dir of splitSearchPath(env['GI_TYPELIB_PATH'], separator)) {
|
|
112
|
+
if (existsDir(dir))
|
|
113
|
+
add(dir);
|
|
114
|
+
}
|
|
115
|
+
// 2 + 3. Candidate libdirs, believed only on the marker.
|
|
116
|
+
const libDirs = [];
|
|
117
|
+
for (const pcDir of searchDirs(env)) {
|
|
118
|
+
if (basename(pcDir) === 'pkgconfig')
|
|
119
|
+
libDirs.push(dirname(pcDir));
|
|
120
|
+
}
|
|
121
|
+
if (platform === 'linux') {
|
|
122
|
+
libDirs.push(...LINUX_LIBDIRS);
|
|
123
|
+
for (const parent of MULTIARCH_PARENTS) {
|
|
124
|
+
for (const entry of listDirs(parent))
|
|
125
|
+
libDirs.push(join(parent, entry));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (platform === 'darwin') {
|
|
129
|
+
libDirs.push(...systemGiLibraryDirs({ platform, env, existsDir, searchDirs }));
|
|
130
|
+
}
|
|
131
|
+
else if (platform === 'win32') {
|
|
132
|
+
for (const pathEntry of splitSearchPath(env['PATH'], separator)) {
|
|
133
|
+
if (basename(pathEntry).toLowerCase() === 'bin')
|
|
134
|
+
libDirs.push(join(dirname(pathEntry), 'lib'));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const libDir of libDirs) {
|
|
138
|
+
const typelibDir = join(libDir, TYPELIB_SUBDIR);
|
|
139
|
+
if (existsDir(typelibDir))
|
|
140
|
+
add(typelibDir);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The installed `<namespace>-<version>.typelib`, or `null`.
|
|
146
|
+
*
|
|
147
|
+
* `version` is GI's API version as it appears in a `gi://` specifier (`4.0` for
|
|
148
|
+
* `gi://Gtk?version=4.0`, `1` for Adwaita) — the same string the filename
|
|
149
|
+
* carries, so the caller states it exactly once.
|
|
150
|
+
*/
|
|
151
|
+
export function findSystemTypelib(namespace, version, { existsFile = existsSync, ...options } = {}) {
|
|
152
|
+
const { join } = options.platform === 'win32' ? win32 : posix;
|
|
153
|
+
const leaf = `${namespace}-${version}.typelib`;
|
|
154
|
+
for (const dir of systemGiTypelibDirs(options)) {
|
|
155
|
+
const candidate = join(dir, leaf);
|
|
156
|
+
if (existsFile(candidate))
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/** The
|
|
1
|
+
/** The bundler-output fields this guard needs, from either engine. */
|
|
2
2
|
export interface GuardedChunk {
|
|
3
3
|
/** Emitted chunk name, for the error message. */
|
|
4
4
|
fileName?: string;
|
|
5
5
|
/** External specifiers the chunk STATICALLY imports (rollup/rolldown contract). */
|
|
6
6
|
imports?: readonly string[];
|
|
7
|
+
/** Emitted source, for the syntax check. Present on a written chunk on both engines. */
|
|
8
|
+
code?: string;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Bare `node:` specifiers a `--app gjs` bundle may keep as static imports.
|
|
@@ -20,5 +22,57 @@ export declare const GJS_ALLOWED_NODE_IMPORTS: readonly string[];
|
|
|
20
22
|
* allowlist. Sorted, so the error message and any test are stable.
|
|
21
23
|
*/
|
|
22
24
|
export declare function findDisallowedNodeImports(chunks: readonly GuardedChunk[], allowed?: readonly string[]): string[];
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Is `id` a BARE package specifier — the only shape GJS's ESM loader cannot resolve on
|
|
27
|
+
* its own?
|
|
28
|
+
*
|
|
29
|
+
* Relative and absolute paths name a file; anything carrying a URL scheme names a
|
|
30
|
+
* loader GJS either has (`gi:`, `file:`, `resource:`) or will report itself. `node:` is
|
|
31
|
+
* classified BEFORE this, because it has its own message and its own symptom.
|
|
32
|
+
* A win32 drive path (`C:\…`) matches the scheme test, which is the intended answer.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isBareSpecifier(id: string): boolean;
|
|
35
|
+
/** What the caller must tell the guard about this build's own promises. */
|
|
36
|
+
export interface GjsImportPolicy {
|
|
37
|
+
/**
|
|
38
|
+
* The target's own externals predicate — pass
|
|
39
|
+
* `createGjsExternalsPredicate(userExternal)`, the same one `externalsPlugin`
|
|
40
|
+
* enforces, so the guard and the build cannot disagree about what was declared.
|
|
41
|
+
* Omitted, nothing counts as declared.
|
|
42
|
+
*/
|
|
43
|
+
isExternal?: (id: string) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Every fileName the build emitted. `chunk.imports` lists sibling CHUNKS by fileName
|
|
46
|
+
* with no `./` prefix, which is indistinguishable from a bare package specifier — so
|
|
47
|
+
* the set is excluded by name rather than guessed at by shape.
|
|
48
|
+
*/
|
|
49
|
+
emitted?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The distinct bare specifiers `chunks` statically import that nothing in this build
|
|
53
|
+
* promised to provide. Sorted, so the error message and any test are stable.
|
|
54
|
+
*/
|
|
55
|
+
export declare function findUnresolvableBareImports(chunks: readonly GuardedChunk[], policy?: GjsImportPolicy): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Throw when a built `--app gjs` bundle statically imports something GJS cannot
|
|
58
|
+
* resolve — a bare `node:` builtin, or any other bare specifier this build did not
|
|
59
|
+
* declare external.
|
|
60
|
+
*
|
|
61
|
+
* The two are reported separately because they have different symptoms and different
|
|
62
|
+
* fixes: a `node:` specifier aborts with "Unsupported URI scheme", any other bare one
|
|
63
|
+
* with "Module not found", and the first means an alias target failed while the second
|
|
64
|
+
* means the dependency is simply not there.
|
|
65
|
+
*/
|
|
66
|
+
export declare function assertGjsBundleLoadable(chunks: readonly GuardedChunk[], outfile: string, policy?: GjsImportPolicy): void;
|
|
67
|
+
/**
|
|
68
|
+
* Throw when a written `--app gjs` chunk is not parseable JavaScript because raw JSX
|
|
69
|
+
* survived into it. See `@gjsify/rolldown-plugin-gjsify`'s `utils/jsx-survival.ts` for
|
|
70
|
+
* why the oracle is a parse and not a pattern, and for the measurement.
|
|
71
|
+
*
|
|
72
|
+
* Called ONLY when the effective JSX setting PRESERVES JSX, which is the only way it can
|
|
73
|
+
* reach an artifact: oxc compiles it otherwise. That is not a cost dodge but the precise
|
|
74
|
+
* condition — though the cost is real, and measured: parsing the repo's largest bundle
|
|
75
|
+
* (`dist/cli.gjs.mjs`, 6.8 MB) takes ~850 ms, which is not worth paying on every GJS
|
|
76
|
+
* build to re-answer a question the transform already settled.
|
|
77
|
+
*/
|
|
78
|
+
export declare function assertGjsBundleParses(chunks: readonly GuardedChunk[], label: string): void;
|