@gjsify/cli 0.3.16 → 0.3.17

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.
@@ -6,11 +6,20 @@ export const buildCommand = {
6
6
  builder: (yargs) => {
7
7
  return yargs
8
8
  .option('entry-points', {
9
- description: "The entry points you want to bundle",
9
+ description: "The entry points you want to bundle. Defaults to bundler.input from package.json#gjsify or .gjsifyrc.js, falling back to src/index.ts when neither is set.",
10
10
  array: true,
11
11
  type: 'string',
12
12
  normalize: true,
13
- default: ['src/index.ts'],
13
+ // No yargs `default` here on purpose. A yargs default value
14
+ // is indistinguishable from "user passed the flag" in the
15
+ // parsed args (cliArgs.entryPoints?.length is truthy either
16
+ // way), so the merge step in config.ts would unconditionally
17
+ // overwrite `bundler.input` declared in package.json#gjsify —
18
+ // silently ignoring `gjsify.bundler.input: "src/start.ts"`
19
+ // and producing a bundle from the wrong entry point. The
20
+ // fallback to src/index.ts is applied in config.ts AFTER
21
+ // merging with the cosmiconfig data.
22
+ defaultDescription: "src/index.ts (fallback)",
14
23
  coerce: (arg) => {
15
24
  // Removes duplicates
16
25
  return [...new Set(arg)];
@@ -43,10 +52,10 @@ export const buildCommand = {
43
52
  normalize: true,
44
53
  })
45
54
  .option('minify', {
46
- description: "When enabled, the generated code will be minified instead of pretty-printed",
55
+ description: "Minify the bundled output. Defaults to true; use --no-minify to emit pretty-printed code (e.g. for debugging or readable bundle review).",
47
56
  type: 'boolean',
48
57
  normalize: true,
49
- default: false
58
+ defaultDescription: 'true',
50
59
  })
51
60
  .option('library', {
52
61
  description: "Use this if you want to build a library for Gjsify",
package/lib/config.js CHANGED
@@ -214,14 +214,26 @@ export class Config {
214
214
  const transform = (bundler.transform ??= {});
215
215
  if (cliArgs.entryPoints?.length)
216
216
  bundler.input = cliArgs.entryPoints;
217
+ // Fallback when neither the CLI flag nor the cosmiconfig data set an
218
+ // entry point. Applied here (post-merge) rather than as a yargs
219
+ // `default:` because yargs defaults are indistinguishable from
220
+ // user-set values, and would silently overwrite `bundler.input`
221
+ // declared in package.json#gjsify.
222
+ if (!bundler.input)
223
+ bundler.input = ['src/index.ts'];
217
224
  if (cliArgs.outfile !== undefined)
218
225
  output.file = cliArgs.outfile;
219
226
  if (cliArgs.outdir !== undefined)
220
227
  output.dir = cliArgs.outdir;
221
228
  if (cliArgs.format !== undefined)
222
229
  output.format = cliArgs.format;
230
+ // CLI flag wins over config; if neither is set, minify by default.
231
+ // Pretty-printed output is opt-in via `--no-minify` or
232
+ // `bundler.output.minify: false` in the config.
223
233
  if (cliArgs.minify !== undefined)
224
234
  output.minify = cliArgs.minify;
235
+ if (output.minify === undefined)
236
+ output.minify = true;
225
237
  if (cliArgs.logLevel) {
226
238
  // Map esbuild log levels to Rolldown's narrower set:
227
239
  // esbuild → rolldown
@@ -18,19 +18,6 @@ export interface NativePackage {
18
18
  * node_modules shadows outer ones), matching Node.js resolution semantics.
19
19
  */
20
20
  export declare function detectNativePackages(startDir: string): NativePackage[];
21
- /**
22
- * Resolve native packages using Node.js module resolution from a given file path.
23
- * Reads the nearest package.json to discover dependencies, then checks each
24
- * for gjsify native prebuilds metadata.
25
- *
26
- * Also checks the **nearest package.json itself** — a workspace package may
27
- * have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
28
- * never list itself in dependencies.
29
- *
30
- * This complements detectNativePackages() (filesystem walk from CWD) by using
31
- * require.resolve() — which handles hoisting, workspaces, and nested node_modules.
32
- */
33
- export declare function resolveNativePackages(fromFilePath: string): NativePackage[];
34
21
  /**
35
22
  * Build the LD_LIBRARY_PATH and GI_TYPELIB_PATH env var values for the detected native packages.
36
23
  * Prepends the new paths to any existing values from the environment.
@@ -1,10 +1,17 @@
1
1
  // Utility to find npm packages with gjsify native prebuilds.
2
2
  // Packages declare: "gjsify": { "prebuilds": "<dir>" } in their package.json.
3
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.
4
13
  import { readdirSync, existsSync, readFileSync } from 'node:fs';
5
- import { createRequire } from 'node:module';
6
- import { dirname, join, resolve } from 'node:path';
7
- import { pathToFileURL } from 'node:url';
14
+ import { join, resolve } from 'node:path';
8
15
  /** Map Node.js process.arch values to the convention used in prebuilds/ directories. */
9
16
  function nodeArchToLinuxArch(arch) {
10
17
  const map = {
@@ -119,77 +126,6 @@ export function detectNativePackages(startDir) {
119
126
  }
120
127
  return merged;
121
128
  }
122
- /** Walk up from dir to find the nearest package.json. */
123
- function findNearestPackageJson(startDir) {
124
- let dir = resolve(startDir);
125
- while (true) {
126
- const candidate = join(dir, 'package.json');
127
- if (existsSync(candidate))
128
- return candidate;
129
- const parent = resolve(dir, '..');
130
- if (parent === dir)
131
- return null;
132
- dir = parent;
133
- }
134
- }
135
- /**
136
- * Resolve native packages using Node.js module resolution from a given file path.
137
- * Reads the nearest package.json to discover dependencies, then checks each
138
- * for gjsify native prebuilds metadata.
139
- *
140
- * Also checks the **nearest package.json itself** — a workspace package may
141
- * have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
142
- * never list itself in dependencies.
143
- *
144
- * This complements detectNativePackages() (filesystem walk from CWD) by using
145
- * require.resolve() — which handles hoisting, workspaces, and nested node_modules.
146
- */
147
- export function resolveNativePackages(fromFilePath) {
148
- const arch = nodeArchToLinuxArch(process.arch);
149
- const results = [];
150
- try {
151
- const req = createRequire(pathToFileURL(fromFilePath).href);
152
- // Find the nearest package.json to get the dependency list
153
- const nearestPkgJson = findNearestPackageJson(dirname(resolve(fromFilePath)));
154
- if (!nearestPkgJson)
155
- return results;
156
- const pkg = readPackageJson(nearestPkgJson);
157
- if (!pkg)
158
- return results;
159
- // Check the nearest package itself (e.g. @gjsify/webgl running its own
160
- // test bundle — webgl never lists itself in dependencies)
161
- const ownName = typeof pkg['name'] === 'string' ? pkg['name'] : '';
162
- if (ownName) {
163
- const ownNative = checkPackage(dirname(nearestPkgJson), ownName, arch);
164
- if (ownNative)
165
- results.push(ownNative);
166
- }
167
- const deps = pkg['dependencies'];
168
- if (!deps)
169
- return results;
170
- for (const depName of Object.keys(deps)) {
171
- try {
172
- // Resolve the package's main entry, then walk up to find its package.json.
173
- // We cannot use require.resolve(name + '/package.json') because packages
174
- // with an "exports" field may not expose ./package.json as a subpath.
175
- const entryPath = req.resolve(depName);
176
- const depPkgJson = findNearestPackageJson(dirname(entryPath));
177
- if (!depPkgJson)
178
- continue;
179
- const native = checkPackage(dirname(depPkgJson), depName, arch);
180
- if (native)
181
- results.push(native);
182
- }
183
- catch {
184
- // Dependency not resolvable — skip
185
- }
186
- }
187
- }
188
- catch {
189
- // Resolution failed — return empty
190
- }
191
- return results;
192
- }
193
129
  /**
194
130
  * Build the LD_LIBRARY_PATH and GI_TYPELIB_PATH env var values for the detected native packages.
195
131
  * Prepends the new paths to any existing values from the environment.
@@ -1,9 +1,19 @@
1
+ /**
2
+ * Pure env computation for a given bundle. Returns the LD_LIBRARY_PATH /
3
+ * GI_TYPELIB_PATH values that {@link runGjsBundle} would inject into the
4
+ * spawned `gjs` process, plus the formatted env-prefix string used for the
5
+ * `$ …` echo.
6
+ */
7
+ export declare function computeNativeEnvForBundle(bundlePath: string, cwd?: string): {
8
+ env: {
9
+ LD_LIBRARY_PATH: string;
10
+ GI_TYPELIB_PATH: string;
11
+ };
12
+ envPrefix: string;
13
+ };
1
14
  /**
2
15
  * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
3
- * for any installed native gjsify packages.
4
- *
5
- * Detection uses two strategies:
6
- * 1. Filesystem walk from CWD (finds packages in the user's project)
7
- * 2. require.resolve from the bundle's location (finds packages in the CLI's dependency tree)
16
+ * for any installed native gjsify packages discoverable from either the CWD
17
+ * or the bundle's own node_modules tree.
8
18
  */
9
19
  export declare function runGjsBundle(bundlePath: string, extraArgs?: string[]): Promise<void>;
@@ -1,29 +1,53 @@
1
1
  // Shared utility for running a GJS bundle with native package env vars.
2
- // Used by both the `run` and `showcase` commands.
2
+ // Used by `gjsify run`, `gjsify dlx`, and the showcase command (via dlx).
3
+ //
4
+ // Detection runs the same exhaustive node_modules walker (`detectNativePackages`)
5
+ // from two starting points and merges by package name (CWD shadows bundle):
6
+ //
7
+ // 1. process.cwd() — picks up native deps in the user's project
8
+ // (yarn / pnpm / npm node_modules walking up).
9
+ // 2. dirname(bundlePath) — picks up native deps in whatever node_modules
10
+ // the bundle lives in. Critical for `gjsify dlx`
11
+ // where the bundle resides in
12
+ // `~/.cache/gjsify/dlx/<sha>/.../node_modules/<pkg>/dist/`
13
+ // and the user's CWD is unrelated. The bundle-side
14
+ // walk also catches transitive deps' typelibs.
15
+ //
16
+ // Env composition is split out as `computeNativeEnvForBundle()` — a pure
17
+ // function that takes a bundle path + cwd and returns the env it would inject.
18
+ // This lets the e2e tests assert the env without spawning gjs.
3
19
  import { spawn } from 'node:child_process';
4
- import { resolve } from 'node:path';
5
- import { detectNativePackages, resolveNativePackages, buildNativeEnv } from './detect-native-packages.js';
20
+ import { dirname, resolve } from 'node:path';
21
+ import { detectNativePackages, buildNativeEnv } from './detect-native-packages.js';
6
22
  /**
7
- * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
8
- * for any installed native gjsify packages.
9
- *
10
- * Detection uses two strategies:
11
- * 1. Filesystem walk from CWD (finds packages in the user's project)
12
- * 2. require.resolve from the bundle's location (finds packages in the CLI's dependency tree)
23
+ * Pure env computation for a given bundle. Returns the LD_LIBRARY_PATH /
24
+ * GI_TYPELIB_PATH values that {@link runGjsBundle} would inject into the
25
+ * spawned `gjs` process, plus the formatted env-prefix string used for the
26
+ * `$ …` echo.
13
27
  */
14
- export async function runGjsBundle(bundlePath, extraArgs = []) {
15
- const cwd = process.cwd();
28
+ export function computeNativeEnvForBundle(bundlePath, cwd = process.cwd()) {
16
29
  const resolvedBundle = resolve(bundlePath);
17
- // Detect from CWD (filesystem walk) + bundle location (require.resolve)
18
30
  const cwdPackages = detectNativePackages(cwd);
19
- const bundlePackages = resolveNativePackages(resolvedBundle);
20
- // Merge, deduplicating by name (CWD takes precedence)
21
- const seen = new Set(cwdPackages.map(p => p.name));
31
+ const bundlePackages = detectNativePackages(dirname(resolvedBundle));
32
+ const seen = new Set(cwdPackages.map((p) => p.name));
22
33
  const nativePackages = [
23
34
  ...cwdPackages,
24
- ...bundlePackages.filter(p => !seen.has(p.name)),
35
+ ...bundlePackages.filter((p) => !seen.has(p.name)),
25
36
  ];
26
- const nativeEnv = buildNativeEnv(nativePackages);
37
+ const env = buildNativeEnv(nativePackages);
38
+ const envPrefix = Object.entries(env)
39
+ .filter(([, value]) => value !== undefined && value !== '')
40
+ .map(([key, value]) => `${key}=${value}`)
41
+ .join(' ');
42
+ return { env, envPrefix };
43
+ }
44
+ /**
45
+ * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
46
+ * for any installed native gjsify packages discoverable from either the CWD
47
+ * or the bundle's own node_modules tree.
48
+ */
49
+ export async function runGjsBundle(bundlePath, extraArgs = []) {
50
+ const { env: nativeEnv, envPrefix } = computeNativeEnvForBundle(bundlePath);
27
51
  const env = {
28
52
  ...process.env,
29
53
  ...nativeEnv,
@@ -32,11 +56,7 @@ export async function runGjsBundle(bundlePath, extraArgs = []) {
32
56
  // Print the exact command being executed so users can copy-paste it to
33
57
  // run gjs directly without the wrapper. Env vars are only shown if we
34
58
  // 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(' ');
59
+ const gjsCommand = ['gjs', ...gjsArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
40
60
  console.log(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
41
61
  const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });
42
62
  await new Promise((resolvePromise, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,19 +23,19 @@
23
23
  "cli"
24
24
  ],
25
25
  "dependencies": {
26
- "@gjsify/create-app": "^0.3.16",
27
- "@gjsify/node-polyfills": "^0.3.16",
28
- "@gjsify/npm-registry": "^0.3.16",
29
- "@gjsify/resolve-npm": "^0.3.16",
30
- "@gjsify/rolldown-plugin-gjsify": "^0.3.16",
31
- "@gjsify/rolldown-plugin-pnp": "^0.3.16",
32
- "@gjsify/semver": "^0.3.16",
33
- "@gjsify/tar": "^0.3.16",
34
- "@gjsify/web-polyfills": "^0.3.16",
26
+ "@gjsify/create-app": "^0.3.17",
27
+ "@gjsify/node-polyfills": "^0.3.17",
28
+ "@gjsify/npm-registry": "^0.3.17",
29
+ "@gjsify/resolve-npm": "^0.3.17",
30
+ "@gjsify/rolldown-plugin-gjsify": "^0.3.17",
31
+ "@gjsify/rolldown-plugin-pnp": "^0.3.17",
32
+ "@gjsify/semver": "^0.3.17",
33
+ "@gjsify/tar": "^0.3.17",
34
+ "@gjsify/web-polyfills": "^0.3.17",
35
35
  "cosmiconfig": "^9.0.1",
36
36
  "get-tsconfig": "^4.14.0",
37
37
  "pkg-types": "^2.3.1",
38
- "rolldown": "^1.0.0-rc.18",
38
+ "rolldown": "^1.0.0",
39
39
  "yargs": "^18.0.0"
40
40
  },
41
41
  "devDependencies": {
@@ -8,11 +8,20 @@ export const buildCommand: Command<any, CliBuildOptions> = {
8
8
  builder: (yargs) => {
9
9
  return yargs
10
10
  .option('entry-points', {
11
- description: "The entry points you want to bundle",
11
+ description: "The entry points you want to bundle. Defaults to bundler.input from package.json#gjsify or .gjsifyrc.js, falling back to src/index.ts when neither is set.",
12
12
  array: true,
13
13
  type: 'string',
14
14
  normalize: true,
15
- default: ['src/index.ts'],
15
+ // No yargs `default` here on purpose. A yargs default value
16
+ // is indistinguishable from "user passed the flag" in the
17
+ // parsed args (cliArgs.entryPoints?.length is truthy either
18
+ // way), so the merge step in config.ts would unconditionally
19
+ // overwrite `bundler.input` declared in package.json#gjsify —
20
+ // silently ignoring `gjsify.bundler.input: "src/start.ts"`
21
+ // and producing a bundle from the wrong entry point. The
22
+ // fallback to src/index.ts is applied in config.ts AFTER
23
+ // merging with the cosmiconfig data.
24
+ defaultDescription: "src/index.ts (fallback)",
16
25
  coerce: (arg: string[]) => {
17
26
  // Removes duplicates
18
27
  return [...new Set(arg)];
@@ -45,10 +54,10 @@ export const buildCommand: Command<any, CliBuildOptions> = {
45
54
  normalize: true,
46
55
  })
47
56
  .option('minify', {
48
- description: "When enabled, the generated code will be minified instead of pretty-printed",
57
+ description: "Minify the bundled output. Defaults to true; use --no-minify to emit pretty-printed code (e.g. for debugging or readable bundle review).",
49
58
  type: 'boolean',
50
59
  normalize: true,
51
- default: false
60
+ defaultDescription: 'true',
52
61
  })
53
62
  .option('library', {
54
63
  description: "Use this if you want to build a library for Gjsify",
package/src/config.ts CHANGED
@@ -231,10 +231,20 @@ export class Config {
231
231
  const transform = (bundler.transform ??= {});
232
232
 
233
233
  if (cliArgs.entryPoints?.length) bundler.input = cliArgs.entryPoints;
234
+ // Fallback when neither the CLI flag nor the cosmiconfig data set an
235
+ // entry point. Applied here (post-merge) rather than as a yargs
236
+ // `default:` because yargs defaults are indistinguishable from
237
+ // user-set values, and would silently overwrite `bundler.input`
238
+ // declared in package.json#gjsify.
239
+ if (!bundler.input) bundler.input = ['src/index.ts'];
234
240
  if (cliArgs.outfile !== undefined) output.file = cliArgs.outfile;
235
241
  if (cliArgs.outdir !== undefined) output.dir = cliArgs.outdir;
236
242
  if (cliArgs.format !== undefined) output.format = cliArgs.format as 'esm' | 'cjs' | 'iife';
243
+ // CLI flag wins over config; if neither is set, minify by default.
244
+ // Pretty-printed output is opt-in via `--no-minify` or
245
+ // `bundler.output.minify: false` in the config.
237
246
  if (cliArgs.minify !== undefined) output.minify = cliArgs.minify;
247
+ if (output.minify === undefined) output.minify = true;
238
248
  if (cliArgs.logLevel) {
239
249
  // Map esbuild log levels to Rolldown's narrower set:
240
250
  // esbuild → rolldown
@@ -1,11 +1,18 @@
1
1
  // Utility to find npm packages with gjsify native prebuilds.
2
2
  // Packages declare: "gjsify": { "prebuilds": "<dir>" } in their package.json.
3
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.
4
13
 
5
14
  import { readdirSync, existsSync, readFileSync } from 'node:fs';
6
- import { createRequire } from 'node:module';
7
- import { dirname, join, resolve } from 'node:path';
8
- import { pathToFileURL } from 'node:url';
15
+ import { join, resolve } from 'node:path';
9
16
 
10
17
  export interface NativePackage {
11
18
  /** npm package name, e.g. "@gjsify/webgl" */
@@ -129,76 +136,6 @@ export function detectNativePackages(startDir: string): NativePackage[] {
129
136
  return merged;
130
137
  }
131
138
 
132
- /** Walk up from dir to find the nearest package.json. */
133
- function findNearestPackageJson(startDir: string): string | null {
134
- let dir = resolve(startDir);
135
- while (true) {
136
- const candidate = join(dir, 'package.json');
137
- if (existsSync(candidate)) return candidate;
138
- const parent = resolve(dir, '..');
139
- if (parent === dir) return null;
140
- dir = parent;
141
- }
142
- }
143
-
144
- /**
145
- * Resolve native packages using Node.js module resolution from a given file path.
146
- * Reads the nearest package.json to discover dependencies, then checks each
147
- * for gjsify native prebuilds metadata.
148
- *
149
- * Also checks the **nearest package.json itself** — a workspace package may
150
- * have its own prebuilds (e.g. `@gjsify/webgl` running its own test) and
151
- * never list itself in dependencies.
152
- *
153
- * This complements detectNativePackages() (filesystem walk from CWD) by using
154
- * require.resolve() — which handles hoisting, workspaces, and nested node_modules.
155
- */
156
- export function resolveNativePackages(fromFilePath: string): NativePackage[] {
157
- const arch = nodeArchToLinuxArch(process.arch);
158
- const results: NativePackage[] = [];
159
-
160
- try {
161
- const req = createRequire(pathToFileURL(fromFilePath).href);
162
-
163
- // Find the nearest package.json to get the dependency list
164
- const nearestPkgJson = findNearestPackageJson(dirname(resolve(fromFilePath)));
165
- if (!nearestPkgJson) return results;
166
-
167
- const pkg = readPackageJson(nearestPkgJson);
168
- if (!pkg) return results;
169
-
170
- // Check the nearest package itself (e.g. @gjsify/webgl running its own
171
- // test bundle — webgl never lists itself in dependencies)
172
- const ownName = typeof pkg['name'] === 'string' ? pkg['name'] as string : '';
173
- if (ownName) {
174
- const ownNative = checkPackage(dirname(nearestPkgJson), ownName, arch);
175
- if (ownNative) results.push(ownNative);
176
- }
177
-
178
- const deps = pkg['dependencies'] as Record<string, string> | undefined;
179
- if (!deps) return results;
180
-
181
- for (const depName of Object.keys(deps)) {
182
- try {
183
- // Resolve the package's main entry, then walk up to find its package.json.
184
- // We cannot use require.resolve(name + '/package.json') because packages
185
- // with an "exports" field may not expose ./package.json as a subpath.
186
- const entryPath = req.resolve(depName);
187
- const depPkgJson = findNearestPackageJson(dirname(entryPath));
188
- if (!depPkgJson) continue;
189
- const native = checkPackage(dirname(depPkgJson), depName, arch);
190
- if (native) results.push(native);
191
- } catch {
192
- // Dependency not resolvable — skip
193
- }
194
- }
195
- } catch {
196
- // Resolution failed — return empty
197
- }
198
-
199
- return results;
200
- }
201
-
202
139
  /**
203
140
  * Build the LD_LIBRARY_PATH and GI_TYPELIB_PATH env var values for the detected native packages.
204
141
  * Prepends the new paths to any existing values from the environment.
@@ -1,34 +1,63 @@
1
1
  // Shared utility for running a GJS bundle with native package env vars.
2
- // Used by both the `run` and `showcase` commands.
2
+ // Used by `gjsify run`, `gjsify dlx`, and the showcase command (via dlx).
3
+ //
4
+ // Detection runs the same exhaustive node_modules walker (`detectNativePackages`)
5
+ // from two starting points and merges by package name (CWD shadows bundle):
6
+ //
7
+ // 1. process.cwd() — picks up native deps in the user's project
8
+ // (yarn / pnpm / npm node_modules walking up).
9
+ // 2. dirname(bundlePath) — picks up native deps in whatever node_modules
10
+ // the bundle lives in. Critical for `gjsify dlx`
11
+ // where the bundle resides in
12
+ // `~/.cache/gjsify/dlx/<sha>/.../node_modules/<pkg>/dist/`
13
+ // and the user's CWD is unrelated. The bundle-side
14
+ // walk also catches transitive deps' typelibs.
15
+ //
16
+ // Env composition is split out as `computeNativeEnvForBundle()` — a pure
17
+ // function that takes a bundle path + cwd and returns the env it would inject.
18
+ // This lets the e2e tests assert the env without spawning gjs.
3
19
 
4
20
  import { spawn } from 'node:child_process';
5
- import { resolve } from 'node:path';
6
- import { detectNativePackages, resolveNativePackages, buildNativeEnv } from './detect-native-packages.js';
21
+ import { dirname, resolve } from 'node:path';
22
+ import { detectNativePackages, buildNativeEnv } from './detect-native-packages.js';
7
23
 
8
24
  /**
9
- * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
10
- * for any installed native gjsify packages.
11
- *
12
- * Detection uses two strategies:
13
- * 1. Filesystem walk from CWD (finds packages in the user's project)
14
- * 2. require.resolve from the bundle's location (finds packages in the CLI's dependency tree)
25
+ * Pure env computation for a given bundle. Returns the LD_LIBRARY_PATH /
26
+ * GI_TYPELIB_PATH values that {@link runGjsBundle} would inject into the
27
+ * spawned `gjs` process, plus the formatted env-prefix string used for the
28
+ * `$ …` echo.
15
29
  */
16
- export async function runGjsBundle(bundlePath: string, extraArgs: string[] = []): Promise<void> {
17
- const cwd = process.cwd();
30
+ export function computeNativeEnvForBundle(
31
+ bundlePath: string,
32
+ cwd: string = process.cwd(),
33
+ ): { env: { LD_LIBRARY_PATH: string; GI_TYPELIB_PATH: string }; envPrefix: string } {
18
34
  const resolvedBundle = resolve(bundlePath);
19
35
 
20
- // Detect from CWD (filesystem walk) + bundle location (require.resolve)
21
36
  const cwdPackages = detectNativePackages(cwd);
22
- const bundlePackages = resolveNativePackages(resolvedBundle);
37
+ const bundlePackages = detectNativePackages(dirname(resolvedBundle));
23
38
 
24
- // Merge, deduplicating by name (CWD takes precedence)
25
- const seen = new Set(cwdPackages.map(p => p.name));
39
+ const seen = new Set(cwdPackages.map((p) => p.name));
26
40
  const nativePackages = [
27
41
  ...cwdPackages,
28
- ...bundlePackages.filter(p => !seen.has(p.name)),
42
+ ...bundlePackages.filter((p) => !seen.has(p.name)),
29
43
  ];
30
44
 
31
- const nativeEnv = buildNativeEnv(nativePackages);
45
+ const env = buildNativeEnv(nativePackages);
46
+ const envPrefix = Object.entries(env)
47
+ .filter(([, value]) => value !== undefined && value !== '')
48
+ .map(([key, value]) => `${key}=${value}`)
49
+ .join(' ');
50
+
51
+ return { env, envPrefix };
52
+ }
53
+
54
+ /**
55
+ * Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH
56
+ * for any installed native gjsify packages discoverable from either the CWD
57
+ * or the bundle's own node_modules tree.
58
+ */
59
+ export async function runGjsBundle(bundlePath: string, extraArgs: string[] = []): Promise<void> {
60
+ const { env: nativeEnv, envPrefix } = computeNativeEnvForBundle(bundlePath);
32
61
 
33
62
  const env = {
34
63
  ...process.env,
@@ -40,11 +69,7 @@ export async function runGjsBundle(bundlePath: string, extraArgs: string[] = [])
40
69
  // Print the exact command being executed so users can copy-paste it to
41
70
  // run gjs directly without the wrapper. Env vars are only shown if we
42
71
  // actually set any (i.e. native gjsify packages were detected).
43
- const envPrefix = Object.entries(nativeEnv)
44
- .filter(([, value]) => value !== undefined && value !== '')
45
- .map(([key, value]) => `${key}=${value}`)
46
- .join(' ');
47
- const gjsCommand = ['gjs', ...gjsArgs.map(a => a.includes(' ') ? `"${a}"` : a)].join(' ');
72
+ const gjsCommand = ['gjs', ...gjsArgs.map((a) => (a.includes(' ') ? `"${a}"` : a))].join(' ');
48
73
  console.log(`$ ${envPrefix ? `${envPrefix} ` : ''}${gjsCommand}`);
49
74
 
50
75
  const child = spawn('gjs', gjsArgs, { env, stdio: 'inherit' });