@gjsify/cli 0.64.4 → 0.64.5

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.
@@ -26,12 +26,16 @@ import { CHANNELS, CHANNEL_ENV, parseDeclaredChannel } from '../utils/channel.js
26
26
  import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
27
27
  import { basename, dirname, join, relative, resolve } from 'node:path';
28
28
  import { Config } from '../config.js';
29
+ import { ensurePkgDir } from './dlx.js';
29
30
  import { renderDesktopEntry, renderMetainfoApp, renderMetainfoCli, validateAppMetadata, } from '../utils/app-metadata.js';
30
31
  import { readPackageJson } from '../utils/pkg-json.js';
32
+ import { parseSpec } from '../utils/parse-spec.js';
33
+ import { cliVersion } from '../utils/publish-headers.js';
31
34
  import { describeExit, spawnToCompletion } from '../utils/spawn.js';
32
35
  import { buildDeb } from '../utils/ship/deb.js';
33
36
  import { deriveDepends, warnAboutGjsFloor, warnAboutNodeFloor } from '../utils/ship/depends.js';
34
37
  import { isGtkRuntimeTarget, stageAppRuntime } from '../utils/ship/app-runtime.js';
38
+ import { acquireGtkRuntime } from '../utils/ship/gtk-runtime-acquire.js';
35
39
  import { discoverPayload } from '../utils/ship/discover.js';
36
40
  import { appDirFor, appDirPayload, appImageHostRequirements, appImageRuntimeNotice, buildAppImage, findPinnedRuntime, stampAppDirTimes, } from '../utils/ship/appimage.js';
37
41
  import { buildDmgImage, dmgVolumeDir, dmgVolumeName } from '../utils/ship/dmg.js';
