@gjsify/cli 0.51.1 → 0.53.0

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.
Files changed (126) hide show
  1. package/dist/cli.gjs.mjs +539 -493
  2. package/lib/actions/build.d.ts +22 -0
  3. package/lib/actions/build.js +37 -9
  4. package/lib/bundler-pick.d.ts +37 -1
  5. package/lib/bundler-pick.js +108 -17
  6. package/lib/cli-app.js +7 -2
  7. package/lib/commands/affected-classify.d.ts +7 -1
  8. package/lib/commands/affected-classify.js +66 -9
  9. package/lib/commands/affected-script-refs.d.ts +20 -0
  10. package/lib/commands/affected-script-refs.js +218 -0
  11. package/lib/commands/affected.js +24 -2
  12. package/lib/commands/env.d.ts +31 -0
  13. package/lib/commands/env.js +114 -0
  14. package/lib/commands/exec.d.ts +10 -0
  15. package/lib/commands/exec.js +208 -0
  16. package/lib/commands/flatpak/diff.js +8 -2
  17. package/lib/commands/flatpak/init.js +4 -1
  18. package/lib/commands/flatpak/sources.js +4 -3
  19. package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
  20. package/lib/commands/flatpak/sync-flathub.js +149 -7
  21. package/lib/commands/flatpak/utils.d.ts +10 -0
  22. package/lib/commands/flatpak/utils.js +10 -0
  23. package/lib/commands/foreach.js +5 -97
  24. package/lib/commands/format.js +26 -6
  25. package/lib/commands/index.d.ts +4 -0
  26. package/lib/commands/index.js +4 -0
  27. package/lib/commands/install.d.ts +2 -2
  28. package/lib/commands/install.js +81 -5
  29. package/lib/commands/link.d.ts +13 -0
  30. package/lib/commands/link.js +243 -0
  31. package/lib/commands/onboard.js +18 -2
  32. package/lib/commands/publish.d.ts +34 -0
  33. package/lib/commands/publish.js +153 -8
  34. package/lib/commands/self-update.js +2 -1
  35. package/lib/commands/ship.js +19 -1
  36. package/lib/commands/test.d.ts +18 -9
  37. package/lib/commands/test.js +56 -11
  38. package/lib/commands/tsc.js +4 -4
  39. package/lib/commands/upgrade.d.ts +15 -0
  40. package/lib/commands/upgrade.js +76 -2
  41. package/lib/commands/webext/index.d.ts +2 -0
  42. package/lib/commands/webext/index.js +15 -0
  43. package/lib/commands/webext/subcommands.d.ts +16 -0
  44. package/lib/commands/webext/subcommands.js +204 -0
  45. package/lib/types/config-data.d.ts +2 -2
  46. package/lib/utils/bin-shim.d.ts +8 -2
  47. package/lib/utils/bin-shim.js +6 -5
  48. package/lib/utils/bundle-inputs.d.ts +84 -0
  49. package/lib/utils/bundle-inputs.js +268 -0
  50. package/lib/utils/check-system-deps.d.ts +5 -19
  51. package/lib/utils/check-system-deps.js +31 -94
  52. package/lib/utils/dev-link.d.ts +225 -0
  53. package/lib/utils/dev-link.js +684 -0
  54. package/lib/utils/exec-bin.d.ts +137 -0
  55. package/lib/utils/exec-bin.js +321 -0
  56. package/lib/utils/gi-runtime-paths.js +3 -2
  57. package/lib/utils/gitignore.d.ts +15 -0
  58. package/lib/utils/gitignore.js +178 -0
  59. package/lib/utils/gjsify-shim.d.ts +47 -0
  60. package/lib/utils/gjsify-shim.js +97 -8
  61. package/lib/utils/hang-watchdog.d.ts +95 -0
  62. package/lib/utils/hang-watchdog.js +194 -0
  63. package/lib/utils/install-backend-native.d.ts +33 -1
  64. package/lib/utils/install-backend-native.js +184 -20
  65. package/lib/utils/install-extraneous.d.ts +90 -0
  66. package/lib/utils/install-extraneous.js +120 -0
  67. package/lib/utils/install-lock.js +4 -25
  68. package/lib/utils/install-tarball-cache.d.ts +37 -9
  69. package/lib/utils/install-tarball-cache.js +59 -14
  70. package/lib/utils/native-skip-scan.d.ts +20 -0
  71. package/lib/utils/native-skip-scan.js +343 -0
  72. package/lib/utils/node-script.js +1 -1
  73. package/lib/utils/normalize-bundler-options.js +11 -0
  74. package/lib/utils/oxc-resolve.js +42 -3
  75. package/lib/utils/process-table.d.ts +22 -0
  76. package/lib/utils/process-table.js +188 -0
  77. package/lib/utils/prune-prefix.js +1 -1
  78. package/lib/utils/publish-pins.d.ts +38 -0
  79. package/lib/utils/publish-pins.js +47 -0
  80. package/lib/utils/publish-readback.d.ts +103 -17
  81. package/lib/utils/publish-readback.js +326 -47
  82. package/lib/utils/run-gjs.d.ts +15 -1
  83. package/lib/utils/run-gjs.js +98 -13
  84. package/lib/utils/run-node.d.ts +2 -0
  85. package/lib/utils/run-node.js +2 -1
  86. package/lib/utils/runtimes.js +14 -13
  87. package/lib/utils/ship/gzip.js +1 -1
  88. package/lib/utils/ship/icns.d.ts +14 -0
  89. package/lib/utils/ship/icns.js +83 -0
  90. package/lib/utils/ship/ico.d.ts +25 -0
  91. package/lib/utils/ship/ico.js +120 -0
  92. package/lib/utils/ship/icons.d.ts +197 -0
  93. package/lib/utils/ship/icons.js +399 -0
  94. package/lib/utils/ship/launcher.js +16 -1
  95. package/lib/utils/ship/layout.d.ts +32 -0
  96. package/lib/utils/ship/layout.js +112 -38
  97. package/lib/utils/ship/msi.d.ts +1 -1
  98. package/lib/utils/ship/msi.js +30 -5
  99. package/lib/utils/ship/pe-launcher.d.ts +18 -0
  100. package/lib/utils/ship/pe-launcher.js +125 -4
  101. package/lib/utils/ship/plan.d.ts +1 -34
  102. package/lib/utils/ship/plan.js +7 -56
  103. package/lib/utils/ship/plist.d.ts +20 -10
  104. package/lib/utils/ship/plist.js +29 -12
  105. package/lib/utils/ship/settings.js +3 -2
  106. package/lib/utils/spawn.d.ts +13 -0
  107. package/lib/utils/spawn.js +10 -5
  108. package/lib/utils/watch-loop.d.ts +15 -1
  109. package/lib/utils/watch-loop.js +9 -5
  110. package/lib/utils/webext/build.d.ts +42 -0
  111. package/lib/utils/webext/build.js +120 -0
  112. package/lib/utils/webext/config.d.ts +72 -0
  113. package/lib/utils/webext/config.js +234 -0
  114. package/lib/utils/webext/icons.d.ts +24 -0
  115. package/lib/utils/webext/icons.js +83 -0
  116. package/lib/utils/webext/launch.d.ts +32 -0
  117. package/lib/utils/webext/launch.js +77 -0
  118. package/lib/utils/webext/manifest.d.ts +53 -0
  119. package/lib/utils/webext/manifest.js +219 -0
  120. package/lib/utils/webext/pages.d.ts +26 -0
  121. package/lib/utils/webext/pages.js +61 -0
  122. package/lib/utils/webext/targets.d.ts +13 -0
  123. package/lib/utils/webext/targets.js +36 -0
  124. package/lib/utils/win32-command.d.ts +17 -0
  125. package/lib/utils/win32-command.js +21 -6
  126. package/package.json +20 -20
