@gjsify/cli 0.64.6 → 0.64.7

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.
@@ -44,7 +44,7 @@ import { buildFlatpakBundle } from '../utils/ship/flatpak.js';
44
44
  import { iconSizes, resolveAppIcon } from '../utils/ship/icons.js';
45
45
  import { localizeMetadata } from '../utils/ship/localize-metadata.js';
46
46
  import { assertHostCanFinish, assertKindCanPack, assertToolsInstalled, canCarryTicket, configuredFormats, defaultFormatIds, formatIdsFor, resolveFormats, windowsProgramDirName, FORMAT_IDS, FORMATS, } from '../utils/ship/formats.js';
47
- import { assertLayoutSupportsArch, hostLayout, layoutForOs, place, placeStage, resolveLayout, LAYOUT_NAMES, } from '../utils/ship/layout.js';
47
+ import { assertLayoutSupportsArch, hostLayout, layoutForOs, place, placeStage, resolveLayout, windowsGuiLauncherPath, LAYOUT_NAMES, LAYOUTS, } from '../utils/ship/layout.js';
48
48
  import { isNodeRuntimeTarget, resolveNodeRuntime } from '../utils/ship/node-runtime.js';
49
49
  import { isUnder, SHARE } from '../utils/ship/share-dirs.js';
50
50
  import { compileSchemasForPayload, compileSchemasForStage } from '../utils/ship/schemas.js';