@@ -411,7 +415,7 @@ async function assemble(args) {
411
415
  // `.deb`/`.rpm` `Depends:` is the promise that `node` or `gjs` is on `PATH`, and
412
416
  // carrying a second copy inside the payload would be a private interpreter no
413
417
  // distribution update ever patches.
414
- const runtime = resolveCarriedRuntime(layout, settings, projectDir);
418
+ const runtime = await resolveCarriedRuntime(layout, settings, projectDir);
415
419
  const stageInputs = {
416
420
  bundleFiles: discovered.bundleFiles,
417
421
  launcher: renderLauncher(settings, basename(settings.bundlePath), layout, runtime.launcher),
@@ -761,21 +765,35 @@ function announceSigning(sign, notarize) {
761
765
  * thing to make non-vacuous on this layout, since a JavaScript bundle has no
762
766
  * cputype to disagree with.
763
767
  */
764
- function resolveCarriedRuntime(layout, settings, projectDir) {
768
+ async function resolveCarriedRuntime(layout, settings, projectDir) {
765
769
  const empty = { files: [], launcher: {}, found: [], missing: [] };
766
770
  if (layout.os === 'linux' || settings.app !== 'node')
767
771
  return empty;
768
772
  const target = `${layout.os}-${settings.arch}`;
769
773
  if (!isGtkRuntimeTarget(target))
770
774
  return empty;
775
+ // The closure is fetched, not installed: a project that lists it puts a second
776
+ // GLib next to Homebrew's in its dev tree (see `gtk-runtime-acquire.ts`).
777
+ const acquired = await acquireGtkRuntime({
778
+ target,
779
+ version: cliVersion(),
780
+ discovery: { cwd: projectDir },
781
+ ensure: async (spec) => (await ensurePkgDir(parseSpec(spec), { verbose: false, cacheMaxAge: DLX_CACHE_MAX_AGE_MIN, frozen: false }))
782
+ .pkgDir,
783
+ log: (message) => console.log(`${LOG} ${message}`),
784
+ });
771
785
  return stageAppRuntime({
772
786
  layout,
773
787
  identity: settings,
774
788
  target,
775
789
  cwd: projectDir,
776
790
  interpreter: isNodeRuntimeTarget(target) ? resolveNodeRuntime(target, { cwd: projectDir }) : null,
791
+ ...(acquired.kind === 'fetched' ? { fetchedGtkRuntimeDir: acquired.dir } : {}),
792
+ ...(acquired.kind === 'failed' ? { gtkFetchFailure: acquired.reason } : {}),
777
793
  });
778
794
  }
795
+ /** How long a fetched runtime stays in the dlx cache — the same week `gjsify showcase` uses. */
796
+ const DLX_CACHE_MAX_AGE_MIN = 60 * 24 * 7;
779
797
  /**
780
798
  * Phase two: pack a stage that was assembled somewhere else.
781
799
  *
@@ -49,6 +49,13 @@ export interface ResolveRuntimeOptions {
49
49
  * is Bun's global-cache behaviour, which is otherwise unreachable from Node.
50
50
  */
51
51
  resolve?: ResolveInstalledPackageOptions['resolve'];
52
+ /**
53
+ * The directory of a `@gjsify/gtk-runtime-<target>` package that
54
+ * {@link acquireGtkRuntime} fetched for THIS run — the last candidate, after
55
+ * everything the author installed or pointed at. See `gtk-runtime-acquire.ts`
56
+ * for why it is fetched rather than installed.
57
+ */
58
+ fetchedGtkRuntimeDir?: string;
52
59
  }
53
60
  /**
54
61
  * Locate the relocated GTK closure for `target`, or `null`.
@@ -68,7 +75,11 @@ export interface ResolveRuntimeOptions {
68
75
  * for a ship run.
69
76
  * 3. the sibling monorepo directory `../gtk-runtime-<target>/gtk`, which is what
70
77
  * an in-repo `npm run build:bundle` writes.
71
- * 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`.
78
+ * 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`, when the
79
+ * author installed it.
80
+ * 5. the same package fetched for this run (`fetchedGtkRuntimeDir`) — NOT in
81
+ * node-gi's probe, because node-gi never fetches: it finds the closure inside
82
+ * the `.app` this function put it in, and the layout there is the same.
72
83
  *
73
84
  * A candidate counts only when it holds BOTH the loadable-code directory and the
74
85
  * typelib directory — node-gi's own probe, restated here rather than weakened,
@@ -195,6 +206,10 @@ export interface StageAppRuntimeInput {
195
206
  nodePath: string;
196
207
  licensePath: string;
197
208
  } | null;
209
+ /** See {@link ResolveRuntimeOptions.fetchedGtkRuntimeDir}. */
210
+ fetchedGtkRuntimeDir?: string;
211
+ /** Why the fetch produced nothing — named in the `missing` line, so the cause is not guessed at. */
212
+ gtkFetchFailure?: string;
198
213
  }
199
214
  /**
200
215
  * Stage the interpreter, the GTK closure and the addon into one bundle.
@@ -123,7 +123,11 @@ export const NODE_GI_PACKAGE = '@gjsify/node-gi';
123
123
  * for a ship run.
124
124
  * 3. the sibling monorepo directory `../gtk-runtime-<target>/gtk`, which is what
125
125
  * an in-repo `npm run build:bundle` writes.
126
- * 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`.
126
+ * 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`, when the
127
+ * author installed it.
128
+ * 5. the same package fetched for this run (`fetchedGtkRuntimeDir`) — NOT in
129
+ * node-gi's probe, because node-gi never fetches: it finds the closure inside
130
+ * the `.app` this function put it in, and the layout there is the same.
127
131
  *
128
132
  * A candidate counts only when it holds BOTH the loadable-code directory and the
129
133
  * typelib directory — node-gi's own probe, restated here rather than weakened,
@@ -155,6 +159,12 @@ export function resolveGtkRuntime(target, options = {}) {
155
159
  const entry = resolveStagedPackage(packageName, options);
156
160
  if (entry !== null)
157
161
  candidates.push({ source: packageName, dir: join(dirname(entry), 'gtk') });
162
+ if (options.fetchedGtkRuntimeDir !== undefined) {
163
+ candidates.push({
164
+ source: `${packageName} (fetched for this ship run)`,
165
+ dir: join(options.fetchedGtkRuntimeDir, 'gtk'),
166
+ });
167
+ }
158
168
  for (const candidate of candidates) {
159
169
  const libDir = join(candidate.dir, nativeSubdir);
160
170
  const typelibDir = join(candidate.dir, 'girepository-1.0');
@@ -340,8 +350,10 @@ export function stageAppRuntime(input) {
340
350
  // resolver then refuses -- a hint that is wrong is worse than none,
341
351
  // because it costs the reader the time to follow it first.
342
352
  const nativeSubdir = input.target.startsWith('win32-') ? 'bin' : 'lib';
343
- missing.push(`no GTK closure — install \`${gtkRuntimePackageName(input.target)}\` (or point ` +
344
- `\`GJSIFY_GTK_RUNTIME\` at a bundle directory holding \`${nativeSubdir}/\` and \`girepository-1.0/\`)`);
353
+ const why = input.gtkFetchFailure === undefined ? '' : ` — fetching it failed: ${input.gtkFetchFailure}`;
354
+ missing.push(`no GTK closure${why} — point \`GJSIFY_GTK_RUNTIME\` at a bundle directory holding ` +
355
+ `\`${nativeSubdir}/\` and \`girepository-1.0/\`, or install \`${gtkRuntimePackageName(input.target)}\` ` +
356
+ 'where the build can reach it');
345
357
  }
346
358
  // node-gi's JavaScript, which the bundle `require`s by name. Staged whenever
347
359
  // the package is resolvable and reported separately from the addon: a bundle
@@ -394,6 +406,8 @@ function discoveryOptions(input) {
394
406
  options.env = input.env;
395
407
  if (input.resolve !== undefined)
396
408
  options.resolve = input.resolve;
409
+ if (input.fetchedGtkRuntimeDir !== undefined)
410
+ options.fetchedGtkRuntimeDir = input.fetchedGtkRuntimeDir;
397
411
  return options;
398
412
  }
399
413
  /**
@@ -0,0 +1,36 @@
1
+ import { type GtkRuntimeTarget, type ResolveRuntimeOptions } from './app-runtime.js';
2
+ export type AcquiredGtkRuntime =
3
+ /** Something already resolves — nothing was fetched, and nothing will be. */
4
+ {
5
+ kind: 'present';
6
+ }
7
+ /** Fetched; `dir` is the PACKAGE directory, holding `gtk/`. */
8
+ | {
9
+ kind: 'fetched';
10
+ dir: string;
11
+ }
12
+ /** Could not be had; `reason` is what to tell the person, verbatim. */
13
+ | {
14
+ kind: 'failed';
15
+ reason: string;
16
+ };
17
+ export interface AcquireGtkRuntimeInput {
18
+ target: GtkRuntimeTarget;
19
+ /** The version to fetch: the CLI's own, because addon and closure are a matched pair. */
20
+ version: string;
21
+ /** The same discovery options the staging step will use — so "present" means what it will mean there. */
22
+ discovery?: ResolveRuntimeOptions;
23
+ /**
24
+ * Fetch a package spec into a cache and return its directory. Injected: the
25
+ * real one is `ensurePkgDir`, and a test must not touch a registry.
26
+ */
27
+ ensure: (spec: string) => Promise<string>;
28
+ /** One progress line. The closure is ~100 MB, so a silent first run looks like a hang. */
29
+ log?: (message: string) => void;
30
+ }
31
+ /**
32
+ * Make sure a GTK closure is reachable for `target`, fetching it only if nothing
33
+ * else provides one. Never throws: a ship run with no closure is a PARTIAL result
34
+ * that `stageAppRuntime` reports, not an error (see its doc).
35
+ */
36
+ export declare function acquireGtkRuntime(input: AcquireGtkRuntimeInput): Promise<AcquiredGtkRuntime>;
@@ -0,0 +1,59 @@
1
+ // Getting the GTK closure for a ship run when the project does not carry it.
2
+ //
3
+ // WHY IT IS FETCHED AND NOT INSTALLED. `@gjsify/gtk-runtime-<target>` is 100 MB of
4
+ // GLib, GObject, GTK and friends, and a project that lists it — as
5
+ // `optionalDependencies`, which the `os`/`cpu` gate turns into "installed on every
6
+ // Mac" — gets it into its own `node_modules`. On macOS that breaks every build and
7
+ // every run that goes through a Homebrew GJS. Every darwin prebuild
8
+ // carries the rpath `@loader_path` → `…/gtk-runtime-darwin-<arch>/gtk/lib` →
9
+ // `<brew>/lib` (ADR 0023: bundle before system, `docs/prebuilds.md`), so the first
10
+ // `dlopen` out of `node_modules/@gjsify/*-native-darwin-*` finds the bundle's
11
+ // GLib/GObject beside the one `gjs` already loaded from Homebrew: two GObject type
12
+ // registries in one process, which shows as `No signal 'context_response' on
13
+ // object 'GVolumeMonitor'` and a segfault, or as `GNotificationCenterDelegate is
14
+ // implemented in both …`. The invariant ADR 0023 names — one registry per process
15
+ // — is violated by the dev tree itself.
16
+ //
17
+ // The closure is only ever needed at the moment it is COPIED into a bundle, so it
18
+ // is acquired then: into the dlx cache (`ensurePkgDir`, the same machinery
19
+ // `gjsify showcase` already uses for the same package), never into the project.
20
+ // What this does NOT change: the search order in `resolveGtkRuntime()` keeps its
21
+ // four existing candidates, so a project that still installs the package ships
22
+ // exactly as before — it merely stays exposed to the dev-tree failure above until
23
+ // it drops the dependency.
24
+ //
25
+ // EXACT VERSION, no range and no fallback. The addon's `@rpath` is
26
+ // `@loader_path/gtk/lib` — addon and closure are a matched pair published from the
27
+ // same release — so "the nearest published version" would be a guess about GTK ABI
28
+ // that `assertPayloadMatchesArch` cannot check. A CLI version with no published
29
+ // runtime (a dev or `next` build) fails with the reason named, and
30
+ // `GJSIFY_GTK_RUNTIME` is the way past it.
31
+ import { gtkRuntimePackageName, resolveGtkRuntime, } from './app-runtime.js';
32
+ /**
33
+ * Make sure a GTK closure is reachable for `target`, fetching it only if nothing
34
+ * else provides one. Never throws: a ship run with no closure is a PARTIAL result
35
+ * that `stageAppRuntime` reports, not an error (see its doc).
36
+ */
37
+ export async function acquireGtkRuntime(input) {
38
+ const discovery = input.discovery ?? {};
39
+ if (resolveGtkRuntime(input.target, discovery) !== null)
40
+ return { kind: 'present' };
41
+ const name = gtkRuntimePackageName(input.target);
42
+ const spec = `${name}@${input.version}`;
43
+ input.log?.(`${name} is not installed here, and is not meant to be — fetching ${spec} into the cache ` +
44
+ '(about 100 MB, once)');
45
+ let dir;
46
+ try {
47
+ dir = await input.ensure(spec);
48
+ }
49
+ catch (error) {
50
+ return { kind: 'failed', reason: error instanceof Error ? error.message : String(error) };
51
+ }
52
+ // The same completeness probe the staging step applies, so a package that
53
+ // resolved but holds no `gtk/` (an unpopulated checkout, a truncated cache
54
+ // entry) is reported HERE, with its name, instead of as a bare "no GTK closure".
55
+ if (resolveGtkRuntime(input.target, { ...discovery, fetchedGtkRuntimeDir: dir }) === null) {
56
+ return { kind: 'failed', reason: `${spec} was fetched to ${dir} but holds no complete gtk/ closure` };
57
+ }
58
+ return { kind: 'fetched', dir };
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.64.4",
3
+ "version": "0.64.5",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,22 +142,22 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/blueprint": "^0.64.4",
146
- "@gjsify/buffer": "^0.64.4",
147
- "@gjsify/create-app": "^0.64.4",
148
- "@gjsify/node-globals": "^0.64.4",
149
- "@gjsify/node-polyfills": "^0.64.4",
150
- "@gjsify/npm-registry": "^0.64.4",
151
- "@gjsify/resolve-npm": "^0.64.4",
152
- "@gjsify/rolldown-plugin-gjsify": "^0.64.4",
153
- "@gjsify/vite-plugin-blueprint": "^0.64.4",
154
- "@gjsify/rolldown-plugin-pnp": "^0.64.4",
155
- "@gjsify/semver": "^0.64.4",
156
- "@gjsify/tar": "^0.64.4",
157
- "@gjsify/tsc": "^0.64.4",
158
- "@gjsify/utils": "^0.64.4",
159
- "@gjsify/web-polyfills": "^0.64.4",
160
- "@gjsify/workspace": "^0.64.4",
145
+ "@gjsify/blueprint": "^0.64.5",
146
+ "@gjsify/buffer": "^0.64.5",
147
+ "@gjsify/create-app": "^0.64.5",
148
+ "@gjsify/node-globals": "^0.64.5",
149
+ "@gjsify/node-polyfills": "^0.64.5",
150
+ "@gjsify/npm-registry": "^0.64.5",
151
+ "@gjsify/resolve-npm": "^0.64.5",
152
+ "@gjsify/rolldown-plugin-gjsify": "^0.64.5",
153
+ "@gjsify/vite-plugin-blueprint": "^0.64.5",
154
+ "@gjsify/rolldown-plugin-pnp": "^0.64.5",
155
+ "@gjsify/semver": "^0.64.5",
156
+ "@gjsify/tar": "^0.64.5",
157
+ "@gjsify/tsc": "^0.64.5",
158
+ "@gjsify/utils": "^0.64.5",
159
+ "@gjsify/web-polyfills": "^0.64.5",
160
+ "@gjsify/workspace": "^0.64.5",
161
161
  "acorn": "^8.17.0",
162
162
  "cosmiconfig": "^9.0.2",
163
163
  "get-tsconfig": "^4.14.0",
@@ -166,13 +166,13 @@
166
166
  "yargs": "^18.0.0"
167
167
  },
168
168
  "devDependencies": {
169
- "@gjsify/unit": "^0.64.4",
169
+ "@gjsify/unit": "^0.64.5",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "^0.64.4",
175
- "@gjsify/rolldown-native": "^0.64.4"
174
+ "@gjsify/oxfmt-native": "^0.64.5",
175
+ "@gjsify/rolldown-native": "^0.64.5"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {