@gjsify/cli 0.1.7 → 0.1.9

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 (36) hide show
  1. package/lib/actions/build.d.ts +19 -0
  2. package/lib/actions/build.js +80 -11
  3. package/lib/commands/build.js +6 -0
  4. package/lib/commands/check.js +41 -12
  5. package/lib/commands/create.d.ts +6 -0
  6. package/lib/commands/create.js +15 -0
  7. package/lib/commands/index.d.ts +1 -0
  8. package/lib/commands/index.js +1 -0
  9. package/lib/commands/showcase.js +35 -33
  10. package/lib/config.js +2 -0
  11. package/lib/index.js +2 -1
  12. package/lib/types/cli-build-options.d.ts +7 -0
  13. package/lib/types/config-data.d.ts +5 -0
  14. package/lib/utils/check-system-deps.d.ts +14 -2
  15. package/lib/utils/check-system-deps.js +214 -46
  16. package/lib/utils/detect-native-packages.d.ts +4 -0
  17. package/lib/utils/detect-native-packages.js +12 -0
  18. package/lib/utils/discover-showcases.d.ts +19 -0
  19. package/lib/utils/{discover-examples.js → discover-showcases.js} +14 -15
  20. package/lib/utils/run-gjs.js +9 -0
  21. package/package.json +10 -50
  22. package/src/actions/build.ts +99 -13
  23. package/src/commands/build.ts +6 -0
  24. package/src/commands/check.ts +45 -12
  25. package/src/commands/create.ts +21 -0
  26. package/src/commands/index.ts +2 -1
  27. package/src/commands/showcase.ts +36 -34
  28. package/src/config.ts +1 -0
  29. package/src/index.ts +2 -1
  30. package/src/types/cli-build-options.ts +8 -1
  31. package/src/types/config-data.ts +6 -1
  32. package/src/utils/check-system-deps.ts +264 -45
  33. package/src/utils/detect-native-packages.ts +12 -0
  34. package/src/utils/{discover-examples.ts → discover-showcases.ts} +18 -19
  35. package/src/utils/run-gjs.ts +11 -0
  36. package/lib/utils/discover-examples.d.ts +0 -19
@@ -1,10 +1,20 @@
1
1
  // System dependency checker for gjsify CLI.
2
2
  // Uses execFileSync with explicit argument arrays — no shell injection possible.
3
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.
4
13
  import { execFileSync } from 'node:child_process';
5
- import { join } from 'node:path';
14
+ import { join, resolve } from 'node:path';
6
15
  import { createRequire } from 'node:module';
7
16
  import { pathToFileURL } from 'node:url';
17
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
8
18
  /** Run a binary and return its stdout trimmed, or null if it fails. */
