@gjsify/cli 0.3.20 → 0.4.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 +798 -0
- package/lib/actions/build.js +4 -17
- package/lib/bundler-pick.d.ts +79 -0
- package/lib/bundler-pick.js +428 -0
- package/lib/commands/foreach.d.ts +16 -0
- package/lib/commands/foreach.js +268 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/install.d.ts +1 -0
- package/lib/commands/install.js +222 -26
- package/lib/commands/run.d.ts +1 -1
- package/lib/commands/run.js +133 -20
- package/lib/commands/workspace.d.ts +8 -0
- package/lib/commands/workspace.js +69 -0
- package/lib/config.js +26 -0
- package/lib/index.js +11 -3
- package/lib/types/config-data.d.ts +10 -1
- package/lib/utils/install-backend-native.d.ts +5 -1
- package/lib/utils/install-backend-native.js +88 -11
- package/lib/utils/install-backend.d.ts +11 -1
- package/lib/utils/install-backend.js +4 -2
- package/lib/utils/pkg-json-edit.d.ts +47 -0
- package/lib/utils/pkg-json-edit.js +108 -0
- package/package.json +36 -12
- package/src/actions/build.ts +0 -431
- package/src/actions/index.ts +0 -1
- package/src/commands/build.ts +0 -146
- package/src/commands/check.ts +0 -87
- package/src/commands/create.ts +0 -63
- package/src/commands/dlx.ts +0 -195
- package/src/commands/flatpak/build.ts +0 -225
- package/src/commands/flatpak/ci.ts +0 -173
- package/src/commands/flatpak/deps.ts +0 -120
- package/src/commands/flatpak/index.ts +0 -53
- package/src/commands/flatpak/init.ts +0 -191
- package/src/commands/flatpak/utils.ts +0 -76
- package/src/commands/gettext.ts +0 -258
- package/src/commands/gresource.ts +0 -97
- package/src/commands/gsettings.ts +0 -87
- package/src/commands/index.ts +0 -12
- package/src/commands/info.ts +0 -70
- package/src/commands/install.ts +0 -195
- package/src/commands/run.ts +0 -33
- package/src/commands/showcase.ts +0 -149
- package/src/config.ts +0 -289
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -37
- package/src/types/cli-build-options.ts +0 -100
- package/src/types/command.ts +0 -10
- package/src/types/config-data-library.ts +0 -5
- package/src/types/config-data-typescript.ts +0 -6
- package/src/types/config-data.ts +0 -225
- package/src/types/cosmiconfig-result.ts +0 -5
- package/src/types/index.ts +0 -6
- package/src/utils/check-system-deps.ts +0 -480
- package/src/utils/detect-native-packages.ts +0 -153
- package/src/utils/discover-showcases.ts +0 -75
- package/src/utils/dlx-cache.ts +0 -135
- package/src/utils/install-backend-native.ts +0 -363
- package/src/utils/install-backend.ts +0 -88
- package/src/utils/install-global.ts +0 -182
- package/src/utils/normalize-bundler-options.ts +0 -129
- package/src/utils/parse-spec.ts +0 -48
- package/src/utils/resolve-gjs-entry.ts +0 -96
- package/src/utils/resolve-plugin-by-name.ts +0 -106
- package/src/utils/run-gjs.ts +0 -90
- package/tsconfig.json +0 -16
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
// System dependency checker for gjsify CLI.
|
|
2
|
-
// Uses execFileSync with explicit argument arrays — no shell injection possible.
|
|
3
|
-
// All binary names are hardcoded constants, never derived from user input.
|
|
4
|
-
//
|
|
5
|
-
// Severity model:
|
|
6
|
-
// - 'required' → must be present, exit code 1 if missing
|
|
7
|
-
// - 'optional' → nice to have, only warned about, exit code stays 0
|
|
8
|
-
//
|
|
9
|
-
// Conditional checking: when a project's package.json is reachable from cwd,
|
|
10
|
-
// optional system deps are only checked if the corresponding @gjsify/* package
|
|
11
|
-
// is in the project's dependency tree. A user with only @gjsify/fs in their
|
|
12
|
-
// project never sees a warning about libmanette.
|
|
13
|
-
|
|
14
|
-
import { execFileSync } from 'node:child_process';
|
|
15
|
-
import { join, resolve } from 'node:path';
|
|
16
|
-
import { createRequire } from 'node:module';
|
|
17
|
-
import { pathToFileURL } from 'node:url';
|
|
18
|
-
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
19
|
-
|
|
20
|
-
export type DepSeverity = 'required' | 'optional';
|
|
21
|
-
|
|
22
|
-
export interface DepCheck {
|
|
23
|
-
/** Stable key used for install command lookup, e.g. "gjs" */
|
|
24
|
-
id: string;
|
|
25
|
-
/** Human-readable display name, e.g. "GJS" */
|
|
26
|
-
name: string;
|
|
27
|
-
found: boolean;
|
|
28
|
-
/** Version string when found, e.g. "1.86.0" */
|
|
29
|
-
version?: string;
|
|
30
|
-
/** required = exit 1 if missing, optional = warn only */
|
|
31
|
-
severity: DepSeverity;
|
|
32
|
-
/** For optional deps: which @gjsify/* packages need this lib */
|
|
33
|
-
requiredBy?: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type PackageManager = 'apt' | 'dnf' | 'pacman' | 'zypper' | 'apk' | 'unknown';
|
|
37
|
-
|
|
38
|
-
/** Run a binary and return its stdout trimmed, or null if it fails. */
|
|
39
|
-
function tryExecFile(binary: string, args: string[]): string | null {
|
|
40
|
-
try {
|
|
41
|
-
return execFileSync(binary, args, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Check if a binary exists and optionally capture its version output. */
|
|
48
|
-
function checkBinary(
|
|
49
|
-
id: string,
|
|
50
|
-
name: string,
|
|
51
|
-
binary: string,
|
|
52
|
-
versionArgs: string[],
|
|
53
|
-
severity: DepSeverity,
|
|
54
|
-
parseVersion?: (out: string) => string,
|
|
55
|
-
requiredBy?: string[],
|
|
56
|
-
): DepCheck {
|
|
57
|
-
const out = tryExecFile(binary, versionArgs);
|
|
58
|
-
if (out === null) return { id, name, found: false, severity, requiredBy };
|
|
59
|
-
const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
|
|
60
|
-
return { id, name, found: true, version, severity, requiredBy };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
|
|
64
|
-
function checkPkgConfig(
|
|
65
|
-
id: string,
|
|
66
|
-
name: string,
|
|
67
|
-
libName: string,
|
|
68
|
-
severity: DepSeverity,
|
|
69
|
-
requiredBy?: string[],
|
|
70
|
-
): DepCheck {
|
|
71
|
-
const version = tryExecFile('pkg-config', ['--modversion', libName]);
|
|
72
|
-
if (version === null) return { id, name, found: false, severity, requiredBy };
|
|
73
|
-
return { id, name, found: true, version: version.split('\n')[0], severity, requiredBy };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check for an npm package. Tries the user's project first (cwd), then falls
|
|
78
|
-
* back to the CLI's own node_modules. This way a locally installed version
|
|
79
|
-
* takes precedence, but npx usage still works via the CLI's own dependencies.
|
|
80
|
-
*/
|
|
81
|
-
function checkNpmPackage(
|
|
82
|
-
id: string,
|
|
83
|
-
name: string,
|
|
84
|
-
packageName: string,
|
|
85
|
-
cwd: string,
|
|
86
|
-
severity: DepSeverity,
|
|
87
|
-
requiredBy?: string[],
|
|
88
|
-
): DepCheck {
|
|
89
|
-
// 1. Try user's project
|
|
90
|
-
try {
|
|
91
|
-
const requireFromCwd = createRequire(pathToFileURL(join(cwd, '_check_.js')).href);
|
|
92
|
-
requireFromCwd.resolve(packageName);
|
|
93
|
-
return { id, name, found: true, severity, requiredBy };
|
|
94
|
-
} catch { /* not in project, try CLI fallback */ }
|
|
95
|
-
|
|
96
|
-
// 2. Fallback: CLI's own node_modules
|
|
97
|
-
try {
|
|
98
|
-
const requireFromCli = createRequire(import.meta.url);
|
|
99
|
-
requireFromCli.resolve(packageName);
|
|
100
|
-
return { id, name, found: true, severity, requiredBy };
|
|
101
|
-
} catch {
|
|
102
|
-
return { id, name, found: false, severity, requiredBy };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function detectPackageManager(): PackageManager {
|
|
107
|
-
const managers: PackageManager[] = ['apt', 'dnf', 'pacman', 'zypper', 'apk'];
|
|
108
|
-
for (const pm of managers) {
|
|
109
|
-
const result = tryExecFile('which', [pm]);
|
|
110
|
-
if (result !== null) return pm;
|
|
111
|
-
}
|
|
112
|
-
return 'unknown';
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// @gjsify/* package → system dependency mapping
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
//
|
|
119
|
-
// Lists which OPTIONAL system dependencies each gjsify package needs at
|
|
120
|
-
// runtime. Required deps (gjs, gtk4, libsoup3, libadwaita, etc.) are checked
|
|
121
|
-
// unconditionally because they're needed by the core toolchain.
|
|
122
|
-
//
|
|
123
|
-
// Used by `runAllChecks(cwd)` to skip optional deps for packages the user
|
|
124
|
-
// isn't using. If a project doesn't depend on @gjsify/gamepad, libmanette
|
|
125
|
-
// is not checked (and won't appear as a warning).
|
|
126
|
-
|
|
127
|
-
interface OptionalDep {
|
|
128
|
-
/** DepCheck id */
|
|
129
|
-
id: string;
|
|
130
|
-
/** Display name */
|
|
131
|
-
name: string;
|
|
132
|
-
/** pkg-config library name */
|
|
133
|
-
pkgName: string;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Optional system deps keyed by their DepCheck id. */
|
|
137
|
-
const OPTIONAL_DEPS: Record<string, OptionalDep> = {
|
|
138
|
-
manette: { id: 'manette', name: 'libmanette', pkgName: 'manette-0.2' },
|
|
139
|
-
gstreamer: { id: 'gstreamer', name: 'GStreamer', pkgName: 'gstreamer-1.0' },
|
|
140
|
-
'gst-app': { id: 'gst-app', name: 'GStreamer App', pkgName: 'gstreamer-app-1.0' },
|
|
141
|
-
'gdk-pixbuf':{ id: 'gdk-pixbuf', name: 'GdkPixbuf', pkgName: 'gdk-pixbuf-2.0' },
|
|
142
|
-
pango: { id: 'pango', name: 'Pango', pkgName: 'pango' },
|
|
143
|
-
pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
|
|
144
|
-
webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
|
|
145
|
-
cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
|
|
150
|
-
* Generated by walking each package's `gi://` imports (excluding the always-
|
|
151
|
-
* available trio GLib/GObject/Gio).
|
|
152
|
-
*
|
|
153
|
-
* Used to compute the set of optional deps to check for a given project.
|
|
154
|
-
*/
|
|
155
|
-
const PACKAGE_DEPS: Record<string, string[]> = {
|
|
156
|
-
'@gjsify/gamepad': ['manette'],
|
|
157
|
-
'@gjsify/webaudio': ['gstreamer', 'gst-app'],
|
|
158
|
-
'@gjsify/iframe': ['webkitgtk'],
|
|
159
|
-
'@gjsify/canvas2d': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
160
|
-
'@gjsify/canvas2d-core': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
|
|
161
|
-
'@gjsify/dom-elements': ['gdk-pixbuf'],
|
|
162
|
-
// @gjsify/webgl needs the gwebgl npm package (Vala prebuild) — handled
|
|
163
|
-
// as a special-case checkNpmPackage rather than checkPkgConfig in
|
|
164
|
-
// runOptionalChecks. Mapping it here so its presence in the project's
|
|
165
|
-
// dep tree triggers the check.
|
|
166
|
-
'@gjsify/webgl': ['gwebgl'],
|
|
167
|
-
// @gjsify/event-bridge only needs gtk4/gdk which are already in the
|
|
168
|
-
// required set, so it doesn't need an optional entry.
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
/** Walk up from cwd looking for the nearest package.json. */
|
|
172
|
-
function findProjectRoot(cwd: string): string | null {
|
|
173
|
-
let dir = resolve(cwd);
|
|
174
|
-
while (true) {
|
|
175
|
-
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
176
|
-
const parent = resolve(dir, '..');
|
|
177
|
-
if (parent === dir) return null;
|
|
178
|
-
dir = parent;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Discover which @gjsify/* packages a project depends on (transitively),
|
|
184
|
-
* by walking the node_modules tree from the project root. Returns a Set of
|
|
185
|
-
* package names like '@gjsify/webgl', '@gjsify/canvas2d'.
|
|
186
|
-
*
|
|
187
|
-
* If the project root cannot be determined or has no node_modules, returns
|
|
188
|
-
* `null` to signal "check all optional deps" — this matches the historical
|
|
189
|
-
* behaviour where `gjsify check` outside any project shows the full list.
|
|
190
|
-
*/
|
|
191
|
-
function discoverGjsifyPackages(cwd: string): Set<string> | null {
|
|
192
|
-
const root = findProjectRoot(cwd);
|
|
193
|
-
if (!root) return null;
|
|
194
|
-
|
|
195
|
-
const nodeModulesDir = join(root, 'node_modules', '@gjsify');
|
|
196
|
-
if (!existsSync(nodeModulesDir)) {
|
|
197
|
-
// Project exists but no @gjsify/* installed → only need core checks.
|
|
198
|
-
return new Set();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Read top-level package.json to determine if @gjsify/cli is the only
|
|
202
|
-
// dep (in which case we still want to warn about everything the CLI
|
|
203
|
-
// transitively brings in). Otherwise scan node_modules for installed packages.
|
|
204
|
-
const pkgJsonPath = join(root, 'package.json');
|
|
205
|
-
let topPkg: Record<string, unknown> = {};
|
|
206
|
-
try {
|
|
207
|
-
topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
208
|
-
} catch { /* ignore */ }
|
|
209
|
-
|
|
210
|
-
const directDeps = {
|
|
211
|
-
...(topPkg.dependencies as Record<string, string> | undefined),
|
|
212
|
-
...(topPkg.devDependencies as Record<string, string> | undefined),
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const found = new Set<string>();
|
|
216
|
-
try {
|
|
217
|
-
for (const entry of readdirSync(nodeModulesDir, { withFileTypes: true })) {
|
|
218
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
219
|
-
found.add(`@gjsify/${entry.name}`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} catch { /* ignore */ }
|
|
223
|
-
|
|
224
|
-
// Also include direct deps even if node_modules walk failed
|
|
225
|
-
for (const dep of Object.keys(directDeps)) {
|
|
226
|
-
if (dep.startsWith('@gjsify/')) found.add(dep);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return found;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Compute the set of optional dep ids that should be checked for the
|
|
234
|
-
* current project. Returns null to indicate "check all" (no project context).
|
|
235
|
-
*/
|
|
236
|
-
function computeNeededOptionalDeps(cwd: string): Set<string> | null {
|
|
237
|
-
const installedPackages = discoverGjsifyPackages(cwd);
|
|
238
|
-
if (installedPackages === null) return null; // no project → check everything
|
|
239
|
-
|
|
240
|
-
const needed = new Set<string>();
|
|
241
|
-
for (const pkg of installedPackages) {
|
|
242
|
-
const deps = PACKAGE_DEPS[pkg];
|
|
243
|
-
if (deps) for (const id of deps) needed.add(id);
|
|
244
|
-
}
|
|
245
|
-
return needed;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Run all dependency checks. Used by `gjsify check` to show full system status.
|
|
250
|
-
*
|
|
251
|
-
* Required deps (gjs, gtk4, libsoup3, libadwaita, gobject-introspection,
|
|
252
|
-
* blueprint-compiler, pkg-config, meson) are always checked.
|
|
253
|
-
*
|
|
254
|
-
* Optional deps are checked conditionally based on which @gjsify/* packages
|
|
255
|
-
* the project (resolved from cwd) actually consumes. If no project context
|
|
256
|
-
* is available, all optional deps are checked.
|
|
257
|
-
*/
|
|
258
|
-
export function runAllChecks(cwd: string): DepCheck[] {
|
|
259
|
-
const needed = computeNeededOptionalDeps(cwd);
|
|
260
|
-
return [...runMinimalChecks(), ...runRequiredChecks(cwd), ...runOptionalChecks(needed, cwd)];
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Minimal checks needed to run any GJS example (Node + GJS binaries only).
|
|
265
|
-
* Used by `gjsify showcase` for examples that have no native deps.
|
|
266
|
-
*/
|
|
267
|
-
export function runMinimalChecks(): DepCheck[] {
|
|
268
|
-
const results: DepCheck[] = [];
|
|
269
|
-
|
|
270
|
-
// Node.js — always present
|
|
271
|
-
results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'required' });
|
|
272
|
-
|
|
273
|
-
// GJS
|
|
274
|
-
results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], 'required',
|
|
275
|
-
(out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
|
|
276
|
-
|
|
277
|
-
return results;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
|
|
281
|
-
export function checkGwebgl(cwd: string): DepCheck {
|
|
282
|
-
return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd, 'optional', ['@gjsify/webgl']);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Required system dependencies — always checked, missing → exit 1.
|
|
287
|
-
* Includes the core build toolchain (pkg-config, meson, blueprint-compiler)
|
|
288
|
-
* and the foundational libraries (gtk4, libadwaita, libsoup3,
|
|
289
|
-
* gobject-introspection) that nearly every gjsify app needs.
|
|
290
|
-
*/
|
|
291
|
-
function runRequiredChecks(_cwd: string): DepCheck[] {
|
|
292
|
-
const results: DepCheck[] = [];
|
|
293
|
-
|
|
294
|
-
// Build toolchain
|
|
295
|
-
results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler',
|
|
296
|
-
'blueprint-compiler', ['--version'], 'required'));
|
|
297
|
-
results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version'], 'required'));
|
|
298
|
-
results.push(checkBinary('meson', 'Meson', 'meson', ['--version'], 'required'));
|
|
299
|
-
|
|
300
|
-
// Foundational libraries
|
|
301
|
-
results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4', 'required'));
|
|
302
|
-
results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1', 'required'));
|
|
303
|
-
results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0', 'required'));
|
|
304
|
-
results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection',
|
|
305
|
-
'gobject-introspection-1.0', 'required'));
|
|
306
|
-
|
|
307
|
-
return results;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Optional system dependencies — only checked if the corresponding @gjsify/*
|
|
312
|
-
* package is in use. Missing optional deps generate warnings, not errors.
|
|
313
|
-
*
|
|
314
|
-
* @param needed Set of optional dep ids to check, or null to check all.
|
|
315
|
-
* @param cwd Used to resolve the gwebgl npm package check.
|
|
316
|
-
*/
|
|
317
|
-
function runOptionalChecks(needed: Set<string> | null, cwd: string): DepCheck[] {
|
|
318
|
-
const results: DepCheck[] = [];
|
|
319
|
-
|
|
320
|
-
for (const [id, dep] of Object.entries(OPTIONAL_DEPS)) {
|
|
321
|
-
if (needed !== null && !needed.has(id)) continue;
|
|
322
|
-
|
|
323
|
-
const requiredBy = Object.entries(PACKAGE_DEPS)
|
|
324
|
-
.filter(([, ids]) => ids.includes(id))
|
|
325
|
-
.map(([pkg]) => pkg);
|
|
326
|
-
|
|
327
|
-
results.push(checkPkgConfig(dep.id, dep.name, dep.pkgName, 'optional', requiredBy));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// gwebgl npm package — special case (not a pkg-config lib). Only checked
|
|
331
|
-
// when the project directly or transitively depends on @gjsify/webgl —
|
|
332
|
-
// since the CLI tarball no longer ships any showcase example packages,
|
|
333
|
-
// gwebgl is not part of the CLI's own dep tree, so reporting it for every
|
|
334
|
-
// project would always be `found: false`. `needed === null` means "check
|
|
335
|
-
// everything" (no project context).
|
|
336
|
-
const hasWebglDep = needed === null || needed.has('gwebgl');
|
|
337
|
-
if (hasWebglDep) {
|
|
338
|
-
results.push(checkGwebgl(cwd));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return results;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Per-package-manager install package names, keyed by dep id.
|
|
345
|
-
// Entries with multiple space-separated packages are intentional (one dep needs multiple system pkgs).
|
|
346
|
-
const PM_PACKAGES: Record<PackageManager, Partial<Record<string, string>>> = {
|
|
347
|
-
apt: {
|
|
348
|
-
gjs: 'gjs',
|
|
349
|
-
'blueprint-compiler': 'blueprint-compiler',
|
|
350
|
-
'pkg-config': 'pkg-config',
|
|
351
|
-
meson: 'meson',
|
|
352
|
-
gtk4: 'libgtk-4-dev',
|
|
353
|
-
libadwaita: 'libadwaita-1-dev',
|
|
354
|
-
libsoup3: 'libsoup-3.0-dev',
|
|
355
|
-
webkitgtk: 'libwebkit2gtk-6.0-dev',
|
|
356
|
-
'gobject-introspection': 'gobject-introspection libgirepository1.0-dev',
|
|
357
|
-
manette: 'libmanette-0.2-0 gir1.2-manette-0.2',
|
|
358
|
-
gstreamer: 'libgstreamer1.0-dev',
|
|
359
|
-
'gst-app': 'libgstreamer-plugins-base1.0-dev gir1.2-gst-plugins-base-1.0',
|
|
360
|
-
'gdk-pixbuf': 'libgdk-pixbuf-2.0-dev',
|
|
361
|
-
pango: 'libpango1.0-dev',
|
|
362
|
-
pangocairo: 'libpango1.0-dev',
|
|
363
|
-
cairo: 'libcairo2-dev',
|
|
364
|
-
},
|
|
365
|
-
dnf: {
|
|
366
|
-
gjs: 'gjs',
|
|
367
|
-
'blueprint-compiler': 'blueprint-compiler',
|
|
368
|
-
'pkg-config': 'pkgconf-pkg-config',
|
|
369
|
-
meson: 'meson',
|
|
370
|
-
gtk4: 'gtk4-devel',
|
|
371
|
-
libadwaita: 'libadwaita-devel',
|
|
372
|
-
libsoup3: 'libsoup3-devel',
|
|
373
|
-
webkitgtk: 'webkitgtk6.0-devel',
|
|
374
|
-
'gobject-introspection': 'gobject-introspection-devel',
|
|
375
|
-
manette: 'libmanette-devel',
|
|
376
|
-
gstreamer: 'gstreamer1-devel',
|
|
377
|
-
'gst-app': 'gstreamer1-plugins-base-devel',
|
|
378
|
-
'gdk-pixbuf': 'gdk-pixbuf2-devel',
|
|
379
|
-
pango: 'pango-devel',
|
|
380
|
-
pangocairo: 'pango-devel',
|
|
381
|
-
cairo: 'cairo-devel',
|
|
382
|
-
},
|
|
383
|
-
pacman: {
|
|
384
|
-
gjs: 'gjs',
|
|
385
|
-
'blueprint-compiler': 'blueprint-compiler',
|
|
386
|
-
'pkg-config': 'pkgconf',
|
|
387
|
-
meson: 'meson',
|
|
388
|
-
gtk4: 'gtk4',
|
|
389
|
-
libadwaita: 'libadwaita',
|
|
390
|
-
libsoup3: 'libsoup3',
|
|
391
|
-
webkitgtk: 'webkitgtk-6.0',
|
|
392
|
-
'gobject-introspection': 'gobject-introspection',
|
|
393
|
-
manette: 'libmanette',
|
|
394
|
-
gstreamer: 'gstreamer',
|
|
395
|
-
'gst-app': 'gst-plugins-base',
|
|
396
|
-
'gdk-pixbuf': 'gdk-pixbuf2',
|
|
397
|
-
pango: 'pango',
|
|
398
|
-
pangocairo: 'pango',
|
|
399
|
-
cairo: 'cairo',
|
|
400
|
-
},
|
|
401
|
-
zypper: {
|
|
402
|
-
gjs: 'gjs',
|
|
403
|
-
'blueprint-compiler': 'blueprint-compiler',
|
|
404
|
-
'pkg-config': 'pkg-config',
|
|
405
|
-
meson: 'meson',
|
|
406
|
-
gtk4: 'gtk4-devel',
|
|
407
|
-
libadwaita: 'libadwaita-devel',
|
|
408
|
-
libsoup3: 'libsoup-3_0-devel',
|
|
409
|
-
webkitgtk: 'webkitgtk6_0-devel',
|
|
410
|
-
'gobject-introspection': 'gobject-introspection-devel',
|
|
411
|
-
manette: 'libmanette-0_2-0-devel',
|
|
412
|
-
gstreamer: 'gstreamer-devel',
|
|
413
|
-
'gst-app': 'gstreamer-plugins-base-devel',
|
|
414
|
-
'gdk-pixbuf': 'gdk-pixbuf-devel',
|
|
415
|
-
pango: 'pango-devel',
|
|
416
|
-
pangocairo: 'pango-devel',
|
|
417
|
-
cairo: 'cairo-devel',
|
|
418
|
-
},
|
|
419
|
-
apk: {
|
|
420
|
-
gjs: 'gjs',
|
|
421
|
-
'blueprint-compiler': 'blueprint-compiler',
|
|
422
|
-
'pkg-config': 'pkgconf',
|
|
423
|
-
meson: 'meson',
|
|
424
|
-
gtk4: 'gtk4.0-dev',
|
|
425
|
-
libadwaita: 'libadwaita-dev',
|
|
426
|
-
libsoup3: 'libsoup3-dev',
|
|
427
|
-
webkitgtk: 'webkit2gtk-6.0-dev',
|
|
428
|
-
'gobject-introspection': 'gobject-introspection-dev',
|
|
429
|
-
manette: 'libmanette-dev',
|
|
430
|
-
gstreamer: 'gstreamer-dev',
|
|
431
|
-
'gst-app': 'gst-plugins-base-dev',
|
|
432
|
-
'gdk-pixbuf': 'gdk-pixbuf-dev',
|
|
433
|
-
pango: 'pango-dev',
|
|
434
|
-
pangocairo: 'pango-dev',
|
|
435
|
-
cairo: 'cairo-dev',
|
|
436
|
-
},
|
|
437
|
-
unknown: {},
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
const PM_INSTALL_PREFIX: Record<PackageManager, string> = {
|
|
441
|
-
apt: 'sudo apt install',
|
|
442
|
-
dnf: 'sudo dnf install',
|
|
443
|
-
pacman: 'sudo pacman -S',
|
|
444
|
-
zypper: 'sudo zypper install',
|
|
445
|
-
apk: 'sudo apk add',
|
|
446
|
-
unknown: '',
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Build a suggested install command for missing dependencies.
|
|
451
|
-
* gwebgl is an npm package, not a system package — handled separately.
|
|
452
|
-
* Returns null when package manager is unknown or no installable deps are missing.
|
|
453
|
-
*/
|
|
454
|
-
export function buildInstallCommand(pm: PackageManager, missing: DepCheck[]): string | null {
|
|
455
|
-
if (pm === 'unknown') return null;
|
|
456
|
-
|
|
457
|
-
const pkgMap = PM_PACKAGES[pm];
|
|
458
|
-
const pkgs: string[] = [];
|
|
459
|
-
const npmDeps: string[] = [];
|
|
460
|
-
|
|
461
|
-
for (const dep of missing) {
|
|
462
|
-
if (dep.id === 'gwebgl') {
|
|
463
|
-
npmDeps.push('@gjsify/webgl');
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
if (dep.id === 'nodejs') continue; // can't be missing if we're running
|
|
467
|
-
const pkg = pkgMap[dep.id];
|
|
468
|
-
if (pkg) pkgs.push(pkg);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const lines: string[] = [];
|
|
472
|
-
if (pkgs.length > 0) {
|
|
473
|
-
lines.push(`${PM_INSTALL_PREFIX[pm]} ${pkgs.join(' ')}`);
|
|
474
|
-
}
|
|
475
|
-
if (npmDeps.length > 0) {
|
|
476
|
-
lines.push(`npm install ${npmDeps.join(' ')}`);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return lines.length > 0 ? lines.join('\n ') : null;
|
|
480
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// Utility to find npm packages with gjsify native prebuilds.
|
|
2
|
-
// Packages declare: "gjsify": { "prebuilds": "<dir>" } in their package.json.
|
|
3
|
-
// The CLI uses this to auto-set LD_LIBRARY_PATH / GI_TYPELIB_PATH before running gjs.
|
|
4
|
-
//
|
|
5
|
-
// One algorithm — `detectNativePackages(startDir)` — walks up from `startDir`
|
|
6
|
-
// and exhaustively scans every `node_modules` it finds. Used by:
|
|
7
|
-
// * `gjsify run`, `gjsify info`, `gjsify install` — startDir = process.cwd()
|
|
8
|
-
// * `runGjsBundle()` — startDir = dirname(bundlePath), so DLX-cache layouts
|
|
9
|
-
// (`~/.cache/gjsify/dlx/<sha>/.../node_modules/<pkg>/dist/bundle.js`) get
|
|
10
|
-
// their full transitive prebuild set picked up automatically. The
|
|
11
|
-
// transitive walk is what makes `gjsify showcase` / `gjsify dlx` work
|
|
12
|
-
// for packages whose Vala typelibs live in *indirect* deps.
|
|
13
|
-
|
|
14
|
-
import { readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
15
|
-
import { join, resolve } from 'node:path';
|
|
16
|
-
|
|
17
|
-
export interface NativePackage {
|
|
18
|
-
/** npm package name, e.g. "@gjsify/webgl" */
|
|
19
|
-
name: string;
|
|
20
|
-
/** Absolute path to the arch-specific prebuilds dir, e.g. "/…/@gjsify/webgl/prebuilds/linux-x86_64" */
|
|
21
|
-
prebuildsDir: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Map Node.js process.arch values to the convention used in prebuilds/ directories. */
|
|
25
|
-
function nodeArchToLinuxArch(arch: string): string {
|
|
26
|
-
const map: Record<string, string> = {
|
|
27
|
-
x64: 'x86_64',
|
|
28
|
-
arm64: 'aarch64',
|
|
29
|
-
arm: 'armv7',
|
|
30
|
-
ia32: 'i686',
|
|
31
|
-
};
|
|
32
|
-
return map[arch] ?? arch;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Read a package.json file and return its parsed content, or null on error. */
|
|
36
|
-
function readPackageJson(pkgJsonPath: string): Record<string, unknown> | null {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Scan all packages in a node_modules directory for gjsify native prebuilds.
|
|
46
|
-
* Handles scoped packages (@scope/name) as well as flat packages.
|
|
47
|
-
*/
|
|
48
|
-
function scanNodeModules(nodeModulesDir: string, arch: string): NativePackage[] {
|
|
49
|
-
const results: NativePackage[] = [];
|
|
50
|
-
if (!existsSync(nodeModulesDir)) return results;
|
|
51
|
-
|
|
52
|
-
let entries: string[];
|
|
53
|
-
try {
|
|
54
|
-
entries = readdirSync(nodeModulesDir);
|
|
55
|
-
} catch {
|
|
56
|
-
return results;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const entry of entries) {
|
|
60
|
-
if (entry.startsWith('.')) continue;
|
|
61
|
-
|
|
62
|
-
if (entry.startsWith('@')) {
|
|
63
|
-
// Scoped packages — one more level deep
|
|
64
|
-
const scopeDir = join(nodeModulesDir, entry);
|
|
65
|
-
let scopeEntries: string[];
|
|
66
|
-
try {
|
|
67
|
-
scopeEntries = readdirSync(scopeDir);
|
|
68
|
-
} catch {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
for (const scopedPkg of scopeEntries) {
|
|
72
|
-
const pkgDir = join(scopeDir, scopedPkg);
|
|
73
|
-
const native = checkPackage(pkgDir, `${entry}/${scopedPkg}`, arch);
|
|
74
|
-
if (native) results.push(native);
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
const pkgDir = join(nodeModulesDir, entry);
|
|
78
|
-
const native = checkPackage(pkgDir, entry, arch);
|
|
79
|
-
if (native) results.push(native);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return results;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Check a single package directory for gjsify prebuilds metadata. */
|
|
87
|
-
function checkPackage(pkgDir: string, name: string, arch: string): NativePackage | null {
|
|
88
|
-
const pkgJson = readPackageJson(join(pkgDir, 'package.json'));
|
|
89
|
-
if (!pkgJson) return null;
|
|
90
|
-
|
|
91
|
-
const gjsifyMeta = pkgJson['gjsify'];
|
|
92
|
-
if (!gjsifyMeta || typeof gjsifyMeta !== 'object') return null;
|
|
93
|
-
|
|
94
|
-
const prebuildsField = (gjsifyMeta as Record<string, unknown>)['prebuilds'];
|
|
95
|
-
if (typeof prebuildsField !== 'string') return null;
|
|
96
|
-
|
|
97
|
-
const prebuildsDir = join(pkgDir, prebuildsField, `linux-${arch}`);
|
|
98
|
-
if (!existsSync(prebuildsDir)) return null;
|
|
99
|
-
|
|
100
|
-
return { name, prebuildsDir };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Walk up the directory tree from `startDir` and merge native packages found
|
|
105
|
-
* in every `node_modules` encountered.
|
|
106
|
-
*
|
|
107
|
-
* We keep walking past the first node_modules because yarn v4 / pnpm hoisting
|
|
108
|
-
* puts a project's direct deps in a local node_modules (often just `.cache/`
|
|
109
|
-
* or a subset) while hoisted transitive deps live in a root `node_modules`
|
|
110
|
-
* higher up. Node's own resolver also walks the chain — returning only the
|
|
111
|
-
* first hit would miss root-hoisted native packages.
|
|
112
|
-
*
|
|
113
|
-
* Deduplication: the first match for a given package name wins (closer
|
|
114
|
-
* node_modules shadows outer ones), matching Node.js resolution semantics.
|
|
115
|
-
*/
|
|
116
|
-
export function detectNativePackages(startDir: string): NativePackage[] {
|
|
117
|
-
const arch = nodeArchToLinuxArch(process.arch);
|
|
118
|
-
const merged: NativePackage[] = [];
|
|
119
|
-
const seen = new Set<string>();
|
|
120
|
-
let dir = resolve(startDir);
|
|
121
|
-
|
|
122
|
-
while (true) {
|
|
123
|
-
const nodeModulesDir = join(dir, 'node_modules');
|
|
124
|
-
if (existsSync(nodeModulesDir)) {
|
|
125
|
-
for (const pkg of scanNodeModules(nodeModulesDir, arch)) {
|
|
126
|
-
if (seen.has(pkg.name)) continue;
|
|
127
|
-
seen.add(pkg.name);
|
|
128
|
-
merged.push(pkg);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const parent = resolve(dir, '..');
|
|
132
|
-
if (parent === dir) break; // reached filesystem root
|
|
133
|
-
dir = parent;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return merged;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Build the LD_LIBRARY_PATH and GI_TYPELIB_PATH env var values for the detected native packages.
|
|
141
|
-
* Prepends the new paths to any existing values from the environment.
|
|
142
|
-
*/
|
|
143
|
-
export function buildNativeEnv(packages: NativePackage[]): { LD_LIBRARY_PATH: string; GI_TYPELIB_PATH: string } {
|
|
144
|
-
const dirs = packages.map(p => p.prebuildsDir);
|
|
145
|
-
|
|
146
|
-
const existing_ld = process.env['LD_LIBRARY_PATH'] ?? '';
|
|
147
|
-
const existing_gi = process.env['GI_TYPELIB_PATH'] ?? '';
|
|
148
|
-
|
|
149
|
-
const LD_LIBRARY_PATH = [...dirs, ...(existing_ld ? [existing_ld] : [])].join(':');
|
|
150
|
-
const GI_TYPELIB_PATH = [...dirs, ...(existing_gi ? [existing_gi] : [])].join(':');
|
|
151
|
-
|
|
152
|
-
return { LD_LIBRARY_PATH, GI_TYPELIB_PATH };
|
|
153
|
-
}
|