@gjsify/cli 0.23.0 → 0.24.2

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.
@@ -0,0 +1,103 @@
1
+ // Build-time guard: a `--app gjs` bundle must not keep bare `node:` imports.
2
+ //
3
+ // Stock GJS has NO `node:` module loader. A single surviving
4
+ // `import … from "node:fs"` makes it abort the ENTIRE module graph before one
5
+ // line executes:
6
+ //
7
+ // Gjs-WARNING **: JS ERROR: ImportError: Unsupported URI scheme for importing: node
8
+ // Gjs-CRITICAL **: Failed to resolve imports for module: '…/dist/cli.gjs.mjs'
9
+ //
10
+ // `--app gjs` maps every Node builtin to its `@gjsify/*` polyfill, so a healthy
11
+ // bundle has ZERO of them. But when an alias TARGET cannot be resolved — the
12
+ // classic case being a workspace checkout whose `@gjsify/*` packages have no
13
+ // built `lib/`, so `exports["."]` points at a file that does not exist — the
14
+ // bundler falls back to treating the import as external and re-emits the
15
+ // ORIGINAL `node:*` specifier. That is a WARNING, not an error: the build
16
+ // "succeeds" and writes a bundle that cannot load anywhere. It cost three red
17
+ // CI jobs to notice, because nothing between `gjsify build` and CI ever
18
+ // executes the emitted GJS bundle (tsc, lint, format and the unit/e2e suites
19
+ // all run the Node entry).
20
+ //
21
+ // The pre-commit hook makes it worse rather than better: it rebuilds and
22
+ // AUTO-STAGES `dist/cli.gjs.mjs`, so a developer whose checkout cannot resolve
23
+ // the polyfills commits a broken artifact without ever seeing it.
24
+ //
25
+ // So the refusal belongs at build time, in `gjsify build --app gjs` itself —
26
+ // not in a git hook and not in one package's `build:gjs-bundle` script. The
27
+ // invariant is a property of the ARTIFACT, and every showcase, every downstream
28
+ // app (buchhaltung, bauplaner, Learn6502) and CI all produce it through this
29
+ // one code path. A hook would protect only commits in this repo; a per-package
30
+ // script would protect one package.
31
+ //
32
+ // ## The oracle is the module graph, NOT the emitted text
33
+ //
34
+ // The first cut of this guard grepped the output for `from "node:…"`. That is
35
+ // wrong, and CI caught it: `@gjsify/process`'s own test bundle names a case
36
+ //
37
+ // await it(`named-import hrtime (from "node:process") preserves .bigint`, …)
38
+ //
39
+ // so the phrase appears verbatim inside a STRING, in a bundle that loads and
40
+ // runs under GJS perfectly well. The same trap sits in `@gjsify/cli`'s own
41
+ // bundle, which embeds `giNodeShimSource()`'s `--app node` codegen template
42
+ // (`import { createRequire } from 'node:module';`). Telling a statement from a
43
+ // quoted lookalike needs a JS lexer — but the bundler already did that work:
44
+ // every chunk carries the list of external specifiers it actually imports. That
45
+ // list is exact, engine-independent (npm `rolldown` returns it directly,
46
+ // `@gjsify/rolldown-native` forwards it through `synthRolldownOutput`), and
47
+ // free.
48
+ //
49
+ // Scope is STATIC imports, which is precisely the failure mode above: they are
50
+ // resolved when GJS links the module, so one bad specifier kills the whole
51
+ // bundle. A dynamic `import('node:x')` throws lazily at its call site instead —
52
+ // a real bug, but not "the artifact cannot load", and not what this gate is
53
+ // for.
54
+ /**
55
+ * Bare `node:` specifiers a `--app gjs` bundle may keep as static imports.
56
+ *
57
+ * Deliberately EMPTY. Every Node builtin has an `@gjsify/*` polyfill that
58
+ * `--app gjs` aliases to, so there is no specifier that both survives into the
59
+ * module graph and loads under GJS. Reading the graph instead of the text is
60
+ * what keeps this list empty — the entries an earlier text-scanning version
61
+ * needed (`node:module`, `node:process`) were quoted lookalikes, never imports.
62
+ *
63
+ * Add an entry ONLY with a comment naming why GJS can resolve it. Each one is a
64
+ * specifier this guard can no longer catch for real.
65
+ */
66
+ export const GJS_ALLOWED_NODE_IMPORTS = [];
67
+ /**
68
+ * The distinct bare `node:` specifiers `chunks` statically import, minus the
69
+ * allowlist. Sorted, so the error message and any test are stable.
70
+ */
71
+ export function findDisallowedNodeImports(chunks, allowed = GJS_ALLOWED_NODE_IMPORTS) {
72
+ const found = new Set();
73
+ for (const chunk of chunks) {
74
+ for (const specifier of chunk.imports ?? []) {
75
+ if (!specifier.startsWith('node:'))
76
+ continue;
77
+ if (allowed.includes(specifier))
78
+ continue;
79
+ found.add(specifier);
80
+ }
81
+ }
82
+ return [...found].sort();
83
+ }
84
+ /**
85
+ * Throw when a built `--app gjs` bundle statically imports Node builtins.
86
+ *
87
+ * @param chunks the emitted chunks, as reported by the bundler
88
+ * @param outfile path of the bundle, for the error message
89
+ */
90
+ export function assertGjsBundleLoadable(chunks, outfile) {
91
+ const offenders = findDisallowedNodeImports(chunks);
92
+ if (offenders.length === 0)
93
+ return;
94
+ throw new Error(`gjsify build --app gjs: ${outfile} still imports ${offenders.length} bare \`node:\` module(s), ` +
95
+ 'which stock GJS cannot resolve — it would fail at load with ' +
96
+ '"ImportError: Unsupported URI scheme for importing: node".\n' +
97
+ offenders.map((s) => ` - ${s}`).join('\n') +
98
+ '\n\nThese should have been aliased to their `@gjsify/*` polyfills. The usual cause is that the ' +
99
+ 'alias TARGET could not be resolved, so the bundler externalised the import instead (look for ' +
100
+ 'UNRESOLVED_IMPORT warnings above): e.g. a workspace checkout whose `@gjsify/*` packages have no ' +
101
+ 'built `lib/`. Build the workspace first (`gjsify run build:infra`), or fix the resolution, then ' +
102
+ 'rebuild.');
103
+ }
@@ -571,7 +571,6 @@ function findVisible(requesterPath, name, byPath) {
571
571
  candidates.push(`${requesterPath}/node_modules/${name}`);
572
572
  // Walk up: strip the last `/node_modules/<pkg>` segment and try again.
573
573
  let p = requesterPath;
574
- // eslint-disable-next-line no-constant-condition
575
574
  while (true) {
576
575
  // Find the deepest `/node_modules/<pkg>` in `p`, strip it.
577
576
  const idx = p.lastIndexOf('/node_modules/');
@@ -86,22 +86,6 @@ export interface LinkedBin {
86
86
  * simplicity and so we don't need to discriminate by runtime here.
87
87
  */
88
88
  export declare function linkGlobalBins(packageNames: string[], layout: GlobalLayout): LinkedBin[];
89
- /**
90
- * Build the POSIX-sh `export` lines that prepend the given prebuild
91
- * directories to GI_TYPELIB_PATH and the host's shared-library search path.
92
- * Any pre-existing value inherited from the user's environment is preserved as
93
- * a suffix so user-installed typelibs/libraries still resolve.
94
- *
95
- * The library variable is host-dependent — `LD_LIBRARY_PATH` on Linux,
96
- * `DYLD_LIBRARY_PATH` on macOS (dyld never reads the ELF one), so a launcher
97
- * written on a Mac used to export a variable nothing consults, leaving every
98
- * `darwin-arm64` prebuild unloadable. `platform` is a parameter so both
99
- * branches are unit-testable from either host.
100
- *
101
- * Returns the empty string when no prebuilds were found — avoids emitting an
102
- * inert assignment in the launcher.
103
- */
104
- export declare function buildLauncherEnvPreamble(prebuildsDirs: string[], platform?: string): string;
105
89
  /** Returns `true` if `binDir` is on the user's PATH. */
106
90
  export declare function binDirOnPath(binDir: string): boolean;
107
91
  /**
@@ -23,8 +23,8 @@
23
23
  import * as fs from 'node:fs';
24
24
  import * as os from 'node:os';
25
25
  import * as path from 'node:path';
26
- import { detectNativePackages, libraryPathVar } from './detect-native-packages.js';
27
- import { buildLauncherShims, parseShebang } from './bin-shim.js';
26
+ import { detectNativePackages } from './detect-native-packages.js';
27
+ import { buildLauncherShims, buildNativeEnvPreamble, parseShebang } from './bin-shim.js';
28
28
  /**
29
29
  * Compute the canonical global install layout for the current user. Honours
30
30
  * `XDG_DATA_HOME` (per the XDG Base Directory Spec) plus
@@ -143,18 +143,25 @@ export async function installGjsEnginePackages(prefix, version, opts = {}) {
143
143
  export function linkGlobalBins(packageNames, layout) {
144
144
  fs.mkdirSync(layout.binDir, { recursive: true });
145
145
  const created = [];
146
- // Discover @gjsify/* packages with native prebuilds (Vala/GObject typelibs
147
- // + shared libs) under the global prefix. The launcher bakes their
148
- // directories into GI_TYPELIB_PATH / LD_LIBRARY_PATH so `imports.gi.X`
149
- // resolves at CLI startup — required for e.g. @gjsify/terminal-native,
150
- // without which process.stdout.isTTY / columns / colors all fall back to
151
- // the conservative env-only defaults (no colors, 80-col wrap).
146
+ // The launcher exports GI_TYPELIB_PATH / <loader path> for the @gjsify/*
147
+ // native prebuilds (Vala/GObject typelibs + shared libs) installed in this
148
+ // prefix, so `imports.gi.X` resolves at CLI startup — required for the
149
+ // bundler engine `@gjsify/rolldown-native` (without which `gjsify build`
150
+ // hard-fails under GJS) and for e.g. `@gjsify/terminal-native`, without
151
+ // which isTTY / columns / colors fall back to conservative defaults.
152
152
  //
153
- // `runGjsBundle()` does the same dance for `gjsify run <bundle>` at
154
- // runtime; here we do it at install time because the global launcher
155
- // invokes the CLI bundle directly, with no opportunity to set env first.
153
+ // `runGjsBundle()` does the same dance for `gjsify run <bundle>` at runtime;
154
+ // here it has to be the launcher, because the typelib lookup happens inside
155
+ // the GJS runtime before the bundle's first line runs.
156
+ //
157
+ // The preamble SCANS the prefix at launch time rather than embedding what
158
+ // was there at install time — see `buildNativeEnvPreamble` for why a
159
+ // snapshot is the wrong shape. `nativePrebuildDirs` is still computed: the
160
+ // Windows `.cmd`/`.ps1` shims take the baked list, and a hit in an
161
+ // ANCESTOR's node_modules (which the walk finds and a single-root scan
162
+ // cannot) is embedded verbatim.
156
163
  const nativePrebuildDirs = detectNativePackages(layout.prefix).map((p) => p.prebuildsDir);
157
- const envPreamble = buildLauncherEnvPreamble(nativePrebuildDirs);
164
+ const envPreamble = buildNativeEnvPreamble(layout.prefix, nativePrebuildDirs);
158
165
  for (const pkgName of packageNames) {
159
166
  const pkgDir = path.join(layout.prefix, 'node_modules', pkgName);
160
167
  const pkgJsonPath = path.join(pkgDir, 'package.json');
@@ -237,30 +244,6 @@ export function linkGlobalBins(packageNames, layout) {
237
244
  function shQuote(s) {
238
245
  return `'${s.replace(/'/g, `'\\''`)}'`;
239
246
  }
240
- /**
241
- * Build the POSIX-sh `export` lines that prepend the given prebuild
242
- * directories to GI_TYPELIB_PATH and the host's shared-library search path.
243
- * Any pre-existing value inherited from the user's environment is preserved as
244
- * a suffix so user-installed typelibs/libraries still resolve.
245
- *
246
- * The library variable is host-dependent — `LD_LIBRARY_PATH` on Linux,
247
- * `DYLD_LIBRARY_PATH` on macOS (dyld never reads the ELF one), so a launcher
248
- * written on a Mac used to export a variable nothing consults, leaving every
249
- * `darwin-arm64` prebuild unloadable. `platform` is a parameter so both
250
- * branches are unit-testable from either host.
251
- *
252
- * Returns the empty string when no prebuilds were found — avoids emitting an
253
- * inert assignment in the launcher.
254
- */
255
- export function buildLauncherEnvPreamble(prebuildsDirs, platform = process.platform) {
256
- if (prebuildsDirs.length === 0)
257
- return '';
258
- const { name: libVar } = libraryPathVar(platform);
259
- const joined = shQuote(prebuildsDirs.join(':'));
260
- return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}\n` +
261
- `${libVar}=${joined}\${${libVar}:+":$${libVar}"}\n` +
262
- `export GI_TYPELIB_PATH ${libVar}\n`);
263
- }
264
247
  /** Parse a bin file's `#!` line; `null` when unreadable or not a shebang. */
265
248
  function readShebangOf(file) {
266
249
  try {
@@ -24,7 +24,6 @@ export function normalizeBundlerOptions(configData) {
24
24
  raw['define'] !== null) {
25
25
  if (!warnedDefineOnce) {
26
26
  warnedDefineOnce = true;
27
- // eslint-disable-next-line no-console
28
27
  console.warn("[gjsify] WARNING: 'bundler.define' is not a valid Rolldown option and would be " +
29
28
  "silently ignored — it has been auto-mapped to 'bundler.transform.define'. " +
30
29
  "Move 'define' under 'bundler.transform.define' in your config to suppress " +
@@ -46,7 +45,6 @@ export function normalizeBundlerOptions(configData) {
46
45
  return fromBundler;
47
46
  if (!warnedOnce) {
48
47
  warnedOnce = true;
49
- // eslint-disable-next-line no-console
50
48
  console.warn("[gjsify] DEPRECATION: the 'esbuild' config key is deprecated and will be removed in 0.5.0. " +
51
49
  "Rename it to 'bundler' (typed as RolldownOptions). See the migration notes in the gjsify CHANGELOG.");
52
50
  }
@@ -46,7 +46,7 @@
46
46
  // lives in the Node launcher, so a native oxlint bridge could only run the
47
47
  // Rust rule subset. Lint stays Node-spawned until that trade-off is decided
48
48
  // (tracked in STATUS.md Open TODOs).
49
- import { existsSync, readFileSync } from 'node:fs';
49
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
50
50
  import { join, resolve } from 'node:path';
51
51
  import { createRequire } from 'node:module';
52
52
  import { pathToFileURL } from 'node:url';
@@ -89,7 +89,12 @@ function oxcBindingSuffix() {
89
89
  // glibc systems have `/lib/ld-linux-*`, musl has `/lib/ld-musl-*`.
90
90
  let libc = 'gnu';
91
91
  try {
92
- const { readdirSync } = require('node:fs');
92
+ // `readdirSync` is imported STATICALLY at the top of this file. It
93
+ // used to be a lazy `require('node:fs')` inside this try — a bare
94
+ // `require` in an ESM module, so it threw ReferenceError, the catch
95
+ // below swallowed it, and `libc` silently stayed 'gnu': a musl host
96
+ // would be told to install the WRONG binding package. Same defect
97
+ // class as commands/affected.ts, and even quieter.
93
98
  const libEntries = readdirSync('/lib');
94
99
  if (libEntries.some((e) => e.startsWith('ld-musl-'))) {
95
100
  libc = 'musl';
@@ -6,6 +6,22 @@ import { type ExampleRuntime } from './runtimes.js';
6
6
  * not available under the GJS-bundled CLI.
7
7
  */
8
8
  export declare function resolveNodeGi(startDir: string): string | null;
9
+ /**
10
+ * Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
11
+ * BUNDLE's own directory first, then from `cwd`.
12
+ *
13
+ * The bundle's directory is the correct base and cwd is the fallback, for the
14
+ * same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
15
+ * kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
16
+ * bare specifier from inside the bundle — which Node performs relative to the
17
+ * importing file, never relative to cwd. A dlx-cache layout
18
+ * (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
19
+ * sitting right beside the bundle and nothing at all under the user's cwd, so a
20
+ * cwd-only probe rejected a run that would have worked. The cwd probe is kept
21
+ * because a project-local `node_modules` legitimately satisfies a bundle built
22
+ * into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
23
+ */
24
+ export declare function resolveNodeGiForBundle(bundlePath: string, cwd: string): string | null;
9
25
  /** Pick the node executable: the current one when on Node, else PATH `node`. */
10
26
  export declare function nodeBinary(): string;
11
27
  export interface RunNodeBundleOptions {
@@ -5,10 +5,12 @@
5
5
  // through `@gjsify/node-gi` (the Axis-5 reverse bridge). Two things have to be
6
6
  // true at runtime:
7
7
  //
8
- // 1. `@gjsify/node-gi` must resolve from the project — it is kept EXTERNAL by
9
- // the bundler (a native addon), so it has to physically exist in the
10
- // consumer's node_modules. We verify this up front and emit a clear hint
11
- // instead of letting Node throw `ERR_MODULE_NOT_FOUND` deep in the bundle.
8
+ // 1. `@gjsify/node-gi` must resolve FROM THE BUNDLE — it is kept EXTERNAL by
9
+ // the bundler (a native addon), so it has to physically exist in a
10
+ // node_modules the bundle's own directory can reach (that is where Node
11
+ // will look), with the CWD as a fallback. We verify this up front and emit
12
+ // a clear hint instead of letting Node throw `ERR_MODULE_NOT_FOUND` deep in
13
+ // the bundle. See `resolveNodeGiForBundle`.
12
14
  // 2. `gi://Gtk` / `gi://Adw` etc. must resolve. System typelibs come from the
13
15
  // default GI search path, but any `@gjsify/*` Vala bridge a story pulls in
14
16
  // ships its typelib in `prebuilds/` — so we reuse the SAME
@@ -21,7 +23,7 @@
21
23
  // `gjs`, which cannot run a `--app node` bundle).
22
24
  import { spawn } from 'node:child_process';
23
25
  import { existsSync, readFileSync } from 'node:fs';
24
- import { join, resolve } from 'node:path';
26
+ import { dirname, join, resolve } from 'node:path';
25
27
  import { isNode } from '@gjsify/rolldown-plugin-gjsify/runtime';
26
28
  import { computeNativeEnvForBundle } from './run-gjs.js';
27
29
  import { RUNTIMES } from './runtimes.js';
@@ -43,6 +45,25 @@ export function resolveNodeGi(startDir) {
43
45
  dir = parent;
44
46
  }
45
47
  }
48
+ /**
49
+ * Resolve `@gjsify/node-gi` for a bundle we are about to launch: from the
50
+ * BUNDLE's own directory first, then from `cwd`.
51
+ *
52
+ * The bundle's directory is the correct base and cwd is the fallback, for the
53
+ * same reason {@link computeNativeEnvForBundle} walks both: `@gjsify/node-gi` is
54
+ * kept EXTERNAL, so what has to be satisfiable is the RUNTIME's resolution of a
55
+ * bare specifier from inside the bundle — which Node performs relative to the
56
+ * importing file, never relative to cwd. A dlx-cache layout
57
+ * (`~/.cache/gjsify/dlx/<sha>/node_modules/<pkg>/dist/bundle.mjs`) has node-gi
58
+ * sitting right beside the bundle and nothing at all under the user's cwd, so a
59
+ * cwd-only probe rejected a run that would have worked. The cwd probe is kept
60
+ * because a project-local `node_modules` legitimately satisfies a bundle built
61
+ * into a `dist/` outside it (a `--outfile /tmp/…` build from a project root).
62
+ */
63
+ export function resolveNodeGiForBundle(bundlePath, cwd) {
64
+ const resolvedBundle = resolve(bundlePath);
65
+ return resolveNodeGi(dirname(resolvedBundle)) ?? resolveNodeGi(cwd);
66
+ }
46
67
  /** Pick the node executable: the current one when on Node, else PATH `node`. */
47
68
  export function nodeBinary() {
48
69
  return isNode() && process.execPath ? process.execPath : 'node';
@@ -55,7 +76,7 @@ export function nodeBinary() {
55
76
  */
56
77
  export async function runNodeBundle(bundlePath, extraArgs = [], options = {}) {
57
78
  const cwd = process.cwd();
58
- if (!resolveNodeGi(cwd)) {
79
+ if (!resolveNodeGiForBundle(bundlePath, cwd)) {
59
80
  throw new Error('Cannot run the storybook on Node: `@gjsify/node-gi` is not installed.\n' +
60
81
  'Add @gjsify/node-gi as a dependency to run the storybook on Node, e.g.:\n' +
61
82
  ' "devDependencies": { "@gjsify/node-gi": "^0.13.0" }');
@@ -113,7 +134,7 @@ export function bundleRequiresNodeGi(bundlePath) {
113
134
  export async function runRuntimeBundle(runtime, bundlePath, extraArgs = [], options = {}) {
114
135
  const cwd = process.cwd();
115
136
  const resolvedBundle = resolve(bundlePath);
116
- if (bundleRequiresNodeGi(resolvedBundle) && !resolveNodeGi(cwd)) {
137
+ if (bundleRequiresNodeGi(resolvedBundle) && !resolveNodeGiForBundle(resolvedBundle, cwd)) {
117
138
  throw new Error(`Cannot run this bundle on ${runtime}: it uses gi:// via \`@gjsify/node-gi\`, which is not installed.\n` +
118
139
  'Add @gjsify/node-gi as a dependency to run gi:// code off GJS, e.g.:\n' +
119
140
  ' "dependencies": { "@gjsify/node-gi": "^0.13.0" }');
@@ -37,6 +37,27 @@ export declare function isRuntimeOnPath(probe: string): boolean;
37
37
  export declare function isRuntimeAvailable(runtime: ExampleRuntime): boolean;
38
38
  /** Which of gjs/node/bun/deno are runnable on this host. */
39
39
  export declare function availableRuntimes(): ExampleRuntime[];
40
+ /**
41
+ * The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
42
+ * whenever gjs is runnable here, else the host runtime.
43
+ *
44
+ * This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
45
+ * artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
46
+ * showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
47
+ * docs call the default. Following the host instead means `npx @gjsify/cli
48
+ * showcase <name>` (host = node) silently asks for the `--app node` bundle,
49
+ * which most showcases do not ship and which additionally needs
50
+ * `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
51
+ * the primary documented entry point failed on a missing file.
52
+ *
53
+ * Same rule `gjsify run` already applies in its bare-file path, where a
54
+ * `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
55
+ * here the "is it a gjs artifact" question is answered by the command instead
56
+ * of by sniffing the file. `--runtime` still overrides explicitly, and a host
57
+ * WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
58
+ * node-capable showcases stay runnable there.
59
+ */
60
+ export declare function defaultExampleRuntime(): ExampleRuntime;
40
61
  interface ExampleGjsifyField {
41
62
  example?: {
42
63
  runtimes?: unknown;
@@ -64,6 +64,29 @@ export function isRuntimeAvailable(runtime) {
64
64
  export function availableRuntimes() {
65
65
  return EXAMPLE_RUNTIMES.filter((rt) => isRuntimeAvailable(rt));
66
66
  }
67
+ /**
68
+ * The default `--runtime` for running a prebuilt EXAMPLE/SHOWCASE: `gjs`
69
+ * whenever gjs is runnable here, else the host runtime.
70
+ *
71
+ * This is deliberately NOT a plain `hostRuntime()`. A showcase's canonical
72
+ * artifact is its `--app gjs` bundle (`gjsify.main`) — that is what every
73
+ * showcase ships, what the `gjs` path installs via `gjsify dlx`, and what the
74
+ * docs call the default. Following the host instead means `npx @gjsify/cli
75
+ * showcase <name>` (host = node) silently asks for the `--app node` bundle,
76
+ * which most showcases do not ship and which additionally needs
77
+ * `@gjsify/node-gi` in the consumer's project — so the FIRST-RUN experience of
78
+ * the primary documented entry point failed on a missing file.
79
+ *
80
+ * Same rule `gjsify run` already applies in its bare-file path, where a
81
+ * `--app gjs` bundle stays on gjs regardless of host (`isLikelyGjsBundle`);
82
+ * here the "is it a gjs artifact" question is answered by the command instead
83
+ * of by sniffing the file. `--runtime` still overrides explicitly, and a host
84
+ * WITHOUT gjs (a plain Node/bun/deno box) keeps following the host so the
85
+ * node-capable showcases stay runnable there.
86
+ */
87
+ export function defaultExampleRuntime() {
88
+ return isRuntimeAvailable('gjs') ? 'gjs' : hostRuntime();
89
+ }
67
90
  /**
68
91
  * Read a package's declared example runtimes. Returns the (validated) list, or
69
92
  * `null` when the package declares none — the "unconstrained / permissive" case.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.23.0",
3
+ "version": "0.24.2",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -130,19 +130,19 @@
130
130
  "cli"
131
131
  ],
132
132
  "dependencies": {
133
- "@gjsify/buffer": "^0.23.0",
134
- "@gjsify/create-app": "^0.23.0",
135
- "@gjsify/node-globals": "^0.23.0",
136
- "@gjsify/node-polyfills": "^0.23.0",
137
- "@gjsify/npm-registry": "^0.23.0",
138
- "@gjsify/resolve-npm": "^0.23.0",
139
- "@gjsify/rolldown-plugin-gjsify": "^0.23.0",
140
- "@gjsify/rolldown-plugin-pnp": "^0.23.0",
141
- "@gjsify/semver": "^0.23.0",
142
- "@gjsify/tar": "^0.23.0",
143
- "@gjsify/tsc": "^0.23.0",
144
- "@gjsify/web-polyfills": "^0.23.0",
145
- "@gjsify/workspace": "^0.23.0",
133
+ "@gjsify/buffer": "^0.24.2",
134
+ "@gjsify/create-app": "^0.24.2",
135
+ "@gjsify/node-globals": "^0.24.2",
136
+ "@gjsify/node-polyfills": "^0.24.2",
137
+ "@gjsify/npm-registry": "^0.24.2",
138
+ "@gjsify/resolve-npm": "^0.24.2",
139
+ "@gjsify/rolldown-plugin-gjsify": "^0.24.2",
140
+ "@gjsify/rolldown-plugin-pnp": "^0.24.2",
141
+ "@gjsify/semver": "^0.24.2",
142
+ "@gjsify/tar": "^0.24.2",
143
+ "@gjsify/tsc": "^0.24.2",
144
+ "@gjsify/web-polyfills": "^0.24.2",
145
+ "@gjsify/workspace": "^0.24.2",
146
146
  "cosmiconfig": "^9.0.2",
147
147
  "get-tsconfig": "^4.14.0",
148
148
  "pkg-types": "^2.3.1",
@@ -150,13 +150,13 @@
150
150
  "yargs": "^18.0.0"
151
151
  },
152
152
  "devDependencies": {
153
- "@gjsify/unit": "^0.23.0",
153
+ "@gjsify/unit": "^0.24.2",
154
154
  "@types/yargs": "^17.0.35",
155
155
  "typescript": "^6.0.3"
156
156
  },
157
157
  "peerDependencies": {
158
- "@gjsify/oxfmt-native": "^0.23.0",
159
- "@gjsify/rolldown-native": "^0.23.0"
158
+ "@gjsify/oxfmt-native": "^0.24.2",
159
+ "@gjsify/rolldown-native": "^0.24.2"
160
160
  },
161
161
  "peerDependenciesMeta": {
162
162
  "@gjsify/oxfmt-native": {