@gjsify/cli 0.64.10 → 0.64.11

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.
@@ -62,7 +62,7 @@ system.exit(await runDevtoolsCli(system.programArgs));
62
62
  * `instances` — so anything a rig does by hand over `gdbus` has a scriptable spelling.
63
63
  */
64
64
  export const devtoolsCommand = {
65
- command: 'devtools',
65
+ command: 'devtools <operation> [operands..]',
66
66
  description: 'Drive a running devtools-enabled app from a script: capture a screenshot to a file, find a widget, ' +
67
67
  'activate it, send a key. Peer of `gjsify debug`, which speaks MCP for an AI agent.',
68
68
  builder: (yargs) => yargs
@@ -1,5 +1,6 @@
1
1
  import type { Command } from '../types/index.js';
2
2
  import { type ParsedSpec } from '../utils/parse-spec.js';
3
+ import { type PlatformTarget } from '../utils/platform-check.js';
3
4
  interface DlxOptions {
4
5
  spec: string;
5
6
  binOrArg?: string;
@@ -31,6 +32,12 @@ export interface EnsureOpts {
31
32
  * Part of the cache key, so the gjs and node trees never share an entry.
32
33
  */
33
34
  extraSpecs?: readonly string[];
35
+ /**
36
+ * Install the tree for this platform rather than the host's: a package that is
37
+ * copied into another machine's artifact (`gjsify ship` fetching a darwin GTK
38
+ * closure on Linux). Part of the cache key.
39
+ */
40
+ platform?: PlatformTarget;
34
41
  }
35
42
  /**
36
43
  * Resolve (installing + caching when remote) the on-disk directory of a package
@@ -16,6 +16,7 @@ import { resolveGjsEntry } from '../utils/resolve-gjs-entry.js';
16
16
  import { doubleDashArgs } from '../utils/double-dash-args.js';
17
17
  import { cacheDirFor, createCacheKey, getValidCachedPkg, makePrepareDir, resolveInstalledPkgDir, symlinkSwap, } from '../utils/dlx-cache.js';
18
18
  import { installPackages, makeProgressReporter } from '../utils/install-backend.js';
19
+ import { describePlatformTarget } from '../utils/platform-check.js';
19
20
  export const dlxCommand = {
20
21
  command: 'dlx <spec> [binOrArg] [extraArgs..]',
21
22
  description: 'Run the GJS bundle of an npm-published package without installing it locally.',
@@ -118,7 +119,10 @@ export async function ensurePkgDir(parsed, opts) {
118
119
  return { pkgDir: parsed.path, cachedPkgName: null };
119
120
  }
120
121
  const specs = [parsed.spec, ...(opts.extraSpecs ?? [])];
121
- const cacheKey = createCacheKey({ packages: specs });
122
+ const cacheKey = createCacheKey({
123
+ packages: specs,
124
+ ...(opts.platform ? { platform: describePlatformTarget(opts.platform) } : {}),
125
+ });
122
126
  const cacheDir = cacheDirFor(cacheKey);
123
127
  const cached = opts.cacheMaxAge > 0 ? getValidCachedPkg(cacheDir, opts.cacheMaxAge) : undefined;
124
128
  if (cached) {
@@ -145,6 +149,7 @@ export async function ensurePkgDir(parsed, opts) {
145
149
  lockfile: true,
146
150
  frozen: opts.frozen,
147
151
  progress,
152
+ ...(opts.platform ? { platform: opts.platform } : {}),
148
153
  });
149
154
  const liveTarget = symlinkSwap(cacheDir, prepareDir);
150
155
  return {
@@ -48,7 +48,7 @@ import { buildPublishHeaders, escapePackageName } from '../utils/publish-headers
48
48
  import { packWorkspace } from './pack.js';
49
49
  import { getNpmTrustedToken, hasGithubOidcEnv, OidcExchangeError, OidcUnavailableError } from '../utils/npm-oidc.js';
50
50
  import { diagnose404, is404DiagnosticCandidate } from '../utils/publish-diagnose.js';
51
- import { DEFAULT_VERIFY_BUDGET_MS, formatUnconfirmedPublish, probePackageName, verifyPublishedVersion, } from '../utils/publish-readback.js';
51
+ import { budgetForPayload, DEFAULT_VERIFY_BUDGET_MS, formatUnconfirmedPublish, probePackageName, verifyPublishedVersion, } from '../utils/publish-readback.js';
52
52
  import { collectWorkspacePins, REQUIRED_BLOCK } from '../utils/publish-pins.js';
53
53
  import { appendLedger, DEFAULT_HOLD_WAIT_MS, findUnavailablePins, holdsPublish, markHeldPublished, pendingEntries, readLedger, recordUnserved, renderLedgerSummary, settleUnserved, summariseReasons, } from '../utils/publish-hold.js';
54
54
  import { describeClass, statusTokenFromEnv } from '../utils/publish-status.js';
@@ -584,9 +584,10 @@ export async function publishWorkspace(input) {
584
584
  * `publish-unconfirmed`. Same origin, same credential, one request later.
585
585
  */
586
586
  const readBack = async () => {
587
- const budgetMs = input.verifyBudgetMs ?? DEFAULT_VERIFY_BUDGET_MS;
588
- if (budgetMs <= 0)
587
+ const configuredMs = input.verifyBudgetMs ?? DEFAULT_VERIFY_BUDGET_MS;
588
+ if (configuredMs <= 0)
589
589
  return undefined;
590
+ const budgetMs = budgetForPayload(configuredMs, bodyStr.length);
590
591
  return verifyPublishedVersion({
591
592
  registry: registryClean,
592
593
  name: packed.name,
@@ -786,12 +786,19 @@ async function resolveCarriedRuntime(layout, settings, projectDir) {
786
786
  return empty;
787
787
  // The closure is fetched, not installed: a project that lists it puts a second
788
788
  // GLib next to Homebrew's in its dev tree (see `gtk-runtime-acquire.ts`).
789
+ // Fetched FOR THE TARGET: on a Linux host the package's own `os`/`cpu` gate
790
+ // refused it as EBADPLATFORM, and every macOS and Windows artifact assembled
791
+ // on Linux shipped without GTK, starting only where Homebrew happened to have it.
789
792
  const acquired = await acquireGtkRuntime({
790
793
  target,
791
794
  version: cliVersion(),
792
795
  discovery: { cwd: projectDir },
793
- ensure: async (spec) => (await ensurePkgDir(parseSpec(spec), { verbose: false, cacheMaxAge: DLX_CACHE_MAX_AGE_MIN, frozen: false }))
794
- .pkgDir,
796
+ ensure: async (spec) => (await ensurePkgDir(parseSpec(spec), {
797
+ verbose: false,
798
+ cacheMaxAge: DLX_CACHE_MAX_AGE_MIN,
799
+ frozen: false,
800
+ platform: { os: layout.os, cpu: settings.arch },
801
+ })).pkgDir,
795
802
  log: (message) => console.log(`${LOG} ${message}`),
796
803
  });
797
804
  return stageAppRuntime({
@@ -1,6 +1,12 @@
1
1
  interface CacheKeyOpts {
2
2
  packages: string[];
3
3
  registries?: Record<string, string>;
4
+ /**
5
+ * The platform the tree was installed FOR (`darwin-arm64`), when not the host's.
6
+ * The same specs filter to a different tree per platform. Absent, the key is
7
+ * the one it always was, so existing cache entries stay valid.
8
+ */
9
+ platform?: string;
4
10
  }
5
11
  /** Stable, sorted JSON hash of inputs. */
6
12
  export declare function createCacheKey(opts: CacheKeyOpts): string;
@@ -19,7 +19,7 @@ function lexCompare(a, b) {
19
19
  export function createCacheKey(opts) {
20
20
  const sortedPkgs = [...opts.packages].sort(lexCompare);
21
21
  const sortedRegs = Object.entries(opts.registries ?? {}).sort(([a], [b]) => lexCompare(a, b));
22
- const payload = JSON.stringify([sortedPkgs, sortedRegs]);
22
+ const payload = JSON.stringify(opts.platform === undefined ? [sortedPkgs, sortedRegs] : [sortedPkgs, sortedRegs, opts.platform]);
23
23
  return createHash('sha256').update(payload).digest('hex');
24
24
  }
25
25
  /** $XDG_CACHE_HOME/gjsify/dlx — created if missing. */
@@ -143,8 +143,9 @@ async function installPackagesNativeLocked(opts, npmrc, log) {
143
143
  // The triple this install materialises FOR. Normally the running host;
144
144
  // `--os/--cpu/--libc` (npm's config keys, read from the environment by
145
145
  // `resolveHostPlatform`) override it, which is what makes the darwin/win32/musl
146
- // branches reviewable from one machine.
147
- const target = resolveHostPlatform();
146
+ // branches reviewable from one machine. A caller that installs for another
147
+ // machine's artifact (`opts.platform`) wins over both.
148
+ const target = opts.platform ?? resolveHostPlatform();
148
149
  const force = readPlatformForce(process.env);
149
150
  log('install: platform target %s%s', describePlatformTarget(target), force ? ' (--force: checks bypassed)' : '');
150
151
  let nodes;
@@ -1,4 +1,5 @@
1
1
  import type { ProgressReporter } from './install-progress.js';
2
+ import type { PlatformTarget } from './platform-check.js';
2
3
  export type { ProgressEvent, ProgressPhase, ProgressReporter } from './install-progress.js';
3
4
  export { makeProgressReporter } from './install-progress.js';
4
5
  export interface InstallOptions {
@@ -79,6 +80,15 @@ export interface InstallOptions {
79
80
  * and the warning falls back to bare ranges.
80
81
  */
81
82
  specOrigins?: Map<string, string[]>;
83
+ /**
84
+ * The `os`/`cpu`/`libc` triple to install FOR, instead of the host's (and of
85
+ * `npm_config_os`/`_cpu`/`_libc`). For a tree that is copied into an artifact
86
+ * for another machine and never run here — `gjsify ship` assembling a darwin or
87
+ * win32 bundle on Linux fetches that target's GTK closure, whose `os`/`cpu`
88
+ * gate would otherwise refuse it as EBADPLATFORM (ADR 0024 § A1). The npm
89
+ * backend passes it on as `--os`/`--cpu`/`--libc`.
90
+ */
91
+ platform?: PlatformTarget;
82
92
  }
83
93
  export interface InstallResult {
84
94
  /** Requested top-level packages with the version each resolved to. Empty for
@@ -24,7 +24,7 @@ export async function installPackages(opts) {
24
24
  const installed = await installPackagesNative(opts);
25
25
  return { installed };
26
26
  }
27
- async function installViaNpm({ prefix, specs, verbose, registry }) {
27
+ async function installViaNpm({ prefix, specs, verbose, registry, platform }) {
28
28
  if (specs.length === 0) {
29
29
  throw new Error('installPackages: empty specs list');
30
30
  }
@@ -42,6 +42,8 @@ async function installViaNpm({ prefix, specs, verbose, registry }) {
42
42
  '--prefix',
43
43
  prefix,
44
44
  ...(verbose ? ['--loglevel', 'verbose'] : ['--loglevel', 'warn']),
45
+ ...(platform ? ['--os', platform.os, '--cpu', platform.cpu] : []),
46
+ ...(platform?.libc ? ['--libc', platform.libc] : []),
45
47
  ...specs,
46
48
  ];
47
49
  // Through `spawnToCompletion` because a bare `spawn('npm', …)` is ENOENT on
@@ -146,6 +146,8 @@ export declare function probeTimeoutFor(configuredMs: number, remainingMs: numbe
146
146
  * state is what the `recorded-not-served` verdict names.
147
147
  */
148
148
  export declare const DEFAULT_VERIFY_BUDGET_MS = 600000;
149
+ /** The read-back budget for a payload: `configuredMs`, longer if it is the default and the upload is large. */
150
+ export declare function budgetForPayload(configuredMs: number, payloadBytes: number): number;
149
151
  /**
150
152
  * Poll the registry until `name@version` resolves, or the budget runs out, then
151
153
  * say WHICH of the three states it found (see {@link ReadbackVerdict}).
@@ -138,6 +138,26 @@ export function probeTimeoutFor(configuredMs, remainingMs) {
138
138
  * state is what the `recorded-not-served` verdict names.
139
139
  */
140
140
  export const DEFAULT_VERIFY_BUDGET_MS = 600_000;
141
+ /**
142
+ * Payload size above which the DEFAULT window starts to grow, and the growth per MiB.
143
+ *
144
+ * v0.64.9, run 40145205912: `@gjsify/gtk-runtime-win32-arm64` PUT 69.7 MB, got a 202,
145
+ * and the read-back gave up after 600 s with `not-published`; the registry's `time`
146
+ * map recorded the version 20 s later. The lag scales with what npm has to ingest,
147
+ * and the 600 s ceiling was measured on packages of a few MB. So the window grows by
148
+ * 10 s per MiB past 32 MiB (that package: ~945 s). Only the PATIENT default grows —
149
+ * an explicit short budget (the sweep's `--verify-timeout 5`) is a caller's decision.
150
+ * An upload npm accepted but never recorded still ends in `not-published`.
151
+ */
152
+ const LARGE_PAYLOAD_BYTES = 32 * 1024 * 1024;
153
+ const EXTRA_BUDGET_PER_MIB_MS = 10_000;
154
+ /** The read-back budget for a payload: `configuredMs`, longer if it is the default and the upload is large. */
155
+ export function budgetForPayload(configuredMs, payloadBytes) {
156
+ if (configuredMs < DEFAULT_VERIFY_BUDGET_MS || payloadBytes <= LARGE_PAYLOAD_BYTES)
157
+ return configuredMs;
158
+ const extraMiB = (payloadBytes - LARGE_PAYLOAD_BYTES) / (1024 * 1024);
159
+ return configuredMs + Math.ceil(extraMiB) * EXTRA_BUDGET_PER_MIB_MS;
160
+ }
141
161
  /**
142
162
  * Make one probe's URL uncacheable, by giving it a key no cache holds.
143
163
  *
@@ -40,6 +40,12 @@ export async function acquireGtkRuntime(input) {
40
40
  return { kind: 'present' };
41
41
  const name = gtkRuntimePackageName(input.target);
42
42
  const spec = `${name}@${input.version}`;
43
+ // An offline build, or a suite that must not download 100 MB per run, stages what is
44
+ // reachable and nothing more. The e2e suites that assert "a project without the packages
45
+ // stages no runtime" held only while the fetch failed on Linux for every foreign target.
46
+ if ((discovery.env ?? process.env)['GJSIFY_GTK_RUNTIME_FETCH'] === '0') {
47
+ return { kind: 'failed', reason: `GJSIFY_GTK_RUNTIME_FETCH=0 turned off the fetch of ${spec}` };
48
+ }
43
49
  input.log?.(`${name} is not installed here, and is not meant to be — fetching ${spec} into the cache ` +
44
50
  '(about 100 MB, once)');
45
51
  let dir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.64.10",
3
+ "version": "0.64.11",
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.10",
146
- "@gjsify/buffer": "^0.64.10",
147
- "@gjsify/create-app": "^0.64.10",
148
- "@gjsify/node-globals": "^0.64.10",
149
- "@gjsify/node-polyfills": "^0.64.10",
150
- "@gjsify/npm-registry": "^0.64.10",
151
- "@gjsify/resolve-npm": "^0.64.10",
152
- "@gjsify/rolldown-plugin-gjsify": "^0.64.10",
153
- "@gjsify/vite-plugin-blueprint": "^0.64.10",
154
- "@gjsify/rolldown-plugin-pnp": "^0.64.10",
155
- "@gjsify/semver": "^0.64.10",
156
- "@gjsify/tar": "^0.64.10",
157
- "@gjsify/tsc": "^0.64.10",
158
- "@gjsify/utils": "^0.64.10",
159
- "@gjsify/web-polyfills": "^0.64.10",
160
- "@gjsify/workspace": "^0.64.10",
145
+ "@gjsify/blueprint": "^0.64.11",
146
+ "@gjsify/buffer": "^0.64.11",
147
+ "@gjsify/create-app": "^0.64.11",
148
+ "@gjsify/node-globals": "^0.64.11",
149
+ "@gjsify/node-polyfills": "^0.64.11",
150
+ "@gjsify/npm-registry": "^0.64.11",
151
+ "@gjsify/resolve-npm": "^0.64.11",
152
+ "@gjsify/rolldown-plugin-gjsify": "^0.64.11",
153
+ "@gjsify/vite-plugin-blueprint": "^0.64.11",
154
+ "@gjsify/rolldown-plugin-pnp": "^0.64.11",
155
+ "@gjsify/semver": "^0.64.11",
156
+ "@gjsify/tar": "^0.64.11",
157
+ "@gjsify/tsc": "^0.64.11",
158
+ "@gjsify/utils": "^0.64.11",
159
+ "@gjsify/web-polyfills": "^0.64.11",
160
+ "@gjsify/workspace": "^0.64.11",
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.10",
169
+ "@gjsify/unit": "^0.64.11",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "^0.64.10",
175
- "@gjsify/rolldown-native": "^0.64.10"
174
+ "@gjsify/oxfmt-native": "^0.64.11",
175
+ "@gjsify/rolldown-native": "^0.64.11"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {