@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.
- package/dist/cli.gjs.mjs +539 -493
- package/lib/actions/build.d.ts +22 -0
- package/lib/actions/build.js +37 -9
- package/lib/bundler-pick.d.ts +37 -1
- package/lib/bundler-pick.js +108 -17
- package/lib/cli-app.js +7 -2
- package/lib/commands/affected-classify.d.ts +7 -1
- package/lib/commands/affected-classify.js +66 -9
- package/lib/commands/affected-script-refs.d.ts +20 -0
- package/lib/commands/affected-script-refs.js +218 -0
- package/lib/commands/affected.js +24 -2
- package/lib/commands/env.d.ts +31 -0
- package/lib/commands/env.js +114 -0
- package/lib/commands/exec.d.ts +10 -0
- package/lib/commands/exec.js +208 -0
- package/lib/commands/flatpak/diff.js +8 -2
- package/lib/commands/flatpak/init.js +4 -1
- package/lib/commands/flatpak/sources.js +4 -3
- package/lib/commands/flatpak/sync-flathub.d.ts +2 -0
- package/lib/commands/flatpak/sync-flathub.js +149 -7
- package/lib/commands/flatpak/utils.d.ts +10 -0
- package/lib/commands/flatpak/utils.js +10 -0
- package/lib/commands/foreach.js +5 -97
- package/lib/commands/format.js +26 -6
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -0
- package/lib/commands/install.d.ts +2 -2
- package/lib/commands/install.js +81 -5
- package/lib/commands/link.d.ts +13 -0
- package/lib/commands/link.js +243 -0
- package/lib/commands/onboard.js +18 -2
- package/lib/commands/publish.d.ts +34 -0
- package/lib/commands/publish.js +153 -8
- package/lib/commands/self-update.js +2 -1
- package/lib/commands/ship.js +19 -1
- package/lib/commands/test.d.ts +18 -9
- package/lib/commands/test.js +56 -11
- package/lib/commands/tsc.js +4 -4
- package/lib/commands/upgrade.d.ts +15 -0
- package/lib/commands/upgrade.js +76 -2
- package/lib/commands/webext/index.d.ts +2 -0
- package/lib/commands/webext/index.js +15 -0
- package/lib/commands/webext/subcommands.d.ts +16 -0
- package/lib/commands/webext/subcommands.js +204 -0
- package/lib/types/config-data.d.ts +2 -2
- package/lib/utils/bin-shim.d.ts +8 -2
- package/lib/utils/bin-shim.js +6 -5
- package/lib/utils/bundle-inputs.d.ts +84 -0
- package/lib/utils/bundle-inputs.js +268 -0
- package/lib/utils/check-system-deps.d.ts +5 -19
- package/lib/utils/check-system-deps.js +31 -94
- package/lib/utils/dev-link.d.ts +225 -0
- package/lib/utils/dev-link.js +684 -0
- package/lib/utils/exec-bin.d.ts +137 -0
- package/lib/utils/exec-bin.js +321 -0
- package/lib/utils/gi-runtime-paths.js +3 -2
- package/lib/utils/gitignore.d.ts +15 -0
- package/lib/utils/gitignore.js +178 -0
- package/lib/utils/gjsify-shim.d.ts +47 -0
- package/lib/utils/gjsify-shim.js +97 -8
- package/lib/utils/hang-watchdog.d.ts +95 -0
- package/lib/utils/hang-watchdog.js +194 -0
- package/lib/utils/install-backend-native.d.ts +33 -1
- package/lib/utils/install-backend-native.js +184 -20
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-lock.js +4 -25
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- package/lib/utils/native-skip-scan.d.ts +20 -0
- package/lib/utils/native-skip-scan.js +343 -0
- package/lib/utils/node-script.js +1 -1
- package/lib/utils/normalize-bundler-options.js +11 -0
- package/lib/utils/oxc-resolve.js +42 -3
- package/lib/utils/process-table.d.ts +22 -0
- package/lib/utils/process-table.js +188 -0
- package/lib/utils/prune-prefix.js +1 -1
- package/lib/utils/publish-pins.d.ts +38 -0
- package/lib/utils/publish-pins.js +47 -0
- package/lib/utils/publish-readback.d.ts +103 -17
- package/lib/utils/publish-readback.js +326 -47
- package/lib/utils/run-gjs.d.ts +15 -1
- package/lib/utils/run-gjs.js +98 -13
- package/lib/utils/run-node.d.ts +2 -0
- package/lib/utils/run-node.js +2 -1
- package/lib/utils/runtimes.js +14 -13
- package/lib/utils/ship/gzip.js +1 -1
- package/lib/utils/ship/icns.d.ts +14 -0
- package/lib/utils/ship/icns.js +83 -0
- package/lib/utils/ship/ico.d.ts +25 -0
- package/lib/utils/ship/ico.js +120 -0
- package/lib/utils/ship/icons.d.ts +197 -0
- package/lib/utils/ship/icons.js +399 -0
- package/lib/utils/ship/launcher.js +16 -1
- package/lib/utils/ship/layout.d.ts +32 -0
- package/lib/utils/ship/layout.js +112 -38
- package/lib/utils/ship/msi.d.ts +1 -1
- package/lib/utils/ship/msi.js +30 -5
- package/lib/utils/ship/pe-launcher.d.ts +18 -0
- package/lib/utils/ship/pe-launcher.js +125 -4
- package/lib/utils/ship/plan.d.ts +1 -34
- package/lib/utils/ship/plan.js +7 -56
- package/lib/utils/ship/plist.d.ts +20 -10
- package/lib/utils/ship/plist.js +29 -12
- package/lib/utils/ship/settings.js +3 -2
- package/lib/utils/spawn.d.ts +13 -0
- package/lib/utils/spawn.js +10 -5
- package/lib/utils/watch-loop.d.ts +15 -1
- package/lib/utils/watch-loop.js +9 -5
- package/lib/utils/webext/build.d.ts +42 -0
- package/lib/utils/webext/build.js +120 -0
- package/lib/utils/webext/config.d.ts +72 -0
- package/lib/utils/webext/config.js +234 -0
- package/lib/utils/webext/icons.d.ts +24 -0
- package/lib/utils/webext/icons.js +83 -0
- package/lib/utils/webext/launch.d.ts +32 -0
- package/lib/utils/webext/launch.js +77 -0
- package/lib/utils/webext/manifest.d.ts +53 -0
- package/lib/utils/webext/manifest.js +219 -0
- package/lib/utils/webext/pages.d.ts +26 -0
- package/lib/utils/webext/pages.js +61 -0
- package/lib/utils/webext/targets.d.ts +13 -0
- package/lib/utils/webext/targets.js +36 -0
- package/lib/utils/win32-command.d.ts +17 -0
- package/lib/utils/win32-command.js +21 -6
- package/package.json +20 -20
package/lib/utils/runtimes.js
CHANGED
|
@@ -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`
|
|
10
|
-
// `@gjsify/rolldown-plugin-gjsify/runtime` host
|
|
11
|
-
// imports) so an external example can deep-import
|
|
12
|
-
// (`@gjsify/cli/lib/utils/runtimes.js`) without pulling
|
|
13
|
-
// same deep-import precedent as `@gjsify/cli/lib/utils/run-gjs.js`.
|
|
14
|
-
import {
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
package/lib/utils/ship/gzip.js
CHANGED
|
@@ -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[];
|