@gjsify/cli 0.28.0 → 0.29.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 +65 -57
- package/lib/commands/format.js +17 -8
- package/lib/commands/install.d.ts +27 -0
- package/lib/commands/install.js +25 -8
- package/lib/commands/system-check.js +22 -2
- package/lib/utils/bin-shim.d.ts +26 -0
- package/lib/utils/bin-shim.js +26 -0
- package/lib/utils/check-system-deps.d.ts +34 -0
- package/lib/utils/check-system-deps.js +121 -1
- package/lib/utils/detect-native-packages.d.ts +45 -1
- package/lib/utils/detect-native-packages.js +56 -0
- package/lib/utils/install-backend-native.d.ts +79 -1
- package/lib/utils/install-backend-native.js +139 -32
- package/lib/utils/system-gi.d.ts +71 -0
- package/lib/utils/system-gi.js +221 -0
- package/package.json +17 -17
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// WHERE the host's GObject-Introspection stack keeps its shared libraries, so the
|
|
3
|
+
// dynamic loader can find the ones a typelib names by BARE LEAF
|
|
4
|
+
// (`libgtk-4.1.dylib`, `libgdk_pixbuf-2.0.0.dylib`, …).
|
|
5
|
+
//
|
|
6
|
+
// ── A DELIBERATE MIRROR ──────────────────────────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// This is a PINNED PORT of `packages/node-gi/node-gi/system-gi.js`. The two must
|
|
9
|
+
// agree, and `system-gi.spec.ts` is the check: it imports node-gi's copy by
|
|
10
|
+
// relative path and asserts both return identical arrays for a table of injected
|
|
11
|
+
// inputs. That is the same shape `impliedExampleNodeEntry()` in
|
|
12
|
+
// `@gjsify/manifest-conformance` uses for `resolveNodeEntry()` in this package —
|
|
13
|
+
// "this is the check, that is the consumer".
|
|
14
|
+
//
|
|
15
|
+
// Why a mirror and not an import: ADR 0005 Decision 2 forbids any Tier-1/Tier-2
|
|
16
|
+
// package taking a `dependencies`/`optionalDependencies` edge on
|
|
17
|
+
// `@gjsify/node-gi`, and `@gjsify/cli` is Tier 1. The alternative — lifting the
|
|
18
|
+
// module into a new shared npm name — is the documented expensive path (manual
|
|
19
|
+
// first publish + Trusted Publisher bootstrap; the `@gjsify/tls-native` incident
|
|
20
|
+
// stalled 60+ packages), so it is tracked in `status/open-todos.md` instead of
|
|
21
|
+
// taken here. A spec may reach across the repo because it is never bundled into
|
|
22
|
+
// the published `lib/`, which is what lets the agreement be machine-checked
|
|
23
|
+
// without creating the forbidden edge.
|
|
24
|
+
//
|
|
25
|
+
// ── WHY THIS EXISTS ──────────────────────────────────────────────────────────
|
|
26
|
+
//
|
|
27
|
+
// Measured twice, on the same class of host, in two runtimes:
|
|
28
|
+
//
|
|
29
|
+
// 1. Node (macOS 15.7.8 / Homebrew /usr/local / Node 24.18.1 / GTK 4.22.4, the
|
|
30
|
+
// first release that shipped a darwin-x64 addon) — this is node-gi's report:
|
|
31
|
+
//
|
|
32
|
+
// GLib-GIRepository-WARNING: Failed to load shared library 'libgtk-4.1.dylib'
|
|
33
|
+
// referenced by the typelib: dlopen(libgtk-4.1.dylib, 0x0009): tried:
|
|
34
|
+
// 'libgtk-4.1.dylib' (no such file), …, '/usr/lib/libgtk-4.1.dylib' (no such file)
|
|
35
|
+
// TypeError: Gtk.DrawingArea is not a subclassable GObject type
|
|
36
|
+
//
|
|
37
|
+
// 2. GJS (same VM, gjs 1.88.1, `gjs -c "imports.gi.Gtk; Gtk.init()"`, nothing
|
|
38
|
+
// gjsify in the process at all) — the reason this CLI needs the module too:
|
|
39
|
+
//
|
|
40
|
+
// dlopen(libgtk-4.1.dylib, 0x0009): tried: 'libgtk-4.1.dylib' (no such file),
|
|
41
|
+
// '/System/Volumes/Preboot/Cryptexes/OSlibgtk-4.1.dylib' (no such file),
|
|
42
|
+
// '/usr/local/Cellar/gjs/1.88.1/bin/../../../../opt/glib/lib/libgtk-4.1.dylib'
|
|
43
|
+
// (no such file), '/usr/lib/libgtk-4.1.dylib' (no such file, not in dyld cache)
|
|
44
|
+
// JS ERROR: Error: Unsupported type void, deriving from fundamental void
|
|
45
|
+
//
|
|
46
|
+
// Read the third path dyld tried: Homebrew's `gjs` binary has an rpath, and it
|
|
47
|
+
// points into GLIB's keg (`…/opt/glib/lib`) — nowhere else. So Homebrew's gjs
|
|
48
|
+
// resolves the glib-family leaves it links against and NO other keg's. The
|
|
49
|
+
// difference between the two reports is cosmetic, not structural: `node` has no
|
|
50
|
+
// rpath at all, `gjs` has one into the wrong keg, and `/usr/local/lib` — where
|
|
51
|
+
// `libgtk-4.1.dylib` actually is — is in neither, nor in dyld's default fallback
|
|
52
|
+
// path. The gap is the LOADER, not the installation: `gi_repository_require`
|
|
53
|
+
// resolves the typelib, `g_module_open('libgtk-4.1.dylib')` then fails, no
|
|
54
|
+
// `gtk_*_get_type` is ever registered, and the failure surfaces one layer up as
|
|
55
|
+
// something that reads like a type-system bug.
|
|
56
|
+
//
|
|
57
|
+
// The value CANNOT come from `pkg-config --variable=libdir gtk4`. Measured on the
|
|
58
|
+
// same host: that answers `/usr/local/Cellar/gtk4/4.22.4/lib` — a PER-FORMULA
|
|
59
|
+
// directory that fixes GTK and leaves `libgdk_pixbuf-2.0.0.dylib` (a different
|
|
60
|
+
// formula, so a different Cellar libdir) still unresolvable. What works is the
|
|
61
|
+
// directory every keg is symlinked into, `<prefix>/lib`, because it is the UNION.
|
|
62
|
+
// So the question this module answers is "which directories hold the GI stack",
|
|
63
|
+
// never "where is one library".
|
|
64
|
+
//
|
|
65
|
+
// THE RULE — a typelib and the library it names are SIBLINGS by GI's own install
|
|
66
|
+
// layout: `<libdir>/girepository-1.0/Gtk-4.0.typelib` beside
|
|
67
|
+
// `<libdir>/libgtk-4.1.dylib`. A directory holding `girepository-1.0/` therefore
|
|
68
|
+
// IS a GI library directory. That convention is what makes this
|
|
69
|
+
// package-manager-agnostic: it lands on `/usr/local/lib` (Homebrew x64),
|
|
70
|
+
// `/opt/homebrew/lib` (Homebrew arm64), `/opt/local/lib` (MacPorts) or a custom
|
|
71
|
+
// prefix without needing to know that any of them exist.
|
|
72
|
+
//
|
|
73
|
+
// PURE FUNCTION of injected host facts (platform / env / fs / pkg-config), like
|
|
74
|
+
// `resolvePrebuildDirName()` and `buildNativeEnv()` beside it — so the darwin
|
|
75
|
+
// branch is unit-testable from a Linux host and the host values are read only at
|
|
76
|
+
// the outermost call site, as defaults.
|
|
77
|
+
//
|
|
78
|
+
// ── HOW THE CONSUMER DIFFERS FROM NODE-GI'S ──────────────────────────────────
|
|
79
|
+
//
|
|
80
|
+
// node-gi RE-EXECS its own process with the result on `DYLD_FALLBACK_LIBRARY_PATH`,
|
|
81
|
+
// because dyld captures that variable at process LAUNCH and an in-process
|
|
82
|
+
// `process.env` mutation therefore does nothing. This CLI never needs a re-exec:
|
|
83
|
+
// it is the one LAUNCHING the gjs child (`buildNativeEnv()` →
|
|
84
|
+
// `spawn('gjs', …, { env })`), so it sets the variable in the child's launch env,
|
|
85
|
+
// which is the same repair one hop earlier. A launcher could not fix this from
|
|
86
|
+
// inside the bundle either way — GI resolves typelibs and their backers before the
|
|
87
|
+
// bundle's first line runs.
|
|
88
|
+
import { spawnSync } from 'node:child_process';
|
|
89
|
+
import { statSync } from 'node:fs';
|
|
90
|
+
import { basename, dirname, join } from 'node:path';
|
|
91
|
+
/**
|
|
92
|
+
* Prefixes a macOS GI stack is installed under in practice — probed, never
|
|
93
|
+
* assumed: each is accepted only when it actually holds a `girepository-1.0/`.
|
|
94
|
+
*
|
|
95
|
+
* This is the no-pkg-config safety net, and it is load-bearing rather than
|
|
96
|
+
* belt-and-braces: `brew install gtk4` installs RUNTIME dependencies only, so a
|
|
97
|
+
* user running a published showcase routinely has the entire GTK stack and no
|
|
98
|
+
* `pkg-config` at all (it is a BUILD dependency of those formulae). The
|
|
99
|
+
* pkg-config source below is the GENERAL mechanism — it is what finds a jhbuild
|
|
100
|
+
* or otherwise bespoke prefix — but it cannot be the only one.
|
|
101
|
+
*/
|
|
102
|
+
const PROBED_GI_LIBDIRS = {
|
|
103
|
+
darwin: ['/opt/homebrew/lib', '/usr/local/lib', '/opt/local/lib'],
|
|
104
|
+
};
|
|
105
|
+
/** The subdir GI installs typelibs into — historically `1.0` even for the girepository-2.0 API. */
|
|
106
|
+
const TYPELIB_SUBDIR = 'girepository-1.0';
|
|
107
|
+
/** Memoized `pkg-config` answer: the spawn happens at most once per process. */
|
|
108
|
+
let pkgConfigDirsCache = null;
|
|
109
|
+
/**
|
|
110
|
+
* Split an OS search-path variable, dropping empty segments.
|
|
111
|
+
*/
|
|
112
|
+
export function splitSearchPath(value, separator = ':') {
|
|
113
|
+
return (value ?? '').split(separator).filter(Boolean);
|
|
114
|
+
}
|
|
115
|
+
function isDirectory(dir) {
|
|
116
|
+
try {
|
|
117
|
+
return statSync(dir).isDirectory();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Any stat failure means the same thing here — ENOENT on an uninstalled
|
|
121
|
+
// prefix, EACCES on a path this process may not read: not a usable libdir.
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `pkg-config`'s OWN configured `.pc` search path plus `$PKG_CONFIG_PATH`. Every
|
|
127
|
+
* entry is a `<libdir>/pkgconfig` (or `<datadir>/pkgconfig`) directory, so its
|
|
128
|
+
* parent is a candidate libdir — which is how a prefix nobody hardcoded is
|
|
129
|
+
* found. Measured on the reported host: `pc_path` =
|
|
130
|
+
* `/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/usr/lib/pkgconfig:…`,
|
|
131
|
+
* whose first parent is exactly the `/usr/local/lib` that resolves every leaf.
|
|
132
|
+
*
|
|
133
|
+
* `spawnSync` reports a missing executable as `result.error`, not by throwing —
|
|
134
|
+
* an absent `pkg-config` is expected on a runtime-only GTK install, so it is a
|
|
135
|
+
* checked return value here and not an exception path. (`@gjsify/child_process`
|
|
136
|
+
* matches Node here deliberately; see its `spawnSync` note.)
|
|
137
|
+
*/
|
|
138
|
+
export function pkgConfigSearchDirs(env) {
|
|
139
|
+
if (pkgConfigDirsCache)
|
|
140
|
+
return pkgConfigDirsCache;
|
|
141
|
+
const dirs = splitSearchPath(env['PKG_CONFIG_PATH']);
|
|
142
|
+
const res = spawnSync('pkg-config', ['--variable=pc_path', 'pkg-config'], {
|
|
143
|
+
encoding: 'utf8',
|
|
144
|
+
timeout: 10_000,
|
|
145
|
+
});
|
|
146
|
+
if (!res.error && res.status === 0)
|
|
147
|
+
dirs.push(...splitSearchPath(String(res.stdout).trim()));
|
|
148
|
+
pkgConfigDirsCache = dirs;
|
|
149
|
+
return dirs;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Directories holding the host's system GObject-Introspection shared libraries —
|
|
153
|
+
* most specific first, deduplicated, each verified on disk.
|
|
154
|
+
*
|
|
155
|
+
* Three sources, in precedence order:
|
|
156
|
+
* 1. `GI_TYPELIB_PATH` — the host stating outright where typelibs live; the
|
|
157
|
+
* libraries are the sibling `dirname()`. Accepted on directory existence
|
|
158
|
+
* alone: an explicit statement is not second-guessed.
|
|
159
|
+
* 2. `pkg-config`'s `.pc` search path → each `<dir>/pkgconfig`'s parent. The
|
|
160
|
+
* general mechanism (any prefix, including a custom one).
|
|
161
|
+
* 3. {@link PROBED_GI_LIBDIRS} — the standard macOS prefixes, for the common
|
|
162
|
+
* case where GTK is installed and pkg-config is not.
|
|
163
|
+
* Sources 2 and 3 are GUESSES at a prefix, so each must show the
|
|
164
|
+
* `girepository-1.0/` marker before it is believed.
|
|
165
|
+
*
|
|
166
|
+
* Empty off darwin, and that is a statement about LOADERS, not about GI. Linux
|
|
167
|
+
* resolves these leaves through `ld.so`'s system-wide configured cache
|
|
168
|
+
* (`/etc/ld.so.conf.d`), which a package install populates — nothing per-process
|
|
169
|
+
* is needed. dyld is the one loader that neither consults a system-wide config
|
|
170
|
+
* NOR re-reads its search path after launch, which is why darwin needs the
|
|
171
|
+
* variable set at launch and nobody else does.
|
|
172
|
+
*
|
|
173
|
+
* win32 is deliberately absent rather than proven unnecessary: `PATH` is the DLL
|
|
174
|
+
* search path, a Windows GTK distribution puts its `bin` there itself, and
|
|
175
|
+
* Windows re-reads that path at every `LoadLibrary` — so if a system GTK ever
|
|
176
|
+
* turns out not to be on it, the repair is a `PATH` prepend (which
|
|
177
|
+
* {@link buildNativeEnv} already does for the prebuilds), never a second
|
|
178
|
+
* variable. Untested there.
|
|
179
|
+
*
|
|
180
|
+
* @returns absolute directories, or `[]` when this platform's loader needs none
|
|
181
|
+
*/
|
|
182
|
+
export function systemGiLibraryDirs({ platform = process.platform, env = process.env, existsDir = isDirectory, searchDirs = pkgConfigSearchDirs, } = {}) {
|
|
183
|
+
const probed = PROBED_GI_LIBDIRS[platform];
|
|
184
|
+
if (!probed)
|
|
185
|
+
return [];
|
|
186
|
+
const out = [];
|
|
187
|
+
const add = (dir) => {
|
|
188
|
+
if (dir && dir !== '/' && !out.includes(dir))
|
|
189
|
+
out.push(dir);
|
|
190
|
+
};
|
|
191
|
+
// 1. Explicit: the typelib dir's sibling libdir.
|
|
192
|
+
for (const typelibDir of splitSearchPath(env['GI_TYPELIB_PATH'])) {
|
|
193
|
+
const libDir = dirname(typelibDir);
|
|
194
|
+
if (existsDir(libDir))
|
|
195
|
+
add(libDir);
|
|
196
|
+
}
|
|
197
|
+
// 2 + 3. Candidate prefixes, believed only on the girepository-1.0 marker.
|
|
198
|
+
const candidates = [];
|
|
199
|
+
for (const pcDir of searchDirs(env)) {
|
|
200
|
+
if (basename(pcDir) === 'pkgconfig')
|
|
201
|
+
candidates.push(dirname(pcDir));
|
|
202
|
+
}
|
|
203
|
+
candidates.push(...probed);
|
|
204
|
+
for (const libDir of candidates) {
|
|
205
|
+
if (existsDir(join(libDir, TYPELIB_SUBDIR)))
|
|
206
|
+
add(libDir);
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Whether `current` (a split `DYLD_FALLBACK_LIBRARY_PATH`) already covers every
|
|
212
|
+
* directory in `wanted` — the "nothing to do" test.
|
|
213
|
+
*
|
|
214
|
+
* Kept in the port even though this CLI's consumer does not branch on it: the
|
|
215
|
+
* mirror's value is that the two files are the SAME file, and a surface that
|
|
216
|
+
* drifts by one export is a mirror nobody can check. `system-gi.spec.ts` pins
|
|
217
|
+
* both.
|
|
218
|
+
*/
|
|
219
|
+
export function pathCovers(wanted, current) {
|
|
220
|
+
return wanted.every((dir) => current.includes(dir));
|
|
221
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -129,19 +129,19 @@
|
|
|
129
129
|
"cli"
|
|
130
130
|
],
|
|
131
131
|
"dependencies": {
|
|
132
|
-
"@gjsify/buffer": "^0.
|
|
133
|
-
"@gjsify/create-app": "^0.
|
|
134
|
-
"@gjsify/node-globals": "^0.
|
|
135
|
-
"@gjsify/node-polyfills": "^0.
|
|
136
|
-
"@gjsify/npm-registry": "^0.
|
|
137
|
-
"@gjsify/resolve-npm": "^0.
|
|
138
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
139
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
140
|
-
"@gjsify/semver": "^0.
|
|
141
|
-
"@gjsify/tar": "^0.
|
|
142
|
-
"@gjsify/tsc": "^0.
|
|
143
|
-
"@gjsify/web-polyfills": "^0.
|
|
144
|
-
"@gjsify/workspace": "^0.
|
|
132
|
+
"@gjsify/buffer": "^0.29.0",
|
|
133
|
+
"@gjsify/create-app": "^0.29.0",
|
|
134
|
+
"@gjsify/node-globals": "^0.29.0",
|
|
135
|
+
"@gjsify/node-polyfills": "^0.29.0",
|
|
136
|
+
"@gjsify/npm-registry": "^0.29.0",
|
|
137
|
+
"@gjsify/resolve-npm": "^0.29.0",
|
|
138
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.29.0",
|
|
139
|
+
"@gjsify/rolldown-plugin-pnp": "^0.29.0",
|
|
140
|
+
"@gjsify/semver": "^0.29.0",
|
|
141
|
+
"@gjsify/tar": "^0.29.0",
|
|
142
|
+
"@gjsify/tsc": "^0.29.0",
|
|
143
|
+
"@gjsify/web-polyfills": "^0.29.0",
|
|
144
|
+
"@gjsify/workspace": "^0.29.0",
|
|
145
145
|
"cosmiconfig": "^9.0.2",
|
|
146
146
|
"get-tsconfig": "^4.14.0",
|
|
147
147
|
"pkg-types": "^2.3.1",
|
|
@@ -149,13 +149,13 @@
|
|
|
149
149
|
"yargs": "^18.0.0"
|
|
150
150
|
},
|
|
151
151
|
"devDependencies": {
|
|
152
|
-
"@gjsify/unit": "^0.
|
|
152
|
+
"@gjsify/unit": "^0.29.0",
|
|
153
153
|
"@types/yargs": "^17.0.35",
|
|
154
154
|
"typescript": "^6.0.3"
|
|
155
155
|
},
|
|
156
156
|
"peerDependencies": {
|
|
157
|
-
"@gjsify/oxfmt-native": "^0.
|
|
158
|
-
"@gjsify/rolldown-native": "^0.
|
|
157
|
+
"@gjsify/oxfmt-native": "^0.29.0",
|
|
158
|
+
"@gjsify/rolldown-native": "^0.29.0"
|
|
159
159
|
},
|
|
160
160
|
"peerDependenciesMeta": {
|
|
161
161
|
"@gjsify/oxfmt-native": {
|