@gjsify/cli 0.3.21 → 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.
Files changed (67) hide show
  1. package/dist/cli.gjs.mjs +798 -0
  2. package/lib/actions/build.js +4 -17
  3. package/lib/bundler-pick.d.ts +79 -0
  4. package/lib/bundler-pick.js +428 -0
  5. package/lib/commands/foreach.d.ts +16 -0
  6. package/lib/commands/foreach.js +268 -0
  7. package/lib/commands/index.d.ts +2 -0
  8. package/lib/commands/index.js +2 -0
  9. package/lib/commands/install.d.ts +1 -0
  10. package/lib/commands/install.js +222 -26
  11. package/lib/commands/run.d.ts +1 -1
  12. package/lib/commands/run.js +133 -20
  13. package/lib/commands/workspace.d.ts +8 -0
  14. package/lib/commands/workspace.js +69 -0
  15. package/lib/config.js +12 -1
  16. package/lib/index.js +11 -3
  17. package/lib/types/config-data.d.ts +10 -1
  18. package/lib/utils/install-backend-native.d.ts +5 -1
  19. package/lib/utils/install-backend-native.js +88 -11
  20. package/lib/utils/install-backend.d.ts +11 -1
  21. package/lib/utils/install-backend.js +4 -2
  22. package/lib/utils/pkg-json-edit.d.ts +47 -0
  23. package/lib/utils/pkg-json-edit.js +108 -0
  24. package/package.json +36 -12
  25. package/src/actions/build.ts +0 -431
  26. package/src/actions/index.ts +0 -1
  27. package/src/commands/build.ts +0 -146
  28. package/src/commands/check.ts +0 -87
  29. package/src/commands/create.ts +0 -63
  30. package/src/commands/dlx.ts +0 -195
  31. package/src/commands/flatpak/build.ts +0 -225
  32. package/src/commands/flatpak/ci.ts +0 -173
  33. package/src/commands/flatpak/deps.ts +0 -120
  34. package/src/commands/flatpak/index.ts +0 -53
  35. package/src/commands/flatpak/init.ts +0 -191
  36. package/src/commands/flatpak/utils.ts +0 -76
  37. package/src/commands/gettext.ts +0 -258
  38. package/src/commands/gresource.ts +0 -97
  39. package/src/commands/gsettings.ts +0 -87
  40. package/src/commands/index.ts +0 -12
  41. package/src/commands/info.ts +0 -70
  42. package/src/commands/install.ts +0 -195
  43. package/src/commands/run.ts +0 -33
  44. package/src/commands/showcase.ts +0 -149
  45. package/src/config.ts +0 -304
  46. package/src/constants.ts +0 -1
  47. package/src/index.ts +0 -37
  48. package/src/types/cli-build-options.ts +0 -100
  49. package/src/types/command.ts +0 -10
  50. package/src/types/config-data-library.ts +0 -5
  51. package/src/types/config-data-typescript.ts +0 -6
  52. package/src/types/config-data.ts +0 -225
  53. package/src/types/cosmiconfig-result.ts +0 -5
  54. package/src/types/index.ts +0 -6
  55. package/src/utils/check-system-deps.ts +0 -480
  56. package/src/utils/detect-native-packages.ts +0 -153
  57. package/src/utils/discover-showcases.ts +0 -75
  58. package/src/utils/dlx-cache.ts +0 -135
  59. package/src/utils/install-backend-native.ts +0 -363
  60. package/src/utils/install-backend.ts +0 -88
  61. package/src/utils/install-global.ts +0 -182
  62. package/src/utils/normalize-bundler-options.ts +0 -129
  63. package/src/utils/parse-spec.ts +0 -48
  64. package/src/utils/resolve-gjs-entry.ts +0 -96
  65. package/src/utils/resolve-plugin-by-name.ts +0 -106
  66. package/src/utils/run-gjs.ts +0 -90
  67. 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
- }