@gjsify/cli 0.4.16 → 0.4.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.
@@ -354,6 +354,14 @@ async function workspaceInstall(cwd, args) {
354
354
  // target in a shell script that picks the right interpreter (`gjs -m`
355
355
  // for `.mjs` bundles, `node` for `.js` files).
356
356
  const wsBinDir = join(cwd, 'node_modules', '.bin');
357
+ // Discover native prebuilds reachable from the workspace cwd so the
358
+ // workspace-local `node_modules/.bin/gjsify` shim sets GI_TYPELIB_PATH /
359
+ // LD_LIBRARY_PATH for them. Same rationale as the global launcher in
360
+ // install-global.ts — the bin shim invokes the CLI bundle via `gjs -m`
361
+ // directly, with no chance to set env after the fact, so without this
362
+ // preamble `imports.gi.GjsifyTerminal` etc. fail and process.stdout
363
+ // collapses to no-color, 80-col defaults.
364
+ const nativePrebuildDirs = detectNativePackages(cwd).map((p) => p.prebuildsDir);
357
365
  let wsBinsCreated = 0;
358
366
  for (const ws of workspaces) {
359
367
  const m = ws.manifest;
@@ -375,7 +383,7 @@ async function workspaceInstall(cwd, args) {
375
383
  rmSync(linkPath, { force: true });
376
384
  }
377
385
  catch { /* fine */ }
378
- writeFileSync(linkPath, buildBinShim(ws.location, nodeTarget, gjsTarget), { mode: 0o755 });
386
+ writeFileSync(linkPath, buildBinShim(ws.location, nodeTarget, gjsTarget, nativePrebuildDirs), { mode: 0o755 });
379
387
  chmodSync(linkPath, 0o755);
380
388
  wsBinsCreated++;
381
389
  }
@@ -441,16 +449,27 @@ function extractOverrides(rootManifest) {
441
449
  merge(rootManifest.resolutions, 'resolutions');
442
450
  return Object.keys(out).length > 0 ? out : undefined;
443
451
  }
444
- function buildBinShim(wsLocation, nodeTarget, gjsTarget) {
452
+ function buildBinShim(wsLocation, nodeTarget, gjsTarget, nativePrebuildDirs = []) {
445
453
  const nodeAbs = nodeTarget ? join(wsLocation, nodeTarget) : null;
446
454
  const gjsAbs = gjsTarget ? join(wsLocation, gjsTarget) : null;
455
+ // GJS-only env preamble — Node ignores GI_TYPELIB_PATH so we scope the
456
+ // export to the gjs branch, keeping the shim minimal when no native pkgs
457
+ // exist or only the Node bin is in play.
458
+ const gjsPreamble = nativePrebuildDirs.length === 0
459
+ ? ''
460
+ : (() => {
461
+ const joined = `'${nativePrebuildDirs.join(':').replace(/'/g, `'\\''`)}'`;
462
+ return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}\n` +
463
+ `LD_LIBRARY_PATH=${joined}\${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}\n` +
464
+ `export GI_TYPELIB_PATH LD_LIBRARY_PATH\n`);
465
+ })();
447
466
  if (nodeAbs && gjsAbs) {
448
- return `#!/bin/sh\nif [ -f "${nodeAbs}" ]; then\n exec node "${nodeAbs}" "$@"\nfi\nexec gjs -m "${gjsAbs}" "$@"\n`;
467
+ return `#!/bin/sh\nif [ -f "${nodeAbs}" ]; then\n exec node "${nodeAbs}" "$@"\nfi\n${gjsPreamble}exec gjs -m "${gjsAbs}" "$@"\n`;
449
468
  }
450
469
  if (nodeAbs)
451
470
  return `#!/bin/sh\nexec node "${nodeAbs}" "$@"\n`;
452
471
  if (gjsAbs)
453
- return `#!/bin/sh\nexec gjs -m "${gjsAbs}" "$@"\n`;
472
+ return `#!/bin/sh\n${gjsPreamble}exec gjs -m "${gjsAbs}" "$@"\n`;
454
473
  throw new Error('buildBinShim: either nodeTarget or gjsTarget must be provided');
455
474
  }
456
475
  /**
@@ -97,13 +97,18 @@ export const uninstallCommand = {
97
97
  * #!/bin/sh
98
98
  * exec '<absolute-path>' "$@"
99
99
  *
100
- * or (for `.gjs.mjs` / `.mjs` targets):
100
+ * or (for `.gjs.mjs` / `.mjs` targets, with optional GI_TYPELIB_PATH /
101
+ * LD_LIBRARY_PATH preamble for native @gjsify/* prebuilds):
101
102
  *
102
103
  * #!/bin/sh
104
+ * GI_TYPELIB_PATH='<dirs>'${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}
105
+ * LD_LIBRARY_PATH='<dirs>'${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}
106
+ * export GI_TYPELIB_PATH LD_LIBRARY_PATH
103
107
  * exec gjs -m '<absolute-path>' "$@"
104
108
  *
105
- * We parse the absolute path out of the single-quoted segment and check
106
- * whether it's under `pkgDir`. Non-shim files (e.g. unrelated binaries
109
+ * We extract the single-quoted path on the `exec` line (NOT the first quoted
110
+ * string in the file, which with the preamble is the prebuild dir list) and
111
+ * check whether it's under `pkgDir`. Non-shim files (e.g. unrelated binaries
107
112
  * the user installed via `npm install -g`) are skipped silently.
108
113
  */
109
114
  function findBinShimsForPackage(binDir, pkgDir, verbose) {
@@ -126,8 +131,15 @@ function findBinShimsForPackage(binDir, pkgDir, verbose) {
126
131
  const content = readFileSync(fullPath, 'utf-8');
127
132
  if (!content.startsWith('#!/bin/sh'))
128
133
  continue;
129
- // Match the first single-quoted absolute path.
130
- const m = content.match(/'([^']+)'/);
134
+ // Find the `exec [gjs -m] '<target>' "$@"` line; the path may
135
+ // contain `:` from the optional prebuild preamble lines, which
136
+ // is why we anchor to `exec ` rather than the first quoted run.
137
+ const execLine = content
138
+ .split('\n')
139
+ .find((line) => /^exec (?:gjs -m )?'/.test(line));
140
+ if (!execLine)
141
+ continue;
142
+ const m = execLine.match(/'([^']+)'/);
131
143
  if (!m)
132
144
  continue;
133
145
  const target = m[1];
package/lib/index.js CHANGED
@@ -8,9 +8,19 @@ import { APP_NAME } from './constants.js';
8
8
  // cosmetic — the event loop holds the process up — but under GJS the
9
9
  // script ends as soon as the top-level synchronous flow finishes, and
10
10
  // fire-and-forget handlers silently exit before any async work runs.
11
- await yargs(hideBin(process.argv))
11
+ const cli = yargs(hideBin(process.argv));
12
+ await cli
12
13
  .scriptName(APP_NAME)
13
14
  .strict()
15
+ // Use the full terminal width for help. yargs's default caps at 80
16
+ // (`Math.min(80, process.stdout.columns)`); we explicitly opt into
17
+ // the real terminal width so long option/description lines wrap at
18
+ // the actual terminal edge instead of an arbitrary 80-col limit.
19
+ // `terminalWidth()` reads `process.stdout.columns`, which under GJS
20
+ // is backed by @gjsify/terminal-native (ioctl TIOCGWINSZ) when the
21
+ // typelib is on GI_TYPELIB_PATH — see the global launcher in
22
+ // packages/infra/cli/src/utils/install-global.ts.
23
+ .wrap(cli.terminalWidth())
14
24
  .command(create.command, create.description, create.builder, create.handler)
15
25
  .command(install.command, install.description, install.builder, install.handler)
16
26
  .command(build.command, build.description, build.builder, build.handler)
@@ -23,6 +23,7 @@
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 } from './detect-native-packages.js';
26
27
  /**
27
28
  * Compute the canonical global install layout for the current user. Honours
28
29
  * `XDG_DATA_HOME` (per the XDG Base Directory Spec) plus
@@ -67,6 +68,18 @@ export function defaultGlobalLayout() {
67
68
  export function linkGlobalBins(packageNames, layout) {
68
69
  fs.mkdirSync(layout.binDir, { recursive: true });
69
70
  const created = [];
71
+ // Discover @gjsify/* packages with native prebuilds (Vala/GObject typelibs
72
+ // + shared libs) under the global prefix. The launcher bakes their
73
+ // directories into GI_TYPELIB_PATH / LD_LIBRARY_PATH so `imports.gi.X`
74
+ // resolves at CLI startup — required for e.g. @gjsify/terminal-native,
75
+ // without which process.stdout.isTTY / columns / colors all fall back to
76
+ // the conservative env-only defaults (no colors, 80-col wrap).
77
+ //
78
+ // `runGjsBundle()` does the same dance for `gjsify run <bundle>` at
79
+ // runtime; here we do it at install time because the global launcher
80
+ // invokes the CLI bundle directly, with no opportunity to set env first.
81
+ const nativePrebuildDirs = detectNativePackages(layout.prefix).map((p) => p.prebuildsDir);
82
+ const envPreamble = buildLauncherEnvPreamble(nativePrebuildDirs);
70
83
  for (const pkgName of packageNames) {
71
84
  const pkgDir = path.join(layout.prefix, 'node_modules', pkgName);
72
85
  const pkgJsonPath = path.join(pkgDir, 'package.json');
@@ -101,8 +114,11 @@ export function linkGlobalBins(packageNames, layout) {
101
114
  // then tries to parse JavaScript as shell. Plain Node scripts
102
115
  // with shebangs (lib/index.js) keep the direct-exec path.
103
116
  const isGjsBundle = targetAbs.endsWith('.gjs.mjs') || targetAbs.endsWith('.mjs');
117
+ // Only GJS bundles need the GI typelib search path. Plain Node
118
+ // scripts ignore GI_TYPELIB_PATH, so skipping the preamble there
119
+ // keeps the launcher minimal.
104
120
  const launcher = isGjsBundle
105
- ? `#!/bin/sh\nexec gjs -m ${shQuote(targetAbs)} "$@"\n`
121
+ ? `#!/bin/sh\n${envPreamble}exec gjs -m ${shQuote(targetAbs)} "$@"\n`
106
122
  : `#!/bin/sh\nexec ${shQuote(targetAbs)} "$@"\n`;
107
123
  fs.writeFileSync(linkPath, launcher);
108
124
  fs.chmodSync(linkPath, 0o755);
@@ -114,6 +130,23 @@ export function linkGlobalBins(packageNames, layout) {
114
130
  function shQuote(s) {
115
131
  return `'${s.replace(/'/g, `'\\''`)}'`;
116
132
  }
133
+ /**
134
+ * Build the POSIX-sh `export` lines that prepend the given prebuild
135
+ * directories to GI_TYPELIB_PATH and LD_LIBRARY_PATH. Any pre-existing value
136
+ * inherited from the user's environment is preserved as a suffix so
137
+ * user-installed typelibs/libraries still resolve.
138
+ *
139
+ * Returns the empty string when no prebuilds were found — avoids emitting an
140
+ * inert assignment in the launcher.
141
+ */
142
+ function buildLauncherEnvPreamble(prebuildsDirs) {
143
+ if (prebuildsDirs.length === 0)
144
+ return '';
145
+ const joined = shQuote(prebuildsDirs.join(':'));
146
+ return (`GI_TYPELIB_PATH=${joined}\${GI_TYPELIB_PATH:+":$GI_TYPELIB_PATH"}\n` +
147
+ `LD_LIBRARY_PATH=${joined}\${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}\n` +
148
+ `export GI_TYPELIB_PATH LD_LIBRARY_PATH\n`);
149
+ }
117
150
  function pickBinMap(pkgName, pkgJson) {
118
151
  const gjsifyEntry = pkgJson.gjsify;
119
152
  if (gjsifyEntry?.bin !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -37,18 +37,18 @@
37
37
  "cli"
38
38
  ],
39
39
  "dependencies": {
40
- "@gjsify/buffer": "^0.4.16",
41
- "@gjsify/create-app": "^0.4.16",
42
- "@gjsify/node-globals": "^0.4.16",
43
- "@gjsify/node-polyfills": "^0.4.16",
44
- "@gjsify/npm-registry": "^0.4.16",
45
- "@gjsify/resolve-npm": "^0.4.16",
46
- "@gjsify/rolldown-plugin-gjsify": "^0.4.16",
47
- "@gjsify/rolldown-plugin-pnp": "^0.4.16",
48
- "@gjsify/semver": "^0.4.16",
49
- "@gjsify/tar": "^0.4.16",
50
- "@gjsify/web-polyfills": "^0.4.16",
51
- "@gjsify/workspace": "^0.4.16",
40
+ "@gjsify/buffer": "^0.4.17",
41
+ "@gjsify/create-app": "^0.4.17",
42
+ "@gjsify/node-globals": "^0.4.17",
43
+ "@gjsify/node-polyfills": "^0.4.17",
44
+ "@gjsify/npm-registry": "^0.4.17",
45
+ "@gjsify/resolve-npm": "^0.4.17",
46
+ "@gjsify/rolldown-plugin-gjsify": "^0.4.17",
47
+ "@gjsify/rolldown-plugin-pnp": "^0.4.17",
48
+ "@gjsify/semver": "^0.4.17",
49
+ "@gjsify/tar": "^0.4.17",
50
+ "@gjsify/web-polyfills": "^0.4.17",
51
+ "@gjsify/workspace": "^0.4.17",
52
52
  "cosmiconfig": "^9.0.1",
53
53
  "get-tsconfig": "^4.14.0",
54
54
  "pkg-types": "^2.3.1",
@@ -56,12 +56,12 @@
56
56
  "yargs": "^18.0.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@gjsify/unit": "^0.4.16",
59
+ "@gjsify/unit": "^0.4.17",
60
60
  "@types/yargs": "^17.0.35",
61
61
  "typescript": "^6.0.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@gjsify/rolldown-native": "^0.4.16"
64
+ "@gjsify/rolldown-native": "^0.4.17"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@gjsify/rolldown-native": {