9
19
  function tryExecFile(binary, args) {
10
20
  try {
@@ -15,41 +25,41 @@ function tryExecFile(binary, args) {
15
25
  }
16
26
  }
17
27
  /** Check if a binary exists and optionally capture its version output. */
18
- function checkBinary(id, name, binary, versionArgs, parseVersion) {
28
+ function checkBinary(id, name, binary, versionArgs, severity, parseVersion, requiredBy) {
19
29
  const out = tryExecFile(binary, versionArgs);
20
30
  if (out === null)
21
- return { id, name, found: false };
31
+ return { id, name, found: false, severity, requiredBy };
22
32
  const version = parseVersion ? parseVersion(out) : out.split('\n')[0] ?? out;
23
- return { id, name, found: true, version };
33
+ return { id, name, found: true, version, severity, requiredBy };
24
34
  }
25
35
  /** Check a pkg-config library. pkg-config --modversion returns version on stdout. */
26
- function checkPkgConfig(id, name, libName) {
36
+ function checkPkgConfig(id, name, libName, severity, requiredBy) {
27
37
  const version = tryExecFile('pkg-config', ['--modversion', libName]);
28
38
  if (version === null)
29
- return { id, name, found: false };
30
- return { id, name, found: true, version: version.split('\n')[0] };
39
+ return { id, name, found: false, severity, requiredBy };
40
+ return { id, name, found: true, version: version.split('\n')[0], severity, requiredBy };
31
41
  }
32
42
  /**
33
43
  * Check for an npm package. Tries the user's project first (cwd), then falls
34
44
  * back to the CLI's own node_modules. This way a locally installed version
35
45
  * takes precedence, but npx usage still works via the CLI's own dependencies.
36
46
  */
37
- function checkNpmPackage(id, name, packageName, cwd) {
47
+ function checkNpmPackage(id, name, packageName, cwd, severity, requiredBy) {
38
48
  // 1. Try user's project
39
49
  try {
40
50
  const requireFromCwd = createRequire(pathToFileURL(join(cwd, '_check_.js')).href);
41
51
  requireFromCwd.resolve(packageName);
42
- return { id, name, found: true };
52
+ return { id, name, found: true, severity, requiredBy };
43
53
  }
44
54
  catch { /* not in project, try CLI fallback */ }
45
55
  // 2. Fallback: CLI's own node_modules
46
56
  try {
47
57
  const requireFromCli = createRequire(import.meta.url);
48
58
  requireFromCli.resolve(packageName);
49
- return { id, name, found: true };
59
+ return { id, name, found: true, severity, requiredBy };
50
60
  }
51
61
  catch {
52
- return { id, name, found: false };
62
+ return { id, name, found: false, severity, requiredBy };
53
63
  }
54
64
  }
55
65
  export function detectPackageManager() {
@@ -61,56 +71,179 @@ export function detectPackageManager() {
61
71
  }
62
72
  return 'unknown';
63
73
  }
74
+ /** Optional system deps keyed by their DepCheck id. */
75
+ const OPTIONAL_DEPS = {
76
+ manette: { id: 'manette', name: 'libmanette', pkgName: 'manette-0.2' },
77
+ gstreamer: { id: 'gstreamer', name: 'GStreamer', pkgName: 'gstreamer-1.0' },
78
+ 'gst-app': { id: 'gst-app', name: 'GStreamer App', pkgName: 'gstreamer-app-1.0' },
79
+ 'gdk-pixbuf': { id: 'gdk-pixbuf', name: 'GdkPixbuf', pkgName: 'gdk-pixbuf-2.0' },
80
+ pango: { id: 'pango', name: 'Pango', pkgName: 'pango' },
81
+ pangocairo: { id: 'pangocairo', name: 'PangoCairo', pkgName: 'pangocairo' },
82
+ webkitgtk: { id: 'webkitgtk', name: 'WebKitGTK', pkgName: 'webkitgtk-6.0' },
83
+ cairo: { id: 'cairo', name: 'Cairo', pkgName: 'cairo' },
84
+ };
85
+ /**
86
+ * Map of @gjsify/* package name → ids of OPTIONAL_DEPS this package needs.
87
+ * Generated by walking each package's `gi://` imports (excluding the always-
88
+ * available trio GLib/GObject/Gio).
89
+ *
90
+ * Used to compute the set of optional deps to check for a given project.
91
+ */
92
+ const PACKAGE_DEPS = {
93
+ '@gjsify/gamepad': ['manette'],
94
+ '@gjsify/webaudio': ['gstreamer', 'gst-app'],
95
+ '@gjsify/iframe': ['webkitgtk'],
96
+ '@gjsify/canvas2d': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
97
+ '@gjsify/canvas2d-core': ['gdk-pixbuf', 'pango', 'pangocairo', 'cairo'],
98
+ '@gjsify/dom-elements': ['gdk-pixbuf'],
99
+ // @gjsify/webgl, @gjsify/event-bridge only need gtk4/gdk which are
100
+ // already in the required set, so they don't need optional entries.
101
+ };
102
+ /** Walk up from cwd looking for the nearest package.json. */
103
+ function findProjectRoot(cwd) {
104
+ let dir = resolve(cwd);
105
+ while (true) {
106
+ if (existsSync(join(dir, 'package.json')))
107
+ return dir;
108
+ const parent = resolve(dir, '..');
109
+ if (parent === dir)
110
+ return null;
111
+ dir = parent;
112
+ }
113
+ }
114
+ /**
115
+ * Discover which @gjsify/* packages a project depends on (transitively),
116
+ * by walking the node_modules tree from the project root. Returns a Set of
117
+ * package names like '@gjsify/webgl', '@gjsify/canvas2d'.
118
+ *
119
+ * If the project root cannot be determined or has no node_modules, returns
120
+ * `null` to signal "check all optional deps" — this matches the historical
121
+ * behaviour where `gjsify check` outside any project shows the full list.
122
+ */
123
+ function discoverGjsifyPackages(cwd) {
124
+ const root = findProjectRoot(cwd);
125
+ if (!root)
126
+ return null;
127
+ const nodeModulesDir = join(root, 'node_modules', '@gjsify');
128
+ if (!existsSync(nodeModulesDir)) {
129
+ // Project exists but no @gjsify/* installed → only need core checks.
130
+ return new Set();
131
+ }
132
+ // Read top-level package.json to determine if @gjsify/cli is the only
133
+ // dep (in which case we still want to warn about everything the CLI
134
+ // transitively brings in). Otherwise scan node_modules for installed packages.
135
+ const pkgJsonPath = join(root, 'package.json');
136
+ let topPkg = {};
137
+ try {
138
+ topPkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
139
+ }
140
+ catch { /* ignore */ }
141
+ const directDeps = {
142
+ ...topPkg.dependencies,
143
+ ...topPkg.devDependencies,
144
+ };
145
+ const found = new Set();
146
+ try {
147
+ for (const entry of readdirSync(nodeModulesDir, { withFileTypes: true })) {
148
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
149
+ found.add(`@gjsify/${entry.name}`);
150
+ }
151
+ }
152
+ }
153
+ catch { /* ignore */ }
154
+ // Also include direct deps even if node_modules walk failed
155
+ for (const dep of Object.keys(directDeps)) {
156
+ if (dep.startsWith('@gjsify/'))
157
+ found.add(dep);
158
+ }
159
+ return found;
160
+ }
161
+ /**
162
+ * Compute the set of optional dep ids that should be checked for the
163
+ * current project. Returns null to indicate "check all" (no project context).
164
+ */
165
+ function computeNeededOptionalDeps(cwd) {
166
+ const installedPackages = discoverGjsifyPackages(cwd);
167
+ if (installedPackages === null)
168
+ return null; // no project → check everything
169
+ const needed = new Set();
170
+ for (const pkg of installedPackages) {
171
+ const deps = PACKAGE_DEPS[pkg];
172
+ if (deps)
173
+ for (const id of deps)
174
+ needed.add(id);
175
+ }
176
+ return needed;
177
+ }
64
178
  /**
65
179
  * Run all dependency checks. Used by `gjsify check` to show full system status.
180
+ *
181
+ * Required deps (gjs, gtk4, libsoup3, libadwaita, gobject-introspection,
182
+ * blueprint-compiler, pkg-config, meson) are always checked.
183
+ *
184
+ * Optional deps are checked conditionally based on which @gjsify/* packages
185
+ * the project (resolved from cwd) actually consumes. If no project context
186
+ * is available, all optional deps are checked.
66
187
  */
67
188
  export function runAllChecks(cwd) {
68
- return [...runMinimalChecks(), ...runExtraChecks(cwd)];
189
+ const needed = computeNeededOptionalDeps(cwd);
190
+ return [...runMinimalChecks(), ...runRequiredChecks(cwd), ...runOptionalChecks(needed, cwd)];
69
191
  }
70
192
  /**
71
- * Minimal checks needed to run any GJS example (GJS binary only).
193
+ * Minimal checks needed to run any GJS example (Node + GJS binaries only).
72
194
  * Used by `gjsify showcase` for examples that have no native deps.
73
195
  */
74
196
  export function runMinimalChecks() {
75
197
  const results = [];
76
198
  // Node.js — always present
77
- results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version });
199
+ results.push({ id: 'nodejs', name: 'Node.js', found: true, version: process.version, severity: 'required' });
78
200
  // GJS
79
- results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], (out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
201
+ results.push(checkBinary('gjs', 'GJS', 'gjs', ['--version'], 'required', (out) => out.replace(/^GJS\s+/i, '').split('\n')[0] ?? out));
80
202
  return results;
81
203
  }
82
- /** Check gwebgl npm package (project first, CLI fallback). */
204
+ /** Check gwebgl npm package (project first, CLI fallback). Optional — only needed by @gjsify/webgl users. */
83
205
  export function checkGwebgl(cwd) {
84
- return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd);
206
+ return checkNpmPackage('gwebgl', 'gwebgl (@gjsify/webgl)', '@gjsify/webgl', cwd, 'optional', ['@gjsify/webgl']);
85
207
  }