@@ -6,13 +6,14 @@
6
6
  // runtime→build-target mapping and `availableRuntimes()` live in ONE place instead
7
7
  // of a per-example copy.
8
8
  //
9
- // Intentionally PURE (only `node:child_process` and the side-effect-free
10
- // `@gjsify/rolldown-plugin-gjsify/runtime` host detector, no `gi://` or native
11
- // imports) so an external example can deep-import it from the installed CLI
12
- // (`@gjsify/cli/lib/utils/runtimes.js`) without pulling in the whole bundler — the
13
- // same deep-import precedent as `@gjsify/cli/lib/utils/run-gjs.js`.
14
- import { execFileSync } from 'node:child_process';
9
+ // Intentionally PURE (only `node:child_process`, `./spawn.js`'s Windows command
10
+ // rewrite and the side-effect-free `@gjsify/rolldown-plugin-gjsify/runtime` host
11
+ // detector, no `gi://` or native imports) so an external example can deep-import
12
+ // it from the installed CLI (`@gjsify/cli/lib/utils/runtimes.js`) without pulling
13
+ // in the whole bundler — the same deep-import precedent as `@gjsify/cli/lib/utils/run-gjs.js`.
14
+ import { spawnSync } from 'node:child_process';
15
15
  import { hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
16
+ import { toSpawnable } from './spawn.js';
16
17
  /** All runtimes, in canonical order (gjs first — it is the reference). */
17
18
  export const EXAMPLE_RUNTIMES = ['gjs', 'node', 'bun', 'deno'];
18
19
  /**
@@ -57,13 +58,13 @@ export function requiresGjsSystemDeps(runtime) {
57
58
  }
58
59
  /** Whether `<probe> --version` succeeds (i.e. the runtime binary is on PATH). */
59
60
  export function isRuntimeOnPath(probe) {
60
- try {
61
- execFileSync(probe, ['--version'], { stdio: 'ignore', timeout: 15000 });
62
- return true;
63
- }
64
- catch {
65
- return false;
66
- }
61
+ const inv = toSpawnable(probe, ['--version']);
62
+ const r = spawnSync(inv.cmd, inv.args, {
63
+ stdio: 'ignore',
64
+ timeout: 15000,
65
+ windowsVerbatimArguments: inv.windowsVerbatimArguments,
66
+ });
67
+ return !r.error && r.status === 0;
67
68
  }
68
69
  /**
69
70
  * Whether a specific runtime is runnable here. The HOST runtime needs no PATH probe
@@ -26,7 +26,7 @@
26
26
  // asked for only where a reader demands it (see below) and not blanket-applied
27
27
  // to the payload tarballs.
28
28
  //
29
- // Ledgered in `status/open-todos.md`; closing it means pinning ONE deflate
29
+ // Ledgered in `status/open-todos/README.md`; closing it means pinning ONE deflate
30
30
  // implementation, which is a larger decision than this file.
31
31
  import { gzip } from '@gjsify/tar';
32
32
  /**
@@ -0,0 +1,14 @@
1
+ import type { AppIconRasters } from './icons.js';
2
+ /** One element of the container: Apple's type code and the pixel size of the PNG it holds. */
3
+ export interface IcnsElement {
4
+ readonly type: string;
5
+ readonly size: number;
6
+ /** The `.iconset` filename `iconutil` gives this element — the reader's spelling, for messages. */
7
+ readonly iconset: string;
8
+ }
9
+ /** The ten PNG-backed element types of a modern application icon, in `iconutil`'s order. */
10
+ export declare const ICNS_ELEMENTS: readonly IcnsElement[];
11
+ /** The distinct pixel sizes the ten elements need — what the layout asks the raster step for. */
12
+ export declare const ICNS_SIZES: readonly number[];
13
+ /** The `.icns` file, `TOC ` first, then the ten elements in {@link ICNS_ELEMENTS} order. */
14
+ export declare function buildIcns(icon: AppIconRasters): Uint8Array;
@@ -0,0 +1,83 @@
1
+ // The macOS icon container — what `iconutil -c icns` produces from an
2
+ // `.iconset`, written here from the same rasters the Windows icon uses.
3
+ //
4
+ // THE FORMAT is a big-endian tagged container: an 8-byte header (`icns`, total
5
+ // length), then elements, each an 8-byte head (4-char type, length INCLUDING the
6
+ // head) and its data. Since Mac OS X 10.7 an element's data may be a whole PNG
7
+ // file, one element type per (size, scale) pair — which is what lets this file
8
+ // be written with no image encoder of its own.
9
+ //
10
+ // THE ELEMENT TABLE is Apple's iconset naming turned into type codes, and it is
11
+ // cited to two readers rather than typed from memory: Pillow's
12
+ // `IcnsImagePlugin.SIZES` (12.3) maps exactly these ten types to these (size,
13
+ // scale) pairs, and `iconutil` on macOS 15 — see the report that lands this file
14
+ // — accepts the result. `icp4`/`icp5` (16 and 32 at 1x) are the two Pillow's
15
+ // WRITER omits and its READER accepts; they are included because Apple's own
16
+ // `iconutil` writes them for `icon_16x16.png` and `icon_32x32.png`, and the Finder
17
+ // list view and the Dock's smallest sizes are drawn from exactly those two.
18
+ //
19
+ // TEN ELEMENTS OVER SEVEN SIZES: a `@2x` element and a 1x element of twice the
20
+ // point size hold the SAME pixels (`ic11` = 16@2x and `icp5` = 32@1x are both 32 px
21
+ // PNGs), so the raster map is asked for seven sizes and three of them are written
22
+ // twice. Writing all ten rather than the seven distinct sizes is what makes the
23
+ // file complete in the Finder's terms: it looks up by (size, scale), not by pixel
24
+ // count.
25
+ //
26
+ // `TOC ` FIRST, as `iconutil` and Pillow both write it: an optional index of the
27
+ // elements that follow, which lets a reader seek without walking. Optional to
28
+ // read, cheap to write, and its absence is the first thing a strict reader
29
+ // notices.
30
+ //
31
+ // WHAT NO LINUX READER HERE CAN SAY. `icns2png` (libicns 0.8.1) parses `ic08`,
32
+ // `ic09` and `ic10` and refuses the other seven types by name — measured on this
33
+ // workstation, so it reads three of ten and is not an oracle for the file. Pillow
34
+ // reads all ten and is NOT in the CI image. `verify-app-plist.py` therefore reads
35
+ // the container with CPython `struct` and checks each PNG's IHDR against the type's
36
+ // size, which is one independent family; the `iconutil` run on macOS is the
37
+ // reader with authority, and it is a VM measurement, not a CI leg.
38
+ import { concatBytes } from './bytes.js';
39
+ import { readPngSize } from './icons.js';
40
+ /** The ten PNG-backed element types of a modern application icon, in `iconutil`'s order. */
41
+ export const ICNS_ELEMENTS = [
42
+ { type: 'icp4', size: 16, iconset: 'icon_16x16.png' },
43
+ { type: 'ic11', size: 32, iconset: 'icon_16x16@2x.png' },
44
+ { type: 'icp5', size: 32, iconset: 'icon_32x32.png' },
45
+ { type: 'ic12', size: 64, iconset: 'icon_32x32@2x.png' },
46
+ { type: 'ic07', size: 128, iconset: 'icon_128x128.png' },
47
+ { type: 'ic13', size: 256, iconset: 'icon_128x128@2x.png' },
48
+ { type: 'ic08', size: 256, iconset: 'icon_256x256.png' },
49
+ { type: 'ic14', size: 512, iconset: 'icon_256x256@2x.png' },
50
+ { type: 'ic09', size: 512, iconset: 'icon_512x512.png' },
51
+ { type: 'ic10', size: 1024, iconset: 'icon_512x512@2x.png' },
52
+ ];
53
+ /** The distinct pixel sizes the ten elements need — what the layout asks the raster step for. */
54
+ export const ICNS_SIZES = [...new Set(ICNS_ELEMENTS.map((element) => element.size))].sort((a, b) => a - b);
55
+ /** An element head: the four-character type and the big-endian length including these 8 bytes. */
56
+ function head(type, length) {
57
+ const out = Buffer.alloc(8);
58
+ out.write(type, 0, 'latin1');
59
+ out.writeUInt32BE(length, 4);
60
+ return new Uint8Array(out);
61
+ }
62
+ /** The `.icns` file, `TOC ` first, then the ten elements in {@link ICNS_ELEMENTS} order. */
63
+ export function buildIcns(icon) {
64
+ const elements = ICNS_ELEMENTS.map((element) => {
65
+ const png = icon.png.get(element.size);
66
+ if (png === undefined) {
67
+ throw new Error(`gjsify ship: the macOS icon needs a ${element.size} px image (${element.iconset}) and the raster ` +
68
+ `set has none — \`resolveAppIcon\` was asked for the wrong sizes.`);
69
+ }
70
+ const { width, height } = readPngSize(png, `the ${element.size} px icon image`);
71
+ if (width !== element.size || height !== element.size) {
72
+ throw new Error(`gjsify ship: the image keyed ${element.size} in the icon raster set is ${width}×${height}.`);
73
+ }
74
+ return { ...element, png };
75
+ });
76
+ const toc = concatBytes([
77
+ head('TOC ', 8 + 8 * elements.length),
78
+ ...elements.map((element) => head(element.type, 8 + element.png.length)),
79
+ ]);
80
+ const body = elements.map((element) => concatBytes([head(element.type, 8 + element.png.length), element.png]));
81
+ const total = 8 + toc.length + body.reduce((sum, element) => sum + element.length, 0);
82
+ return concatBytes([head('icns', total), toc, ...body]);
83
+ }
@@ -0,0 +1,25 @@
1
+ import type { AppIconRasters } from './icons.js';
2
+ /** The edge lengths a Windows application icon carries. Ascending, so the entry order is too. */
3
+ export declare const ICO_SIZES: readonly number[];
4
+ /** `RT_ICON` and `RT_GROUP_ICON`: the two resource type ids. */
5
+ export declare const RT_ICON = 3;
6
+ export declare const RT_GROUP_ICON = 14;
7
+ /**
8
+ * The `.ico` file: `ICONDIR`, one `ICONDIRENTRY` per image, then the PNGs.
9
+ */
10
+ export declare function buildIco(icon: AppIconRasters): Uint8Array;
11
+ /** What the PE resource directory carries: the group, and the images the group names by id. */
12
+ export interface IconResources {
13
+ /** The `RT_GROUP_ICON` payload: `GRPICONDIR` with 14-byte entries naming `icons` by 1-based id. */
14
+ group: Uint8Array;
15
+ /** The `RT_ICON` payloads, in id order: `icons[i]` is resource id `i + 1`. */
16
+ icons: readonly Uint8Array[];
17
+ }
18
+ /**
19
+ * The same images as resources: `RT_ICON` id `n` is the n-th image, and the
20
+ * `RT_GROUP_ICON` names them by that id.
21
+ *
22
+ * Ids start at 1 because resource id 0 is never used by convention and some
23
+ * readers treat it as "no icon".
24
+ */
25
+ export declare function iconResources(icon: AppIconRasters): IconResources;
@@ -0,0 +1,120 @@
1
+ // The Windows icon container, in its two spellings: the `.ico` FILE, and the
2
+ // pair of resource types (`RT_GROUP_ICON`, `RT_ICON`) the same bytes take inside
3
+ // a PE image.
4
+ //
5
+ // WHY BOTH. Explorer, the Start menu, the taskbar and the Alt-Tab list all ask
6
+ // the EXECUTABLE for its icon, so the launcher `pe-launcher.ts` emits carries the
7
+ // resource form. The `.ico` file form is what an independent reader opens —
8
+ // Pillow, `icotool`, ImageMagick — and it is how `verify-program-dir.py`
9
+ // reassembles what it extracted from the `.rsrc` section to hand it to one.
10
+ //
11
+ // THE RESOURCE FORM IS NOT THE FILE FORM, and the difference is the one thing
12
+ // worth knowing about this format. An `.ico` is an `ICONDIR` whose entries end in
13
+ // a 4-byte `dwImageOffset` into the file. Inside a PE the images are separate
14
+ // `RT_ICON` resources, so the group directory's entries end in a 2-byte `nId`
15
+ // naming the resource instead — `GRPICONDIRENTRY` is 14 bytes where
16
+ // `ICONDIRENTRY` is 16. Writing the file form into `RT_GROUP_ICON` produces a
17
+ // resource Windows reads as a group with garbage ids, and the shell shows the
18
+ // generic icon with no error anywhere.
19
+ //
20
+ // PNG PAYLOADS AT EVERY SIZE. Since Windows Vista an image entry may be a whole
21
+ // PNG file rather than a BMP `BITMAPINFOHEADER` + pixel rows + AND mask, and that
22
+ // is what removes the need for a BMP encoder here. Confirmed against two readers
23
+ // on this workstation rather than trusted: Pillow 12.3 and `icotool -l`
24
+ // (icoutils 0.32) both list a six-entry PNG-only `.ico` at 16/24/32/48/64/256
25
+ // as six 32-bit images. The 256 entry is the one every guide says MUST be PNG
26
+ // (a BMP at that size is 256 KiB); the smaller ones are PNG for the same reason
27
+ // and because one encoding is one code path. Whether Windows 11's shell draws the
28
+ // small PNG entries at the same quality as BMP ones is a VM measurement this
29
+ // tree has not made — see the report that lands this file.
30
+ //
31
+ // THE SIZES are Microsoft's list for a classic desktop application icon — 16,
32
+ // 24, 32, 48 and 256, "the full set" in the *Icons (Design basics)* guidance —
33
+ // plus 64, which the same page names for the 200 % scaling of the 32 slot. The
34
+ // Start menu and the taskbar pick from these by DPI; a set missing 16 or 32
35
+ // renders the entry as a scaled 256, which is the blur every reader has seen on a
36
+ // half-finished port.
37
+ import { concatBytes } from './bytes.js';
38
+ import { readPngSize } from './icons.js';
39
+ /** The edge lengths a Windows application icon carries. Ascending, so the entry order is too. */
40
+ export const ICO_SIZES = [16, 24, 32, 48, 64, 256];
41
+ /** `RT_ICON` and `RT_GROUP_ICON`: the two resource type ids. */
42
+ export const RT_ICON = 3;
43
+ export const RT_GROUP_ICON = 14;
44
+ /** The ICONDIRENTRY / GRPICONDIRENTRY head shared by both forms: 8 bytes. */
45
+ function entryHead(size) {
46
+ const head = Buffer.alloc(8);
47
+ // 0 means 256 in the one-byte width/height fields — the format predates 256
48
+ // px icons and a byte cannot hold the number.
49
+ head.writeUInt8(size >= 256 ? 0 : size, 0); // bWidth
50
+ head.writeUInt8(size >= 256 ? 0 : size, 1); // bHeight
51
+ head.writeUInt8(0, 2); // bColorCount — 0 for anything past 8 bpp
52
+ head.writeUInt8(0, 3); // bReserved
53
+ head.writeUInt16LE(1, 4); // wPlanes
54
+ head.writeUInt16LE(32, 6); // wBitCount — what a PNG-backed entry declares
55
+ return new Uint8Array(head);
56
+ }
57
+ /** The sizes in ascending order, each one asserted present and square at that size. */
58
+ function entries(icon) {
59
+ const out = [];
60
+ for (const size of ICO_SIZES) {
61
+ const png = icon.png.get(size);
62
+ if (png === undefined) {
63
+ throw new Error(`gjsify ship: the Windows icon needs a ${size} px image and the raster set has none — ` +
64
+ `\`resolveAppIcon\` was asked for the wrong sizes (it has ${[...icon.png.keys()].join(', ')}).`);
65
+ }
66
+ const { width, height } = readPngSize(png, `the ${size} px icon image`);
67
+ if (width !== size || height !== size) {
68
+ throw new Error(`gjsify ship: the image keyed ${size} in the icon raster set is ${width}×${height}.`);
69
+ }
70
+ if (size > 256) {
71
+ // The one-byte size fields cannot say more than 256, and Windows reads
72
+ // nothing larger from an icon.
73
+ throw new Error(`gjsify ship: a Windows icon image cannot be ${size} px — 256 is the format's ceiling.`);
74
+ }
75
+ out.push({ size, png });
76
+ }
77
+ return out;
78
+ }
79
+ /**
80
+ * The `.ico` file: `ICONDIR`, one `ICONDIRENTRY` per image, then the PNGs.
81
+ */
82
+ export function buildIco(icon) {
83
+ const images = entries(icon);
84
+ const header = Buffer.alloc(6);
85
+ header.writeUInt16LE(0, 0); // idReserved
86
+ header.writeUInt16LE(1, 2); // idType — 1 is an icon, 2 a cursor
87
+ header.writeUInt16LE(images.length, 4);
88
+ const directory = [];
89
+ let offset = 6 + 16 * images.length;
90
+ for (const { size, png } of images) {
91
+ const tail = Buffer.alloc(8);
92
+ tail.writeUInt32LE(png.length, 0); // dwBytesInRes
93
+ tail.writeUInt32LE(offset, 4); // dwImageOffset — a FILE offset
94
+ directory.push(entryHead(size), new Uint8Array(tail));
95
+ offset += png.length;
96
+ }
97
+ return concatBytes([new Uint8Array(header), ...directory, ...images.map((image) => image.png)]);
98
+ }
99
+ /**
100
+ * The same images as resources: `RT_ICON` id `n` is the n-th image, and the
101
+ * `RT_GROUP_ICON` names them by that id.
102
+ *
103
+ * Ids start at 1 because resource id 0 is never used by convention and some
104
+ * readers treat it as "no icon".
105
+ */
106
+ export function iconResources(icon) {
107
+ const images = entries(icon);
108
+ const header = Buffer.alloc(6);
109
+ header.writeUInt16LE(0, 0);
110
+ header.writeUInt16LE(1, 2);
111
+ header.writeUInt16LE(images.length, 4);
112
+ const directory = [];
113
+ images.forEach(({ size, png }, index) => {
114
+ const tail = Buffer.alloc(6);
115
+ tail.writeUInt32LE(png.length, 0); // dwBytesInRes
116
+ tail.writeUInt16LE(index + 1, 4); // nId — the RT_ICON resource, NOT an offset
117
+ directory.push(entryHead(size), new Uint8Array(tail));
118
+ });
119
+ return { group: concatBytes([new Uint8Array(header), ...directory]), icons: images.map((image) => image.png) };
120
+ }
@@ -0,0 +1,197 @@
1
+ import { spawnToCompletion } from '../spawn.js';
2
+ import type { SystemGiOptions } from '../system-gi.js';
3
+ /** The runtime that renders. On `PATH`, like `glib-compile-schemas` — a GJS host has it by definition. */
4
+ export declare const RASTERIZER = "gjs";
5
+ /**
6
+ * How to get the typelib the script imports, in the two distributions' words.
7
+ *
8
+ * `librsvg2` on Fedora carries the typelib itself (measured: `rpm -qf
9
+ * /usr/lib64/girepository-1.0/Rsvg-2.0.typelib` → `librsvg2-2.62.3`); Debian
10
+ * splits it into `gir1.2-rsvg-2.0`.
11
+ *
12
+ * The macOS row IS measured, on the `macos-suites` darwin leg, and its first
13
+ * spelling was WRONG: `brew install gjs librsvg` alone still failed, with the
14
+ * error moving from `namespace 'Rsvg' not found` to `namespace 'cairo', version
15
+ * '1.0' not found`. The script imports both, and `Cairo-1.0.typelib` belongs to
16
+ * `gobject-introspection` rather than to the `cairo` formula (`rpm -qf` says so
17
+ * for the Fedora twin). A hint that names two thirds of what is needed sends a
18
+ * stranger round the same loop this one went round.
19
+ *
20
+ * INSTALLING ALL THREE IS STILL NOT ENOUGH on macOS, and this sentence cost the
21
+ * third red run: the dylib then EXISTS and dyld does not look where it is. That
22
+ * half is not a hint, it is {@link rasterizerEnv} — and the message deliberately
23
+ * stays quiet about it, because a host that reaches this line after the repair
24
+ * really is missing a package.
25
+ *
26
+ * Assembly is a Linux step by ADR 0024 § A2, so a darwin host reaching this line
27
+ * is running `ship` outside the documented arrangement; the message should still
28
+ * tell it what to install rather than name two Linuxes at it.
29
+ */
30
+ export declare const RASTERIZER_HINT: string;
31
+ /**
32
+ * The pixels of the app icon, one PNG per square edge length.
33
+ *
34
+ * A MAP keyed by size and not an array, because every consumer asks "the PNG for
35
+ * N" — the `.ico` writer for its six entries, the `.icns` writer for its ten
36
+ * elements over seven sizes — and an array would make each of them search.
37
+ */
38
+ export interface AppIconRasters {
39
+ /** Where the pixels came from, for the log line: the SVG's path, or how many sized PNGs. */
40
+ readonly source: string;
41
+ readonly png: ReadonlyMap<number, Uint8Array>;
42
+ }
43
+ /**
44
+ * Whether an icon belongs in the theme's SYMBOLIC context rather than at a size.
45
+ *
46
+ * `symbolic` is a directory of its own in a hicolor theme and NOT a size value.
47
+ * Measured in `refs/adwaita-icon-theme/index.theme`, which lists `symbolic/apps`
48
+ * in `Directories=` beside `scalable/apps` and `16x16/apps` and gives it its own
49
+ * section — `Context=Applications`, `Size=16`, `MinSize=8`, `MaxSize=512`,
50
+ * `Type=Scalable` — against the scalable row's `Size=128`. The freedesktop icon
51
+ * theme specification has no notion of `symbolic` at all: a theme expresses it by
52
+ * giving the directory a section, which is exactly why reading the EXTENSION
53
+ * cannot see it, and why this question has to be asked before the size one.
54
+ *
55
+ * TWO SIGNALS, the same pair `iconThemeDir` already reads for a size: the
56
+ * directory an author put the file in, and the name they gave it. GTK resolves a
57
+ * symbolic icon by the `-symbolic` name suffix, so an author who wrote the name
58
+ * has said as much as one who wrote the path.
59
+ *
60
+ * SVG ONLY, because the symbolic directory is declared `Type=Scalable`: the
61
+ * raster form GTK's own `gtk-encode-symbolic-svg` produces
62
+ * (`<name>-symbolic.symbolic.png`) is installed at a SIZE instead. So a PNG keeps
63
+ * answering the size question, and one that cannot answer it stays refused rather
64
+ * than quietly becoming a scalable icon that does not scale.
65
+ */
66
+ export declare function isSymbolicIcon(iconPath: string): boolean;
67
+ /**
68
+ * The hicolor subdirectory for an icon: the `symbolic` CONTEXT, else `scalable`
69
+ * for an SVG, else the pixel size read from a `<n>x<n>` path component or a
70
+ * trailing number in the filename.
71
+ *
72
+ * Named for the theme DIRECTORY rather than a size from the moment the first of
73
+ * those answers stopped being one: `iconSizeDir` returning `symbolic` would be a
74
+ * name every caller has to read past.
75
+ */
76
+ export declare function iconThemeDir(iconPath: string): string;
77
+ /**
78
+ * A PNG's pixel size, read from its IHDR — with the file's two ends checked.
79
+ *
80
+ * The signature and the IHDR say what the file CLAIMS to be; the `IEND` trailer
81
+ * says it is all there. A PNG truncated by a half-written copy still has a valid
82
+ * header, and an icon container built around one embeds a picture that decodes
83
+ * to nothing on the target OS with no error at pack time. Twelve bytes at the end
84
+ * is the cheapest check that catches that whole class.
85
+ */
86
+ export declare function readPngSize(bytes: Uint8Array, what: string): {
87
+ width: number;
88
+ height: number;
89
+ };
90
+ /**
91
+ * The program the child GJS runs. `ARGV` is the SVG path followed by the sizes;
92
+ * stdout is one JSON object mapping each size to the PNG, base64.
93
+ *
94
+ * Legacy `imports`, deliberately: `gjs -c` takes a script, not a module, and the
95
+ * legacy namespace is the one a script can reach synchronously. `imports.cairo`
96
+ * is GJS's own cairo binding — `ImageSurface` and `writeToPNG` are its, not the
97
+ * typelib's, which has no constructors.
98
+ *
99
+ * `render_document` scales the document to the viewport, so each size is a
100
+ * fresh vector render and never a resample of a larger one — the difference
101
+ * between a crisp 16 px icon and a blurred one, which is the whole reason to
102
+ * render per size rather than once.
103
+ *
104
+ * Through a file rather than a memory surface's bytes: `writeToPNG` is the PNG
105
+ * ENCODER, and cairo is the one this process has. Base64 on stdout because it is
106
+ * the transport both runtimes' `child_process` read the same way.
107
+ */
108
+ export declare const RASTERIZE_SCRIPT: string;
109
+ /**
110
+ * The launch env for the rasterizing child — the HOST's GI libdirs put back.
111
+ *
112
+ * THE INCIDENT, measured on both `macos-suites` legs (2026-09-13). `brew install
113
+ * gjs librsvg gobject-introspection` installs everything the script imports, and
114
+ * the render still died:
115
+ *
116
+ * Failed to load shared library 'librsvg-2.2.dylib' referenced by the typelib:
117
+ * dlopen(librsvg-2.2.dylib): tried: 'librsvg-2.2.dylib' (no such file),
118
+ * '/usr/local/Cellar/gjs/1.88.1/bin/../../../../opt/glib/lib/librsvg-2.2.dylib',
119
+ * '/usr/lib/librsvg-2.2.dylib' (not in dyld cache)
120
+ * JS ERROR: Error: Unsupported type void, deriving from fundamental void
121
+ *
122
+ * `/usr/local/lib` (x64) and `/opt/homebrew/lib` (arm64) — where Homebrew puts
123
+ * the dylib — are in NEITHER list, because a typelib names its library by bare
124
+ * leaf and Homebrew's `gjs` carries one rpath, into GLIB's keg alone. The second
125
+ * line is the symptom, not the cause: the typelib loads, its library does not,
126
+ * and the first call then marshals a type that is no longer resolvable. It reads
127
+ * like GJS was built without cairo, and this comment exists because that is what
128
+ * it was first diagnosed as.
129
+ *
130
+ * {@link buildNativeEnv} already repairs exactly this, and its own header calls
131
+ * itself "the ONE place `gjsify run`, `showcase`, `storybook`, `tsc` and `info`
132
+ * all pass through". `ship` opened a SECOND gjs child that went around it — which
133
+ * is the class, not the bug: the repair is per-call-site, so every new child
134
+ * inherits the gap until someone reads that header. `rasterizerEnv` is what the
135
+ * spec beside it pins, so the wiring is asserted and not just the composition.
136
+ *
137
+ * No packages are passed: the gap is in the host's loader, not in a prebuild we
138
+ * ship, and the child is a bare `gjs -c` that loads no `.node` at all.
139
+ *
140
+ * `env` FIRST, so the composed values win over the inherited ones rather than
141
+ * being shadowed by them.
142
+ */
143
+ export declare function rasterizerEnv(opts?: {
144
+ platform?: string;
145
+ env?: Record<string, string | undefined>;
146
+ /** Injectable for tests — the darwin branch is asserted from Linux. */
147
+ systemGiDirs?: (opts: SystemGiOptions) => string[];
148
+ }): NodeJS.ProcessEnv;
149
+ export interface RasterizeInput {
150
+ /** Absolute path of the SVG. */
151
+ svg: string;
152
+ /** Square edge lengths to render, each one a separate vector render. */
153
+ sizes: readonly number[];
154
+ /**
155
+ * Injectable for tests. NOT a convenience: the {@link rasterizerEnv} repair is
156
+ * only worth anything if it reaches the child, and a spec over the composition
157
+ * alone passes just as well when nobody wires it in.
158
+ */
159
+ spawn?: typeof spawnToCompletion;
160
+ /**
161
+ * The command a refusal names (default `gjsify ship`). `gjsify webext` renders
162
+ * its toolbar icons through the same child, and a message blaming `ship`
163
+ * would send its reader to the wrong command's documentation.
164
+ */
165
+ command?: string;
166
+ }
167
+ /**
168
+ * Render an SVG at every size through librsvg, in a child GJS.
169
+ *
170
+ * Every PNG that comes back is read for its IHDR before it is returned: the size
171
+ * the child rendered is the size the caller asked for or the whole batch is
172
+ * refused, because the writer that would embed it trusts the map's key.
173
+ */
174
+ export declare function rasterizeSvg(input: RasterizeInput): Promise<Map<number, Uint8Array>>;
175
+ export interface ResolveAppIconInput {
176
+ /** Absolute paths of the project's icons — `ShipSettings.iconFiles`. */
177
+ iconFiles: readonly string[];
178
+ appId: string;
179
+ /** The square sizes the layout's writer needs, from {@link Layout.icon}. */
180
+ sizes: readonly number[];
181
+ /** What the sizes are FOR, in the refusal: "the Windows launcher's icon". */
182
+ target: string;
183
+ /** The renderer, injectable so the selection is testable without a GJS. */
184
+ rasterize?: (input: RasterizeInput) => Promise<Map<number, Uint8Array>>;
185
+ }
186
+ /**
187
+ * The raster set a layout needs, from whatever the project declares.
188
+ *
189
+ * Sized PNGs the project ships are used at their (IHDR-read) size; every size
190
+ * they leave uncovered is rendered from the scalable SVG; a size neither can
191
+ * answer is a refusal that names the file to add. The `-symbolic` icon is never
192
+ * a source: it is the monochrome status-bar glyph, and an app icon drawn from it
193
+ * would be a grey silhouette on both platforms.
194
+ */
195
+ export declare function resolveAppIcon(input: ResolveAppIconInput): Promise<AppIconRasters>;
196
+ /** The sizes in a raster set, ascending — for log lines and error messages. */
197
+ export declare function iconSizes(icon: AppIconRasters): number[];