@@ -501,7 +501,17 @@ async function assemble(args) {
501
501
  // `packOne` reads it again. That is the price of checking the tree that is
502
502
  // shipped rather than the bytes that happen to be in memory, and it is the
503
503
  // same reason `readStage` exists at all.
504
- assertPayloadMatchesArch(readStage([stageDir], staged), settings.arch);
504
+ //
505
+ // ONE NAMED EXEMPTION: the windows GUI-launcher stub is x86-64 machine code
506
+ // on every windows arch by design (`pe-launcher.ts`'s `MACHINE_AMD64`
507
+ // comment) — it only `exec`s the sibling `.cmd`, which hands off to the
508
+ // arch-correct `node.exe` the stage carries, and Windows-on-ARM runs x64
509
+ // code through Prism for exactly this path. `layout.windows` now emits that
510
+ // stub unconditionally (it used to skip arm64 entirely), so an arm64 stage
511
+ // legitimately carries one x64 PE beside its arm64 payload.
512
+ assertPayloadMatchesArch(readStage([stageDir], staged), settings.arch, {
513
+ exempt: layout === LAYOUTS.windows ? new Set([windowsGuiLauncherPath(settings)]) : undefined,
514
+ });
505
515
  // What the map carried into a layout that has no install step for it.
506
516
  //
507
517
  // NOT a warning about this command's output being wrong — the files are the
@@ -268,7 +268,7 @@ function resolveLocalShowcaseDir(showcase) {
268
268
  */
269
269
  function gtkRuntimeSpecs(cliVersion) {
270
270
  const target = `${process.platform}-${process.arch}`;
271
- const shipped = ['win32-x64', 'darwin-arm64', 'darwin-x64'];
271
+ const shipped = ['win32-x64', 'win32-arm64', 'darwin-arm64', 'darwin-x64'];
272
272
  if (!shipped.includes(target))
273
273
  return [];
274
274
  const name = `@gjsify/gtk-runtime-${target}`;
@@ -10,7 +10,7 @@ import type { StagedFile } from './types.js';
10
10
  * deriving one from the other would make a future Linux GTK bundle silently imply
11
11
  * a bundled Linux interpreter (which `node-runtime.ts` refuses on purpose).
12
12
  */
13
- export declare const GTK_RUNTIME_TARGETS: readonly ["darwin-arm64", "darwin-x64", "win32-x64"];
13
+ export declare const GTK_RUNTIME_TARGETS: readonly ["darwin-arm64", "darwin-x64", "win32-x64", "win32-arm64"];
14
14
  export type GtkRuntimeTarget = (typeof GTK_RUNTIME_TARGETS)[number];
15
15
  /** Is this `<platform>-<arch>` one of the published GTK-closure targets? */
16
16
  export declare function isGtkRuntimeTarget(target: string): target is GtkRuntimeTarget;
@@ -93,7 +93,7 @@ import { resolveInstalledPackage } from '../resolve-npm-package.js';
93
93
  * deriving one from the other would make a future Linux GTK bundle silently imply
94
94
  * a bundled Linux interpreter (which `node-runtime.ts` refuses on purpose).
95
95
  */
96
- export const GTK_RUNTIME_TARGETS = ['darwin-arm64', 'darwin-x64', 'win32-x64'];
96
+ export const GTK_RUNTIME_TARGETS = ['darwin-arm64', 'darwin-x64', 'win32-x64', 'win32-arm64'];
97
97
  /** Is this `<platform>-<arch>` one of the published GTK-closure targets? */
98
98
  export function isGtkRuntimeTarget(target) {
99
99
  return GTK_RUNTIME_TARGETS.includes(target);
@@ -132,23 +132,19 @@ function macosArch(arch, _archIndependent) {
132
132
  /**
133
133
  * `process.arch` → the name Windows and everything that targets it use.
134
134
  *
135
- * ONE ROW, and it is not caution: there is no arm64 GTK CLOSURE to put on it yet.
136
- * `wingtk/gvsbuild` publishes no arm64 assets, which is why ADR 0089 builds that
137
- * prefix from our `gjsify/gvsbuild` fork in CI instead — but what a Windows
138
- * artifact stages is `@gjsify/gtk-runtime-*`, and the arm64 one is not built, so
139
- * there is nothing for a Windows/ARM artifact to load (#1117). `Layout.arches`
140
- * refuses the same value one phase earlier, at stage time, with that reason spelled
141
- * out; this table is the second half — the one that stops a stage assembled by an
142
- * older gjsify from acquiring a label here.
135
+ * TWO ROWS now. `wingtk/gvsbuild` still publishes x64 assets only, but ADR 0089
136
+ * builds the arm64 GTK prefix from our `gjsify/gvsbuild` fork in CI, and
137
+ * `@gjsify/gtk-runtime-win32-arm64` + `@gjsify/node-runtime-win32-arm64` exist —
138
+ * the two packages a Windows/ARM artifact stages (#1117, closed by this row).
143
139
  *
144
- * `x64` maps to itself, and unlike the macOS table that is not a coincidence to be
145
- * caught later: `x64` is the spelling Node's own release archives use
146
- * (`node-v24.20.0-win-x64.zip`), the spelling gvsbuild's assets use
147
- * (`GTK4_Gvsbuild_<v>_x64.zip`), and the spelling a `windows-latest x64` runner
148
- * label uses. `%PROCESSOR_ARCHITECTURE%` says `AMD64` and nothing a user downloads
149
- * is named after it.
140
+ * `x64` and `arm64` map to themselves, and unlike the macOS table that is not a
141
+ * coincidence to be caught later: both are the spelling Node's own release
142
+ * archives use (`node-v24.20.0-win-{x64,arm64}.zip`), the spelling a
143
+ * `windows-11-arm` runner's architecture is, and the spelling `--arch` already
144
+ * accepts everywhere else in this command. `%PROCESSOR_ARCHITECTURE%` says
145
+ * `AMD64`/`ARM64` and nothing a user downloads is named after that spelling.
150
146
  */
151
- const WINDOWS_ARCH = { x64: 'x64' };
147
+ const WINDOWS_ARCH = { x64: 'x64', arm64: 'arm64' };
152
148
  function windowsArch(arch, _archIndependent) {
153
149
  return lookupArch(WINDOWS_ARCH, arch, 'Windows');
154
150
  }
@@ -481,39 +481,40 @@ export const LAYOUTS = {
481
481
  required: true,
482
482
  },
483
483
  metadata: (input) => {
484
+ // Unconditional on `input.arch` — see `pe-launcher.ts`'s `MACHINE_AMD64`
485
+ // comment for why: the stub is x86-64 machine code on EVERY windows arch,
486
+ // because all it does is `exec` the sibling `.cmd`, which hands off to the
487
+ // native `node.exe` the layout staged for that arch. This used to read
488
+ // `input.arch === 'x64'`, which silently emitted NO launcher stub — and
489
+ // therefore no icon and no `.exe` for the MSI shortcut to point at — on
490
+ // arm64, the one arch `arches.only` now admits besides x64.
484
491
  const icon = iconFor(input, 'windows');
485
- return input.arch === 'x64'
486
- ? [
487
- {
488
- path: windowsGuiLauncherPath(input),
489
- mode: 0o755,
490
- source: {
491
- kind: 'bytes',
492
- data: buildGuiLauncher({
493
- logLeaf: windowsLaunchLogLeaf(input),
494
- ...(icon === undefined ? {} : { icon: iconResources(icon) }),
495
- }),
496
- },
492
+ return [
493
+ {
494
+ path: windowsGuiLauncherPath(input),
495
+ mode: 0o755,
496
+ source: {
497
+ kind: 'bytes',
498
+ data: buildGuiLauncher({
499
+ logLeaf: windowsLaunchLogLeaf(input),
500
+ ...(icon === undefined ? {} : { icon: iconResources(icon) }),
501
+ }),
497
502
  },
498
- ]
499
- : [];
503
+ },
504
+ ];
500
505
  },
501
- // ONE, and the blocker is OURS now. `wingtk/gvsbuild` hardcodes
502
- // `self.platform = "x64"` and publishes x64 assets only — but we stopped
503
- // waiting for it: ADR 0089 builds the arm64 GTK prefix from
504
- // `gjsify/gvsbuild` in our own CI and `@gjsify/node-gi` declares
505
- // `win32-arm64` against it (#2003). What is missing is
506
- // `@gjsify/gtk-runtime-win32-arm64`, the closure this layout stages and on
507
- // Windows the only GTK there is, so a Windows/ARM artifact still has
508
- // nothing to load. Adding arm64 to `only` is the PR that builds that
509
- // package, not this one.
506
+ // TWO now. `wingtk/gvsbuild` still hardcodes `self.platform = "x64"` and
507
+ // publishes x64 assets only, but ADR 0089 builds the arm64 GTK prefix from
508
+ // our `gjsify/gvsbuild` fork in CI, `@gjsify/node-gi` ships a win32-arm64
509
+ // prebuild against it (#2003), and `@gjsify/gtk-runtime-win32-arm64` +
510
+ // `@gjsify/node-runtime-win32-arm64` now exist — the two packages this
511
+ // layout stages. Tracked in gjsify/gjsify#1117; remaining gaps (gstsoup,
512
+ // webgl, gamepad, WebView2 on arm64) are `status/open-todos/windows.md`
513
+ // § win32-arm64, not a reason to withhold the architecture.
510
514
  arches: {
511
- only: ['x64'],
512
- why: 'the arm64 GTK closure `@gjsify/gtk-runtime-win32-arm64` does not exist yet — the prefix it ' +
513
- 'would be built from is no longer missing, because ADR 0089 builds it from our `gjsify/gvsbuild` ' +
514
- 'fork in CI and `@gjsify/node-gi` ships a win32-arm64 prebuild against it — so what is left is ' +
515
- 'the runtime package this layout would stage, ordered in `status/open-todos/windows.md` and ' +
516
- 'tracked in gjsify/gjsify#1117',
515
+ only: ['x64', 'arm64'],
516
+ why: 'win32 runs on x64 natively and on arm64 via the MSVC closure ADR 0089 builds — both have a ' +
517
+ '`@gjsify/gtk-runtime-win32-*` and `@gjsify/node-runtime-win32-*` to stage',
517
518
  },
518
519
  },
519
520
  };
@@ -34,8 +34,15 @@
34
34
  // answer:
35
35
  //
36
36
  // 1. `wixl -a x64` sets Component `Attributes` to 256 — `msidbComponent-
37
- // Attributes64bit` — on its own, so `Win64="yes"` is neither written here nor
38
- // needed on either backend (`candle -arch x64` does the same).
37
+ // Attributes64bit` — on its own, so `Win64="yes"` was not written here at
38
+ // first. ADR 0089 (win32-arm64) is what reopened the question: wixl's own
39
+ // source sets that bit only for `Arch.X64`/`Arch.IA64`, never for
40
+ // `Arch.ARM64` (`tools/wixl/builder.vala`), so an unmodified `-a arm64`
41
+ // build would have shipped a 32-bit component tree. `Win64` is now written
42
+ // explicitly for every non-x86 arch, on both backends — a Component whose
43
+ // bitness depends on which backend's inference happens to cover it is
44
+ // exactly the kind of divergence this file's whole design (one `.wxs`, two
45
+ // readers) exists to prevent.
39
46
  // 2. An ABSOLUTE `Source=` resolves — and this file writes RELATIVE ones anyway,
40
47
  // which is the measurement changing a decision rather than confirming it. The
41
48
  // SAME document is compiled twice, on two machines: `wixl` here and WiX v3 on
@@ -240,6 +247,16 @@ function directoryTree(files) {
240
247
  */
241
248
  export function renderWxs(input) {
242
249
  const { settings, programDirName, archLabel } = input;
250
+ // EXPLICIT, not inferred — measured against msitools' own source
251
+ // (tools/wixl/builder.vala, `compileComponent`): `wixl -a <arch>` sets the
252
+ // `msidbComponentAttributes64bit` bit itself ONLY for `Arch.X64` and
253
+ // `Arch.IA64`; `Arch.ARM64` falls through and the component stays 32-bit
254
+ // unless `Win64="yes"` is written here. `candle -arch arm64` (WiX v3.14,
255
+ // the win32 backend) is not re-verified the same way, so this file no
256
+ // longer trusts EITHER backend's default for a non-x86 architecture — a
257
+ // Component silently installing into `%ProgramFiles(x86)%` is the wrong
258
+ // kind of thing to find out by running the artifact.
259
+ const win64 = archLabel !== 'x86';
243
260
  const productVersion = msiProductVersion(settings.version);
244
261
  // `Name <email>` is `PackSettings.maintainer`'s shape; ARP shows `Publisher`
245
262
  // to a human, and an address in that field reads as a mistake.
@@ -298,7 +315,7 @@ export function renderWxs(input) {
298
315
  // not the authority for it — a Windows packing host would otherwise
299
316
  // split a name containing a backslash.
300
317
  const name = xmlEscape(posix.basename(file.path));
301
- lines.push(`${indent}<Component Id="${componentId}" Guid="${guid}">`);
318
+ lines.push(`${indent}<Component Id="${componentId}" Guid="${guid}" Win64="${win64 ? 'yes' : 'no'}">`);
302
319
  // THE SHORTCUT LIVES IN THE LAUNCHER'S OWN COMPONENT, nested in its
303
320
  // `<File>` and advertised. The obvious alternative — a second component
304
321
  // under `ProgramMenuFolder` whose key path is an HKCU registry value —
@@ -1,5 +1,5 @@
1
1
  /** The targets a bundled interpreter is published for. */
2
- export declare const NODE_RUNTIME_TARGETS: readonly ["darwin-arm64", "darwin-x64", "win32-x64"];
2
+ export declare const NODE_RUNTIME_TARGETS: readonly ["darwin-arm64", "darwin-x64", "win32-x64", "win32-arm64"];
3
3
  export type NodeRuntimeTarget = (typeof NODE_RUNTIME_TARGETS)[number];
4
4
  /** Is this `<platform>-<arch>` one of the published targets? */
5
5
  export declare function isNodeRuntimeTarget(target: string): target is NodeRuntimeTarget;
@@ -45,7 +45,7 @@ import { existsSync } from 'node:fs';
45
45
  import { dirname, join } from 'node:path';
46
46
  import { resolveInstalledPackage } from '../resolve-npm-package.js';
47
47
  /** The targets a bundled interpreter is published for. */
48
- export const NODE_RUNTIME_TARGETS = ['darwin-arm64', 'darwin-x64', 'win32-x64'];
48
+ export const NODE_RUNTIME_TARGETS = ['darwin-arm64', 'darwin-x64', 'win32-x64', 'win32-arm64'];
49
49
  /** Is this `<platform>-<arch>` one of the published targets? */
50
50
  export function isNodeRuntimeTarget(target) {
51
51
  return NODE_RUNTIME_TARGETS.includes(target);
@@ -275,7 +275,9 @@ export declare function readBinaryArch(data: Uint8Array): string | null;
275
275
  * JavaScript and ADR 0024 § A1 turns it into a design commitment — so a host
276
276
  * comparison would refuse the very case the split exists to allow.
277
277
  */
278
- export declare function assertPayloadMatchesArch(payload: readonly PayloadEntry[], arch: string): void;
278
+ export declare function assertPayloadMatchesArch(payload: readonly PayloadEntry[], arch: string, options?: {
279
+ exempt?: ReadonlySet<string>;
280
+ }): void;
279
281
  /**
280
282
  * Which executable format these bytes are, or `null` for anything else.
281
283
  *
@@ -767,11 +767,21 @@ export function readBinaryArch(data) {
767
767
  * JavaScript and ADR 0024 § A1 turns it into a design commitment — so a host
768
768
  * comparison would refuse the very case the split exists to allow.
769
769
  */
770
- export function assertPayloadMatchesArch(payload, arch) {
770
+ export function assertPayloadMatchesArch(payload, arch, options = {}) {
771
771
  for (const entry of payload) {
772
772
  const found = readBinaryArch(entry.data);
773
773
  if (found === null || found === arch)
774
774
  continue;
775
+ // ONE NAMED EXCEPTION, not a relaxation of the rule: the windows layout's
776
+ // GUI-launcher stub (`pe-launcher.ts`) is x86-64 machine code on every
777
+ // windows arch BY DESIGN — it only `exec`s the sibling `.cmd`, which hands
778
+ // off to the arch-correct `node.exe` the stage actually carries, and
779
+ // Windows-on-ARM runs x64 code through Prism for exactly this reason. The
780
+ // caller names that one path; nothing else may match it, so a real
781
+ // cross-arch payload (the #1354/0.41.0 incident this function exists for)
782
+ // still throws.
783
+ if (options.exempt?.has(entry.path))
784
+ continue;
775
785
  throw new Error(`gjsify ship: the payload is ${found}, but the package would be labelled ${arch} — ` +
776
786
  `${entry.path} is built for ${found}.\n` +
777
787
  ' `--arch` names the architecture the PAYLOAD was built for; it does not cross-compile ' +
@@ -76,7 +76,17 @@ import { RT_GROUP_ICON, RT_ICON } from './ico.js';
76
76
  export const PE_SUBSYSTEM_GUI = 2;
77
77
  /** Where `Subsystem` lands in the emitted image: `e_lfanew` + 4 + 20 + 68. */
78
78
  export const PE_SUBSYSTEM_OFFSET = 0x80 + 4 + 20 + 68;
79
- /** `IMAGE_FILE_HEADER.Machine` for x86-64. The only architecture the windows layout has. */
79
+ /**
80
+ * `IMAGE_FILE_HEADER.Machine` for x86-64 — ALWAYS, even when `windows-dir` is staged for
81
+ * `--arch arm64` (ADR 0089). This stub does nothing an arm64 build needs a native image for: it
82
+ * `exec`s the sibling `.cmd`, which Windows resolves to the NATIVE `cmd.exe` either way (no path
83
+ * is given, so it comes off `System32`, and win32-on-arm64 keeps a native `cmd.exe` beside the
84
+ * emulated one), and that `.cmd` starts the staged `node.exe` — the arm64 build's OWN interpreter,
85
+ * never this stub's architecture. So the one thing a second, ARM64-native stub would buy is
86
+ * skipping Prism's x86-64 emulation for the ~550 bytes THIS image executes before handing off —
87
+ * a second machine-code emitter, a second `.rsrc` builder and a second `pei-aarch64` reader in
88
+ * `pe-launcher.spec.ts`, to save microseconds on a path that ends in a process handoff anyway.
89
+ */
80
90
  const MACHINE_AMD64 = 0x8664;
81
91
  /** Where the PE signature starts. MSVC emits 0x78; nothing reads the value except through `e_lfanew`. */
82
92
  const PE_OFFSET = 0x80;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.64.6",
3
+ "version": "0.64.7",
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.6",
146
- "@gjsify/buffer": "^0.64.6",
147
- "@gjsify/create-app": "^0.64.6",
148
- "@gjsify/node-globals": "^0.64.6",
149
- "@gjsify/node-polyfills": "^0.64.6",
150
- "@gjsify/npm-registry": "^0.64.6",
151
- "@gjsify/resolve-npm": "^0.64.6",
152
- "@gjsify/rolldown-plugin-gjsify": "^0.64.6",
153
- "@gjsify/vite-plugin-blueprint": "^0.64.6",
154
- "@gjsify/rolldown-plugin-pnp": "^0.64.6",
155
- "@gjsify/semver": "^0.64.6",
156
- "@gjsify/tar": "^0.64.6",
157
- "@gjsify/tsc": "^0.64.6",
158
- "@gjsify/utils": "^0.64.6",
159
- "@gjsify/web-polyfills": "^0.64.6",
160
- "@gjsify/workspace": "^0.64.6",
145
+ "@gjsify/blueprint": "^0.64.7",
146
+ "@gjsify/buffer": "^0.64.7",
147
+ "@gjsify/create-app": "^0.64.7",
148
+ "@gjsify/node-globals": "^0.64.7",
149
+ "@gjsify/node-polyfills": "^0.64.7",
150
+ "@gjsify/npm-registry": "^0.64.7",
151
+ "@gjsify/resolve-npm": "^0.64.7",
152
+ "@gjsify/rolldown-plugin-gjsify": "^0.64.7",
153
+ "@gjsify/vite-plugin-blueprint": "^0.64.7",
154
+ "@gjsify/rolldown-plugin-pnp": "^0.64.7",
155
+ "@gjsify/semver": "^0.64.7",
156
+ "@gjsify/tar": "^0.64.7",
157
+ "@gjsify/tsc": "^0.64.7",
158
+ "@gjsify/utils": "^0.64.7",
159
+ "@gjsify/web-polyfills": "^0.64.7",
160
+ "@gjsify/workspace": "^0.64.7",
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.6",
169
+ "@gjsify/unit": "^0.64.7",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "^0.64.6",
175
- "@gjsify/rolldown-native": "^0.64.6"
174
+ "@gjsify/oxfmt-native": "^0.64.7",
175
+ "@gjsify/rolldown-native": "^0.64.7"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {