@gjsify/cli 0.51.0 → 0.52.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 +505 -490
- package/lib/cli-app.js +3 -1
- 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/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/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/install.js +76 -0
- 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/ship.js +46 -4
- package/lib/types/config-data.d.ts +1 -1
- package/lib/utils/check-system-deps.d.ts +1 -17
- package/lib/utils/check-system-deps.js +10 -68
- package/lib/utils/dev-link.d.ts +222 -0
- package/lib/utils/dev-link.js +632 -0
- 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 +14 -0
- package/lib/utils/install-backend-native.js +91 -13
- package/lib/utils/install-extraneous.d.ts +90 -0
- package/lib/utils/install-extraneous.js +120 -0
- package/lib/utils/install-tarball-cache.d.ts +37 -9
- package/lib/utils/install-tarball-cache.js +59 -14
- package/lib/utils/normalize-bundler-options.js +11 -0
- 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.js +82 -6
- package/lib/utils/ship/appimage.d.ts +24 -4
- package/lib/utils/ship/appimage.js +51 -6
- package/lib/utils/ship/formats.js +44 -11
- 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 +191 -0
- package/lib/utils/ship/icons.js +398 -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/schemas.d.ts +46 -0
- package/lib/utils/ship/schemas.js +92 -11
- package/lib/utils/ship/settings.js +3 -2
- package/package.json +20 -20
|
@@ -99,6 +99,7 @@ import { describeExit, spawnToCompletion } from '../spawn.js';
|
|
|
99
99
|
import { encodeUtf8 } from './bytes.js';
|
|
100
100
|
import { DEFAULT_GJS_FLOOR, DEFAULT_NODE_FLOOR, hostProvidedNamespaces } from './depends.js';
|
|
101
101
|
import { LAYOUTS } from './layout.js';
|
|
102
|
+
import { isSchemaSource, SCHEMA_CACHE } from './schemas.js';
|
|
102
103
|
import { SHARE } from './share-dirs.js';
|
|
103
104
|
/** The tool this packer execs. One spelling, read by this module AND by the format row. */
|
|
104
105
|
export const APPIMAGE_TOOL = 'appimagetool';
|
|
@@ -234,6 +235,17 @@ export function interpreterFloor(input) {
|
|
|
234
235
|
* drifts is the one nothing tests: `tests/e2e/ship-layout` compares the staged
|
|
235
236
|
* trees, not this script.
|
|
236
237
|
*
|
|
238
|
+
* THAT CLAIM WAS RE-MEASURED AGAINST THE ARTIFACT and it holds — which is worth
|
|
239
|
+
* writing down, because when Learn6502 0.8.0's AppImage died on `GSettings schema
|
|
240
|
+
* … not found` this script was the obvious suspect and adding
|
|
241
|
+
* `GSETTINGS_SCHEMA_DIR` here was the obvious fix. It would have changed nothing.
|
|
242
|
+
* Measured on the real 0.8.0 image, by compiling `gschemas.compiled` into the
|
|
243
|
+
* extracted AppDir and touching NO environment variable anywhere: the application
|
|
244
|
+
* started. The launcher's `XDG_DATA_DIRS` was already pointing GSettings at the
|
|
245
|
+
* right directory; the directory had no cache in it. An extra export would have
|
|
246
|
+
* been a second name for a path that was already correct, and it would have
|
|
247
|
+
* "fixed" the bug by sitting next to the change that did.
|
|
248
|
+
*
|
|
237
249
|
* WHAT IT DOES ADD is the refusal ADR 0024 § 9 asks for. An AppImage whose
|
|
238
250
|
* interpreter is missing exits with the loader's own message — `gjs: not found`
|
|
239
251
|
* from a shell, or nothing at all when a desktop launches it — and the user
|
|
@@ -435,7 +447,7 @@ function rootIconRank(path, appId) {
|
|
|
435
447
|
* those two are separate from each other (ADR 0024 § A3): three failures, three
|
|
436
448
|
* different fixes — a different machine, a package, or a line in package.json.
|
|
437
449
|
*/
|
|
438
|
-
export function assertAppImageIsPackable(payload, settings) {
|
|
450
|
+
export function assertAppImageIsPackable(payload, settings, schemaCache) {
|
|
439
451
|
const desktopEntry = `${SHARE.applications}/${settings.appId}.desktop`;
|
|
440
452
|
if (!payload.some((entry) => entry.path === desktopEntry)) {
|
|
441
453
|
throw new Error(`gjsify ship: an AppImage needs a desktop entry and this payload has no ${desktopEntry}. ` +
|
|
@@ -450,10 +462,40 @@ export function assertAppImageIsPackable(payload, settings) {
|
|
|
450
462
|
'file keeping its own basename is an icon nothing looks up — point `gjsify.ship.icon` at a PNG ' +
|
|
451
463
|
'or an SVG and it is renamed into the theme for you.');
|
|
452
464
|
}
|
|
465
|
+
// THE THIRD REFUSAL, and the only one whose failure is invisible until a user
|
|
466
|
+
// double-clicks the file. The two above are decided by `package.json` and
|
|
467
|
+
// appimagetool would have caught both eventually; this one is decided by the
|
|
468
|
+
// PACK PATH and nothing catches it — the image builds, the oracle reads it
|
|
469
|
+
// back, every listing agrees, and the app dies at its first
|
|
470
|
+
// `Gio.Settings.new()` because `share/glib-2.0/schemas` holds a source with no
|
|
471
|
+
// compiled cache beside it. That is exactly what Learn6502 0.8.0 shipped.
|
|
472
|
+
//
|
|
473
|
+
// A GUARD ON THE PURE FUNCTION rather than a step in `packOne`, because this
|
|
474
|
+
// is the INVARIANT and not the procedure: whoever assembles an AppDir from a
|
|
475
|
+
// payload carrying schema sources owes it a cache, and an assembler that
|
|
476
|
+
// forgets must not be able to produce a tree. It is also what keeps the e2e
|
|
477
|
+
// honest — the suite calls this function to build its own AppDir, so it
|
|
478
|
+
// inherits the refusal instead of quietly modelling the defect.
|
|
479
|
+
if (schemaCache === undefined && payload.some((entry) => isSchemaSource(entry.path))) {
|
|
480
|
+
throw new Error(`gjsify ship: this payload stages GSettings schema sources under ${SHARE.schemas}/ and no ` +
|
|
481
|
+
`${SCHEMA_CACHE} was compiled for it. An AppImage has NO INSTALL STEP — that is the whole ` +
|
|
482
|
+
'point of the format — so the `.deb`/`.rpm` postinst that compiles the system schema ' +
|
|
483
|
+
'directory never runs, and GSettings aborts on a directory holding only sources. The AppDir ' +
|
|
484
|
+
`must carry ${SCHEMA_CACHE}; \`compileSchemasForPayload\` produces it from this payload.`);
|
|
485
|
+
}
|
|
453
486
|
}
|
|
454
487
|
/**
|
|
455
|
-
* The whole AppDir as one payload: the prefix under `usr/`, plus the
|
|
456
|
-
* at its root.
|
|
488
|
+
* The whole AppDir as one payload: the prefix under `usr/`, plus the compiled
|
|
489
|
+
* schema cache, plus the three files at its root.
|
|
490
|
+
*
|
|
491
|
+
* THE SCHEMA CACHE IS AN ARGUMENT AND NOT A COMPILE, so this function stays pure
|
|
492
|
+
* and so the one impure step in the AppDir has exactly one home (`packOne`). It
|
|
493
|
+
* is prefix-relative on the way in — `share/glib-2.0/schemas/gschemas.compiled`,
|
|
494
|
+
* the same shape every other entry has — and gets the `usr/` prefix from the same
|
|
495
|
+
* map below, because a second spelling of that prefix is the thing this packer
|
|
496
|
+
* spent a comment explaining it must not have. `undefined` is legal and means the
|
|
497
|
+
* payload has no schemas at all; a payload that HAS them and no cache is refused
|
|
498
|
+
* by {@link assertAppImageIsPackable}, not silently packed.
|
|
457
499
|
*
|
|
458
500
|
* PURE, AND ONE LIST RATHER THAN A WRITER, which is the shape `msi.ts` and
|
|
459
501
|
* `dmg.ts` already have — `packOne` owns the single `writePayload` call for every
|
|
@@ -471,15 +513,18 @@ export function assertAppImageIsPackable(payload, settings) {
|
|
|
471
513
|
* its schemas one directory too high — at exit 0, until the first
|
|
472
514
|
* `Gio.Settings.new()`.
|
|
473
515
|
*/
|
|
474
|
-
export function appDirPayload(settings, payload, hostRequirements) {
|
|
475
|
-
assertAppImageIsPackable(payload, settings);
|
|
516
|
+
export function appDirPayload(settings, payload, hostRequirements, schemaCache) {
|
|
517
|
+
assertAppImageIsPackable(payload, settings, schemaCache);
|
|
476
518
|
// Non-null by construction: the assertion above refuses a payload with
|
|
477
519
|
// neither, and both reads are the SAME selection it made.
|
|
478
520
|
const icon = selectAppDirIcon(payload, settings.appId);
|
|
479
521
|
const desktopPath = `${SHARE.applications}/${settings.appId}.desktop`;
|
|
480
522
|
const desktop = payload.find((entry) => entry.path === desktopPath);
|
|
481
523
|
return [
|
|
482
|
-
...payload.map((entry) => ({
|
|
524
|
+
...[...payload, ...(schemaCache === undefined ? [] : [schemaCache])].map((entry) => ({
|
|
525
|
+
...entry,
|
|
526
|
+
path: `${APPDIR_PREFIX_DIR}/${entry.path}`,
|
|
527
|
+
})),
|
|
483
528
|
{ path: APPRUN_NAME, mode: 0o755, data: encodeUtf8(renderAppRun(settings, hostRequirements)) },
|
|
484
529
|
// THE SAME BYTES, TWICE, and that is the AppImage specification rather
|
|
485
530
|
// than a duplication to lift: the entry under `usr/share/applications` is
|
|
@@ -396,23 +396,56 @@ export const FORMATS = {
|
|
|
396
396
|
// is: the container is an ELF runtime for Linux with a Linux filesystem
|
|
397
397
|
// behind it, so the format is bound the way the application is.
|
|
398
398
|
finishOn: ['linux'],
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// `
|
|
405
|
-
//
|
|
406
|
-
|
|
399
|
+
// THE SCHEMA COMPILER IS DECLARED HERE, and this row is the one place
|
|
400
|
+
// in the table where it sits on a PACK path. It used to be absent, on
|
|
401
|
+
// an argument that read well and was false: "this layout has an install
|
|
402
|
+
// step in every other format, and `compileSchemasForStage` has already
|
|
403
|
+
// run at assembly time, so declaring the compiler here would refuse a
|
|
404
|
+
// `--from-stage` pack that works." The second clause never held.
|
|
405
|
+
// `compileSchemasForStage` is skipped for `layout.os === 'linux'` —
|
|
406
|
+
// deliberately, because the Linux stage is SHARED with the `.deb` and
|
|
407
|
+
// the `.rpm`, whose `share/glib-2.0/schemas` is the SYSTEM directory
|
|
408
|
+
// that a postinst compiles and that our cache must never be installed
|
|
409
|
+
// over. So no Linux stage carries `gschemas.compiled`, a `--from-stage`
|
|
410
|
+
// AppImage pack has to produce it, and this row needs the tool.
|
|
411
|
+
//
|
|
412
|
+
// MEASURED, not reasoned: Learn6502 0.8.0's first CI-built AppImage
|
|
413
|
+
// shipped `eu.jumplink.Learn6502.gschema.xml` with nothing beside it
|
|
414
|
+
// and died at `Gio.Settings.new()` on the first line it ran.
|
|
415
|
+
//
|
|
416
|
+
// AND ONLY THIS ONE OF THE FOUR, which is the question the fix had to
|
|
417
|
+
// answer next and which is measured rather than argued.
|
|
418
|
+
// `cacheRefreshCommands` (`scripts.ts`) names four install-time steps,
|
|
419
|
+
// and an AppImage runs none of them. Three DEGRADE and one ABORTS:
|
|
420
|
+
//
|
|
421
|
+
// * `glib-compile-schemas` — ABORTS. GSettings refuses a schema
|
|
422
|
+
// directory holding a source with no cache; there is no fallback.
|
|
423
|
+
// * `gtk-update-icon-cache` — does not. Measured against the rebuilt
|
|
424
|
+
// 0.8.0 image on GTK 4 / glib 2.88.3: with `XDG_DATA_DIRS` pointing
|
|
425
|
+
// at the mounted `usr/share` and no `icon-theme.cache` anywhere,
|
|
426
|
+
// `Gtk.IconTheme.has_icon('eu.jumplink.Learn6502')` is `true` — and
|
|
427
|
+
// `false` for a name that is not there, so the probe discriminates.
|
|
428
|
+
// GTK scans the directory; the cache is a lookup optimisation.
|
|
429
|
+
// * `update-desktop-database`, `update-mime-database` — about a
|
|
430
|
+
// SYSTEM database an install writes into. An AppImage is not
|
|
431
|
+
// installed, so there is nothing for them to update: what a desktop
|
|
432
|
+
// reads is the entry at the AppDir root, via the runtime's own
|
|
433
|
+
// integration.
|
|
434
|
+
//
|
|
435
|
+
// `share/locale` needs no step on any layout — the launcher exports
|
|
436
|
+
// `GJSIFY_LOCALE_DIR` and gettext reads the `.mo` directly.
|
|
437
|
+
requiredTools: [APPIMAGE_TOOL, SCHEMA_COMPILER],
|
|
407
438
|
// A URL AND NOT A PACKAGE NAME, and it is the first hint in this table
|
|
408
439
|
// that has to be. `flatpak-builder` and `msitools` are in Fedora and in
|
|
409
440
|
// Debian; `appimagetool` is in neither, so pointing at a distro would
|
|
410
441
|
// send the reader to an `E: Unable to locate package`. The `--stage`
|
|
411
442
|
// half is repeated here because it is the answer for a CI image that is
|
|
412
|
-
// not going to grow a hand-installed binary.
|
|
413
|
-
|
|
443
|
+
// not going to grow a hand-installed binary. The compiler IS packaged,
|
|
444
|
+
// so its half of the hint is the ordinary one.
|
|
445
|
+
installHint: 'no distribution packages appimagetool — take the release from ' +
|
|
414
446
|
'https://github.com/AppImage/appimagetool/releases, `chmod +x` it and put it on PATH as ' +
|
|
415
|
-
'`appimagetool`; or assemble here with `gjsify ship linux --stage` and pack where it is'
|
|
447
|
+
'`appimagetool`; or assemble here with `gjsify ship linux --stage` and pack where it is. ' +
|
|
448
|
+
`The schema compiler is packaged: ${SCHEMA_COMPILER_HINT}`,
|
|
416
449
|
oracle: {
|
|
417
450
|
// TWO READERS, NEITHER OF THEM appimagetool AND NEITHER OF THEM THE
|
|
418
451
|
// ARTIFACT'S OWN RUNTIME — which is what `--appimage-offset` and
|
|
@@ -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,191 @@
|
|
|
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
|
+
/**
|
|
162
|
+
* Render an SVG at every size through librsvg, in a child GJS.
|
|
163
|
+
*
|
|
164
|
+
* Every PNG that comes back is read for its IHDR before it is returned: the size
|
|
165
|
+
* the child rendered is the size the caller asked for or the whole batch is
|
|
166
|
+
* refused, because the writer that would embed it trusts the map's key.
|
|
167
|
+
*/
|
|
168
|
+
export declare function rasterizeSvg(input: RasterizeInput): Promise<Map<number, Uint8Array>>;
|
|
169
|
+
export interface ResolveAppIconInput {
|
|
170
|
+
/** Absolute paths of the project's icons — `ShipSettings.iconFiles`. */
|
|
171
|
+
iconFiles: readonly string[];
|
|
172
|
+
appId: string;
|
|
173
|
+
/** The square sizes the layout's writer needs, from {@link Layout.icon}. */
|
|
174
|
+
sizes: readonly number[];
|
|
175
|
+
/** What the sizes are FOR, in the refusal: "the Windows launcher's icon". */
|
|
176
|
+
target: string;
|
|
177
|
+
/** The renderer, injectable so the selection is testable without a GJS. */
|
|
178
|
+
rasterize?: (input: RasterizeInput) => Promise<Map<number, Uint8Array>>;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The raster set a layout needs, from whatever the project declares.
|
|
182
|
+
*
|
|
183
|
+
* Sized PNGs the project ships are used at their (IHDR-read) size; every size
|
|
184
|
+
* they leave uncovered is rendered from the scalable SVG; a size neither can
|
|
185
|
+
* answer is a refusal that names the file to add. The `-symbolic` icon is never
|
|
186
|
+
* a source: it is the monochrome status-bar glyph, and an app icon drawn from it
|
|
187
|
+
* would be a grey silhouette on both platforms.
|
|
188
|
+
*/
|
|
189
|
+
export declare function resolveAppIcon(input: ResolveAppIconInput): Promise<AppIconRasters>;
|
|
190
|
+
/** The sizes in a raster set, ascending — for log lines and error messages. */
|
|
191
|
+
export declare function iconSizes(icon: AppIconRasters): number[];
|