86
208
  /**
87
- * Extra checks for development and full system audit.
209
+ * Required system dependencies always checked, missing → exit 1.
210
+ * Includes the core build toolchain (pkg-config, meson, blueprint-compiler)
211
+ * and the foundational libraries (gtk4, libadwaita, libsoup3,
212
+ * gobject-introspection) that nearly every gjsify app needs.
88
213
  */
89
- function runExtraChecks(cwd) {
214
+ function runRequiredChecks(_cwd) {
90
215
  const results = [];
91
- // Blueprint Compiler
92
- results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler', 'blueprint-compiler', ['--version']));
93
- // pkg-config (needed for library checks)
94
- results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version']));
95
- // Meson (for building native extensions)
96
- results.push(checkBinary('meson', 'Meson', 'meson', ['--version']));
97
- // GTK4
98
- results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4'));
99
- // libadwaita
100
- results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1'));
101
- // libsoup3
102
- results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0'));
103
- // WebKitGTKtry 4.1 first, fall back to 4.0
104
- const webkitCheck = checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkit2gtk-4.1');
105
- if (webkitCheck.found) {
106
- results.push(webkitCheck);
107
- }
108
- else {
109
- results.push(checkPkgConfig('webkitgtk', 'WebKitGTK', 'webkitgtk-6.0'));
110
- }
111
- // GObject Introspection
112
- results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection', 'gobject-introspection-1.0'));
113
- // gwebgl — project first, CLI fallback.
216
+ // Build toolchain
217
+ results.push(checkBinary('blueprint-compiler', 'Blueprint Compiler', 'blueprint-compiler', ['--version'], 'required'));
218
+ results.push(checkBinary('pkg-config', 'pkg-config', 'pkg-config', ['--version'], 'required'));
219
+ results.push(checkBinary('meson', 'Meson', 'meson', ['--version'], 'required'));
220
+ // Foundational libraries
221
+ results.push(checkPkgConfig('gtk4', 'GTK4', 'gtk4', 'required'));
222
+ results.push(checkPkgConfig('libadwaita', 'libadwaita', 'libadwaita-1', 'required'));
223
+ results.push(checkPkgConfig('libsoup3', 'libsoup3', 'libsoup-3.0', 'required'));
224
+ results.push(checkPkgConfig('gobject-introspection', 'GObject Introspection', 'gobject-introspection-1.0', 'required'));
225
+ return results;
226
+ }
227
+ /**
228
+ * Optional system dependencies only checked if the corresponding @gjsify/*
229
+ * package is in use. Missing optional deps generate warnings, not errors.
230
+ *
231
+ * @param needed Set of optional dep ids to check, or null to check all.
232
+ * @param cwd Used to resolve the gwebgl npm package check.
233
+ */
234
+ function runOptionalChecks(needed, cwd) {
235
+ const results = [];
236
+ for (const [id, dep] of Object.entries(OPTIONAL_DEPS)) {
237
+ if (needed !== null && !needed.has(id))
238
+ continue;
239
+ const requiredBy = Object.entries(PACKAGE_DEPS)
240
+ .filter(([, ids]) => ids.includes(id))
241
+ .map(([pkg]) => pkg);
242
+ results.push(checkPkgConfig(dep.id, dep.name, dep.pkgName, 'optional', requiredBy));
243
+ }
244
+ // gwebgl npm package — special case (not a pkg-config lib).
245
+ // Always reported (the npm package is bundled with the CLI), but marked
246
+ // optional because only @gjsify/webgl users need it.
114
247
  results.push(checkGwebgl(cwd));
115
248
  return results;
116
249
  }
@@ -125,8 +258,15 @@ const PM_PACKAGES = {
125
258
  gtk4: 'libgtk-4-dev',
126
259
  libadwaita: 'libadwaita-1-dev',
127
260
  libsoup3: 'libsoup-3.0-dev',
128
- webkitgtk: 'libwebkit2gtk-4.1-dev',
261
+ webkitgtk: 'libwebkit2gtk-6.0-dev',
129
262
  'gobject-introspection': 'gobject-introspection libgirepository1.0-dev',
263
+ manette: 'libmanette-0.2-0 gir1.2-manette-0.2',
264
+ gstreamer: 'libgstreamer1.0-dev',
265
+ 'gst-app': 'libgstreamer-plugins-base1.0-dev gir1.2-gst-plugins-base-1.0',
266
+ 'gdk-pixbuf': 'libgdk-pixbuf-2.0-dev',
267
+ pango: 'libpango1.0-dev',
268
+ pangocairo: 'libpango1.0-dev',
269
+ cairo: 'libcairo2-dev',
130
270
  },
131
271
  dnf: {
132
272
  gjs: 'gjs',
@@ -138,6 +278,13 @@ const PM_PACKAGES = {
138
278
  libsoup3: 'libsoup3-devel',
139
279
  webkitgtk: 'webkitgtk6.0-devel',
140
280
  'gobject-introspection': 'gobject-introspection-devel',
281
+ manette: 'libmanette-devel',
282
+ gstreamer: 'gstreamer1-devel',
283
+ 'gst-app': 'gstreamer1-plugins-base-devel',
284
+ 'gdk-pixbuf': 'gdk-pixbuf2-devel',
285
+ pango: 'pango-devel',
286
+ pangocairo: 'pango-devel',
287
+ cairo: 'cairo-devel',
141
288
  },
142
289
  pacman: {
143
290
  gjs: 'gjs',
@@ -147,8 +294,15 @@ const PM_PACKAGES = {
147
294
  gtk4: 'gtk4',
148
295
  libadwaita: 'libadwaita',
149
296
  libsoup3: 'libsoup3',
150
- webkitgtk: 'webkit2gtk-4.1',
297
+ webkitgtk: 'webkitgtk-6.0',
151
298
  'gobject-introspection': 'gobject-introspection',
299
+ manette: 'libmanette',
300
+ gstreamer: 'gstreamer',
301
+ 'gst-app': 'gst-plugins-base',
302
+ 'gdk-pixbuf': 'gdk-pixbuf2',
303
+ pango: 'pango',
304
+ pangocairo: 'pango',
305
+ cairo: 'cairo',
152
306
  },
153
307
  zypper: {
154
308
  gjs: 'gjs',
@@ -158,8 +312,15 @@ const PM_PACKAGES = {
158
312
  gtk4: 'gtk4-devel',
159
313
  libadwaita: 'libadwaita-devel',
160
314
  libsoup3: 'libsoup-3_0-devel',
161
- webkitgtk: 'webkit2gtk3-devel',
315
+ webkitgtk: 'webkitgtk6_0-devel',
162
316
  'gobject-introspection': 'gobject-introspection-devel',
317
+ manette: 'libmanette-0_2-0-devel',
318
+ gstreamer: 'gstreamer-devel',
319
+ 'gst-app': 'gstreamer-plugins-base-devel',
320
+ 'gdk-pixbuf': 'gdk-pixbuf-devel',
321
+ pango: 'pango-devel',
322
+ pangocairo: 'pango-devel',
323
+ cairo: 'cairo-devel',
163
324
  },
164
325
  apk: {
165
326
  gjs: 'gjs',
@@ -169,8 +330,15 @@ const PM_PACKAGES = {
169
330
  gtk4: 'gtk4.0-dev',
170
331
  libadwaita: 'libadwaita-dev',
171
332
  libsoup3: 'libsoup3-dev',
172
- webkitgtk: 'webkit2gtk-4.1-dev',
333
+ webkitgtk: 'webkit2gtk-6.0-dev',
173
334
  'gobject-introspection': 'gobject-introspection-dev',
335
+ manette: 'libmanette-dev',
336
+ gstreamer: 'gstreamer-dev',
337
+ 'gst-app': 'gst-plugins-base-dev',
338
+ 'gdk-pixbuf': 'gdk-pixbuf-dev',
339
+ pango: 'pango-dev',
340
+ pangocairo: 'pango-dev',
341
+ cairo: 'cairo-dev',
174
342
  },
175
343
  unknown: {},
176
344
  };
@@ -17,6 +17,10 @@ export declare function detectNativePackages(startDir: string): NativePackage[];
17
17
  * Reads the nearest package.json to discover dependencies, then checks each
18
18
  * for gjsify native prebuilds metadata.
19
19
  *
20
+ * Also checks the **nearest package.json itself** — a workspace package may
21
+ * have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
22
+ * never list itself in dependencies.
23
+ *
20
24
  * This complements detectNativePackages() (filesystem walk from CWD) by using
21
25
  * require.resolve() — which handles hoisting, workspaces, and nested node_modules.
22
26
  */
@@ -125,6 +125,10 @@ function findNearestPackageJson(startDir) {
125
125
  * Reads the nearest package.json to discover dependencies, then checks each
126
126
  * for gjsify native prebuilds metadata.
127
127
  *
128
+ * Also checks the **nearest package.json itself** — a workspace package may
129
+ * have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
130
+ * never list itself in dependencies.
131
+ *
128
132
  * This complements detectNativePackages() (filesystem walk from CWD) by using
129
133
  * require.resolve() — which handles hoisting, workspaces, and nested node_modules.
130
134
  */
@@ -140,6 +144,14 @@ export function resolveNativePackages(fromFilePath) {
140
144
  const pkg = readPackageJson(nearestPkgJson);
141
145
  if (!pkg)
142
146
  return results;
147
+ // Check the nearest package itself (e.g. @gjsify/webgl running its own
148
+ // test bundle — webgl never lists itself in dependencies)
149
+ const ownName = typeof pkg['name'] === 'string' ? pkg['name'] : '';
150
+ if (ownName) {
151
+ const ownNative = checkPackage(dirname(nearestPkgJson), ownName, arch);
152
+ if (ownNative)
153
+ results.push(ownNative);
154
+ }
143
155
  const deps = pkg['dependencies'];
144
156
  if (!deps)
145
157
  return results;
@@ -0,0 +1,19 @@
1
+ export interface ShowcaseInfo {
2
+ /** Short name, e.g. "three-postprocessing-pixel" */
3
+ name: string;
4
+ /** Full npm package name, e.g. "@gjsify/example-dom-three-postprocessing-pixel" */
5
+ packageName: string;
6
+ /** Category: "dom" or "node" */
7
+ category: string;
8
+ /** Description from showcase's package.json */
9
+ description: string;
10
+ /** Absolute path to the GJS bundle (resolved from "main" field) */
11
+ bundlePath: string;
12
+ }
13
+ /**
14
+ * Discover all installed showcase packages by scanning the CLI's own dependencies.
15
+ * Returns showcases sorted by category then name.
16
+ */
17
+ export declare function discoverShowcases(): ShowcaseInfo[];
18
+ /** Find a single showcase by short name. */
19
+ export declare function findShowcase(name: string): ShowcaseInfo | undefined;
@@ -1,4 +1,4 @@
1
- // Dynamic discovery of installed @gjsify/example-* packages.
1
+ // Dynamic discovery of installed showcase packages (@gjsify/example-*).
2
2
  // Scans the CLI's own package.json dependencies at runtime.
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { dirname, join } from 'node:path';
@@ -6,9 +6,8 @@ import { createRequire } from 'node:module';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  const EXAMPLE_PREFIX = '@gjsify/example-';
8
8
  /** Extract short name and category from package name. */
9
- function parseExampleName(packageName) {
10
- // @gjsify/example-dom-three-geometry-shapes → category=dom, name=three-geometry-shapes
11
- // @gjsify/example-node-cli-node-path → category=node, name=cli-node-path
9
+ function parseShowcaseName(packageName) {
10
+ // @gjsify/example-dom-three-postprocessing-pixel → category=dom, name=three-postprocessing-pixel
12
11
  const suffix = packageName.slice(EXAMPLE_PREFIX.length);
13
12
  const dashIdx = suffix.indexOf('-');
14
13
  if (dashIdx === -1)
@@ -19,10 +18,10 @@ function parseExampleName(packageName) {
19
18
  };
20
19
  }
21
20
  /**
22
- * Discover all installed example packages by scanning the CLI's own dependencies.
23
- * Returns examples sorted by category then name.
21
+ * Discover all installed showcase packages by scanning the CLI's own dependencies.
22
+ * Returns showcases sorted by category then name.
24
23
  */
25
- export function discoverExamples() {
24
+ export function discoverShowcases() {
26
25
  const require = createRequire(import.meta.url);
27
26
  const cliPkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
28
27
  let cliPkg;
@@ -35,11 +34,11 @@ export function discoverExamples() {
35
34
  const deps = cliPkg['dependencies'];
36
35
  if (!deps)
37
36
  return [];
38
- const examples = [];
37
+ const showcases = [];
39
38
  for (const packageName of Object.keys(deps)) {
40
39
  if (!packageName.startsWith(EXAMPLE_PREFIX))
41
40
  continue;
42
- const parsed = parseExampleName(packageName);
41
+ const parsed = parseShowcaseName(packageName);
43
42
  if (!parsed)
44
43
  continue;
45
44
  try {
@@ -48,7 +47,7 @@ export function discoverExamples() {
48
47
  const main = pkg['main'];
49
48
  if (!main)
50
49
  continue;
51
- examples.push({
50
+ showcases.push({
52
51
  name: parsed.name,
53
52
  packageName,
54
53
  category: parsed.category,
@@ -60,10 +59,10 @@ export function discoverExamples() {
60
59
  // Package listed as dep but not resolvable — skip silently
61
60
  }
62
61
  }
63
- examples.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
64
- return examples;
62
+ showcases.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
63
+ return showcases;
65
64
  }
66
- /** Find a single example by short name. */
67
- export function findExample(name) {
68
- return discoverExamples().find(e => e.name === name);
65
+ /** Find a single showcase by short name. */
66
+ export function findShowcase(name) {
67
+ return discoverShowcases().find(e => e.name === name);
69
68
  }
@@ -29,6 +29,15 @@ export async function runGjsBundle(bundlePath, extraArgs = []) {
29
29
  ...nativeEnv,
30
30
  };
31
31
  const gjsArgs = ['-m', bundlePath, ...extraArgs];
32
+ // Print the exact command being executed so users can copy-paste it to
33
+ // run gjs directly without the wrapper. Env vars are only shown if we
34
+ // actually set any (i.e. native gjsify packages were detected).
35
+ const envPrefix = Object.entries(nativeEnv)
36
+ .filter(([, value]) => value !== undefined && value !== '')
37
+ .map(([key, value]) => `${key}=${value}`)
38
+ .join(' ');
39
+ const gjsCommand = ['gjs', ...gjsArgs.map(a => a.includes(' ') ? `"${a}"` : a)].join(' ');
40
+ console.log(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
32
41
  const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
33
42
  await new Promise((resolvePromise, reject) => {
34
43
  child.on('close', (code) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,55 +23,15 @@
23
23
  "cli"
24
24
  ],
25
25
  "dependencies": {
26
- "@gjsify/esbuild-plugin-gjsify": "^0.1.7",
27
- "@gjsify/example-dom-canvas2d-confetti": "^0.1.7",
28
- "@gjsify/example-dom-canvas2d-fireworks": "^0.1.7",
29
- "@gjsify/example-dom-canvas2d-text": "^0.1.7",
30
- "@gjsify/example-dom-iframe-basic": "^0.1.7",
31
- "@gjsify/example-dom-three-extrude-shapes": "^0.1.7",
32
- "@gjsify/example-dom-three-geometry-cube": "^0.1.7",
33
- "@gjsify/example-dom-three-geometry-shapes": "^0.1.7",
34
- "@gjsify/example-dom-three-geometry-teapot": "^0.1.7",
35
- "@gjsify/example-dom-three-loader-ldraw": "^0.1.7",
36
- "@gjsify/example-dom-three-morphtargets": "^0.1.7",
37
- "@gjsify/example-dom-three-multiple-rendertargets": "^0.1.7",
38
- "@gjsify/example-dom-three-postprocessing-pixel": "^0.1.7",
39
- "@gjsify/example-dom-webgl-demo-fade": "^0.1.7",
40
- "@gjsify/example-dom-webgl-tutorial-01": "^0.1.7",
41
- "@gjsify/example-dom-webgl-tutorial-02": "^0.1.7",
42
- "@gjsify/example-dom-webgl-tutorial-03": "^0.1.7",
43
- "@gjsify/example-dom-webgl-tutorial-04": "^0.1.7",
44
- "@gjsify/example-dom-webgl-tutorial-05": "^0.1.7",
45
- "@gjsify/example-dom-webgl-tutorial-06": "^0.1.7",
46
- "@gjsify/example-dom-webgl-tutorial-07": "^0.1.7",
47
- "@gjsify/example-node-cli-deepkit-di": "^0.1.7",
48
- "@gjsify/example-node-cli-deepkit-events": "^0.1.7",
49
- "@gjsify/example-node-cli-deepkit-types": "^0.1.7",
50
- "@gjsify/example-node-cli-deepkit-validation": "^0.1.7",
51
- "@gjsify/example-node-cli-deepkit-workflow": "^0.1.7",
52
- "@gjsify/example-node-cli-dns-lookup": "^0.1.7",
53
- "@gjsify/example-node-cli-esbuild-plugin-deepkit": "^0.1.7",
54
- "@gjsify/example-node-cli-esbuild-plugin-transform-ext": "^0.1.7",
55
- "@gjsify/example-node-cli-file-search": "^0.1.7",
56
- "@gjsify/example-node-cli-gio-cat": "^0.1.7",
57
- "@gjsify/example-node-cli-json-store": "^0.1.7",
58
- "@gjsify/example-node-cli-node-buffer": "^0.1.7",
59
- "@gjsify/example-node-cli-node-events": "^0.1.7",
60
- "@gjsify/example-node-cli-node-fs": "^0.1.7",
61
- "@gjsify/example-node-cli-node-os": "^0.1.7",
62
- "@gjsify/example-node-cli-node-path": "^0.1.7",
63
- "@gjsify/example-node-cli-node-url": "^0.1.7",
64
- "@gjsify/example-node-cli-worker-pool": "^0.1.7",
65
- "@gjsify/example-node-cli-yargs-demo": "^0.1.7",
66
- "@gjsify/example-node-gtk-http-dashboard": "^0.1.7",
67
- "@gjsify/example-node-net-express-hello": "^0.1.7",
68
- "@gjsify/example-node-net-hono-rest": "^0.1.7",
69
- "@gjsify/example-node-net-koa-blog": "^0.1.7",
70
- "@gjsify/example-node-net-sse-chat": "^0.1.7",
71
- "@gjsify/example-node-net-static-file-server": "^0.1.7",
72
- "@gjsify/example-node-net-ws-chat": "^0.1.7",
73
- "@gjsify/node-polyfills": "^0.1.7",
74
- "@gjsify/web-polyfills": "^0.1.7",
26
+ "@gjsify/create-app": "^0.1.9",
27
+ "@gjsify/esbuild-plugin-gjsify": "^0.1.9",
28
+ "@gjsify/example-dom-canvas2d-fireworks": "^0.1.9",
29
+ "@gjsify/example-dom-excalibur-jelly-jumper": "^0.1.9",
30
+ "@gjsify/example-dom-three-geometry-teapot": "^0.1.9",
31
+ "@gjsify/example-dom-three-postprocessing-pixel": "^0.1.9",
32
+ "@gjsify/example-node-express-webserver": "^0.1.9",
33
+ "@gjsify/node-polyfills": "^0.1.9",
34
+ "@gjsify/web-polyfills": "^0.1.9",
75
35
  "cosmiconfig": "^9.0.1",
76
36
  "esbuild": "^0.28.0",
77
37
  "get-tsconfig": "^4.13.7",