@gjsify/cli 0.50.0 → 0.51.1
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 +45 -44
- package/lib/commands/ship.js +103 -4
- package/lib/utils/ship/appimage.d.ts +372 -0
- package/lib/utils/ship/appimage.js +716 -0
- package/lib/utils/ship/depends.d.ts +20 -0
- package/lib/utils/ship/depends.js +31 -16
- package/lib/utils/ship/formats.d.ts +9 -0
- package/lib/utils/ship/formats.js +183 -0
- package/lib/utils/ship/schemas.d.ts +46 -0
- package/lib/utils/ship/schemas.js +92 -11
- package/lib/utils/ship/settings.d.ts +14 -0
- package/lib/utils/ship/settings.js +19 -1
- package/lib/utils/ship/types.d.ts +17 -1
- package/package.json +19 -19
|
@@ -91,6 +91,26 @@ export interface DependsInputs {
|
|
|
91
91
|
/** Minimum Node major, when one is depended on. Default {@link DEFAULT_NODE_FLOOR}. */
|
|
92
92
|
minNodeVersion?: string;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The namespaces the bundle imports that the HOST has to provide — scanned set
|
|
96
|
+
* minus everything the payload carries itself.
|
|
97
|
+
*
|
|
98
|
+
* LIFTED OUT OF {@link deriveDepends} because a second caller appeared and the
|
|
99
|
+
* question is not a `.deb`'s. `appimage.ts` asks it to write the one sentence an
|
|
100
|
+
* AppImage owes its user — *this file carries the application, and takes these
|
|
101
|
+
* from your system* — and an AppImage has no `Depends:` field, so it cannot go
|
|
102
|
+
* through the package-name table below. A copied filter would drift in the
|
|
103
|
+
* direction that is invisible: the drifted copy would still produce a plausible
|
|
104
|
+
* list, just one that no longer matches the dependencies the `.deb` beside it
|
|
105
|
+
* declares from the same payload.
|
|
106
|
+
*
|
|
107
|
+
* Deriving the carried set from the staged FILENAMES is the point, and it is
|
|
108
|
+
* `deriveDepends`' reasoning unchanged: `Gwebgl-0.1.typelib` answers for both
|
|
109
|
+
* `Gwebgl-0.1` and `Gwebgl`, so a project that declared a namespace bundled
|
|
110
|
+
* without the file being there gets an artifact that names its real requirement
|
|
111
|
+
* instead of one that installs and dies at the first import.
|
|
112
|
+
*/
|
|
113
|
+
export declare function hostProvidedNamespaces(namespaces: readonly string[], bundledTypelibs?: readonly string[]): string[];
|
|
94
114
|
/**
|
|
95
115
|
* The `Depends:` / `Requires:` list for one format.
|
|
96
116
|
*
|
|
@@ -256,6 +256,36 @@ const TYPELIB_PACKAGES = {
|
|
|
256
256
|
* whose bundle reaches one, which includes every `@gjsify/http` server.
|
|
257
257
|
*/
|
|
258
258
|
const BUNDLED_TYPELIB = /^Gjsify[A-Z]/;
|
|
259
|
+
/**
|
|
260
|
+
* The namespaces the bundle imports that the HOST has to provide — scanned set
|
|
261
|
+
* minus everything the payload carries itself.
|
|
262
|
+
*
|
|
263
|
+
* LIFTED OUT OF {@link deriveDepends} because a second caller appeared and the
|
|
264
|
+
* question is not a `.deb`'s. `appimage.ts` asks it to write the one sentence an
|
|
265
|
+
* AppImage owes its user — *this file carries the application, and takes these
|
|
266
|
+
* from your system* — and an AppImage has no `Depends:` field, so it cannot go
|
|
267
|
+
* through the package-name table below. A copied filter would drift in the
|
|
268
|
+
* direction that is invisible: the drifted copy would still produce a plausible
|
|
269
|
+
* list, just one that no longer matches the dependencies the `.deb` beside it
|
|
270
|
+
* declares from the same payload.
|
|
271
|
+
*
|
|
272
|
+
* Deriving the carried set from the staged FILENAMES is the point, and it is
|
|
273
|
+
* `deriveDepends`' reasoning unchanged: `Gwebgl-0.1.typelib` answers for both
|
|
274
|
+
* `Gwebgl-0.1` and `Gwebgl`, so a project that declared a namespace bundled
|
|
275
|
+
* without the file being there gets an artifact that names its real requirement
|
|
276
|
+
* instead of one that installs and dies at the first import.
|
|
277
|
+
*/
|
|
278
|
+
export function hostProvidedNamespaces(namespaces, bundledTypelibs = []) {
|
|
279
|
+
const shipped = new Set();
|
|
280
|
+
for (const file of bundledTypelibs) {
|
|
281
|
+
const match = /([A-Za-z0-9]+)-([\d.]+)\.typelib$/.exec(file);
|
|
282
|
+
if (!match)
|
|
283
|
+
continue;
|
|
284
|
+
shipped.add(`${match[1]}-${match[2]}`);
|
|
285
|
+
shipped.add(match[1]);
|
|
286
|
+
}
|
|
287
|
+
return [...new Set(namespaces)].sort().filter((ns) => !BUNDLED_TYPELIB.test(ns) && !shipped.has(ns));
|
|
288
|
+
}
|
|
259
289
|
/**
|
|
260
290
|
* The `Depends:` / `Requires:` list for one format.
|
|
261
291
|
*
|
|
@@ -271,22 +301,7 @@ export function deriveDepends(format, inputs) {
|
|
|
271
301
|
? [`${NODE_PACKAGE[format]} >= ${inputs.minNodeVersion ?? DEFAULT_NODE_FLOOR}`]
|
|
272
302
|
: [`gjs >= ${inputs.minGjsVersion ?? DEFAULT_GJS_FLOOR}`];
|
|
273
303
|
const unmapped = [];
|
|
274
|
-
|
|
275
|
-
// separate declaration: `Gwebgl-0.1.typelib` → `Gwebgl-0.1` and `Gwebgl`. Deriving it is the
|
|
276
|
-
// point — a project that declared a namespace bundled without the file being there would get
|
|
277
|
-
// a package that installs and dies at the first import, which is exactly what this check
|
|
278
|
-
// exists to prevent.
|
|
279
|
-
const shipped = new Set();
|
|
280
|
-
for (const file of inputs.bundledTypelibs ?? []) {
|
|
281
|
-
const match = /([A-Za-z0-9]+)-([\d.]+)\.typelib$/.exec(file);
|
|
282
|
-
if (!match)
|
|
283
|
-
continue;
|
|
284
|
-
shipped.add(`${match[1]}-${match[2]}`);
|
|
285
|
-
shipped.add(match[1]);
|
|
286
|
-
}
|
|
287
|
-
for (const namespace of [...new Set(inputs.namespaces)].sort()) {
|
|
288
|
-
if (BUNDLED_TYPELIB.test(namespace) || shipped.has(namespace))
|
|
289
|
-
continue;
|
|
304
|
+
for (const namespace of hostProvidedNamespaces(inputs.namespaces, inputs.bundledTypelibs)) {
|
|
290
305
|
const entry = lookupTypelib(namespace, inputs.typelibPackages);
|
|
291
306
|
if (entry === undefined) {
|
|
292
307
|
unmapped.push(namespace);
|
|
@@ -138,4 +138,13 @@ export declare function configuredFormats(raw: readonly string[], layout: Layout
|
|
|
138
138
|
* the two questions are separate, and a `.app` is precisely the item Apple's
|
|
139
139
|
* remedy above tells you to staple after submitting the archive around it.
|
|
140
140
|
*/
|
|
141
|
+
/**
|
|
142
|
+
* Refuse a project whose `kind` no chosen container can hold.
|
|
143
|
+
*
|
|
144
|
+
* DERIVED FROM THE TABLE (`FormatDescriptor.requiresDesktopEntry`), never from a
|
|
145
|
+
* list of format ids here — a fourth format that also needs an entry would
|
|
146
|
+
* otherwise be refused by appimagetool's own "Desktop file not found, aborting"
|
|
147
|
+
* after a full build, which is the message this replaced.
|
|
148
|
+
*/
|
|
149
|
+
export declare function assertKindCanPack(formats: readonly FormatDescriptor[], kind: 'app' | 'cli'): void;
|
|
141
150
|
export declare function canCarryTicket(id: FormatId): boolean;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// and one filename. What it also proved is what the design did NOT predict —
|
|
9
9
|
// a format can be host-bound (§ A3), so `host` is a descriptor field now.
|
|
10
10
|
import { isOnPath } from '../check-system-deps.js';
|
|
11
|
+
import { APPIMAGE_TOOL } from './appimage.js';
|
|
11
12
|
import { DMG_TOOL } from './dmg.js';
|
|
12
13
|
import { LAYOUTS, LAYOUT_NAMES } from './layout.js';
|
|
13
14
|
import { SCHEMA_COMPILER, SCHEMA_COMPILER_HINT } from './schemas.js';
|
|
@@ -72,6 +73,33 @@ function rpmArch(arch, archIndependent) {
|
|
|
72
73
|
function flatpakArch(arch, _archIndependent) {
|
|
73
74
|
return lookupArch(FLATPAK_ARCH, arch, 'Flatpak');
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* `process.arch` → the name appimagetool uses, which is `uname -m`'s.
|
|
78
|
+
*
|
|
79
|
+
* A TABLE OF ITS OWN even though it currently equals `RPM_ARCH` on every row it
|
|
80
|
+
* has, and the reason is that the two answer different questions: rpm's is a
|
|
81
|
+
* package-manager namespace with `noarch`, `i686` and `ppc64le` in it, while this
|
|
82
|
+
* one is the string appimagetool puts in `$ARCH` and matches against the runtimes
|
|
83
|
+
* it can embed. Sharing the table would make an rpm row addition silently claim
|
|
84
|
+
* an AppImage architecture that has no runtime — `ppc64le` is exactly that case
|
|
85
|
+
* today. So the short list is the honest one, and an arch not in it is REFUSED
|
|
86
|
+
* rather than guessed, which is `FLATPAK_ARCH`'s rule for `FLATPAK_ARCH`'s reason:
|
|
87
|
+
* the value ends up in a filename a user is asked to download.
|
|
88
|
+
*
|
|
89
|
+
* `archIndependent` is IGNORED, like Flatpak's and unlike deb's and rpm's. There
|
|
90
|
+
* is no `noarch` AppImage: the file BEGINS with an ELF runtime, so a payload of
|
|
91
|
+
* pure JavaScript still ships in an x86-64 executable and labelling it otherwise
|
|
92
|
+
* would name a file nothing can run.
|
|
93
|
+
*/
|
|
94
|
+
const APPIMAGE_ARCH = {
|
|
95
|
+
x64: 'x86_64',
|
|
96
|
+
arm64: 'aarch64',
|
|
97
|
+
ia32: 'i686',
|
|
98
|
+
arm: 'armhf',
|
|
99
|
+
};
|
|
100
|
+
function appImageArch(arch, _archIndependent) {
|
|
101
|
+
return lookupArch(APPIMAGE_ARCH, arch, 'AppImage');
|
|
102
|
+
}
|
|
75
103
|
/**
|
|
76
104
|
* `process.arch` → the name Apple's tools use.
|
|
77
105
|
*
|
|
@@ -344,6 +372,141 @@ export const FORMATS = {
|
|
|
344
372
|
fileName: (s, archLabel) => `${s.appId}-${s.version}-${s.release}.${archLabel}.flatpak`,
|
|
345
373
|
artifactKind: 'file',
|
|
346
374
|
},
|
|
375
|
+
// THE FOURTH CONTAINER OVER THE LINUX TREE, and the first row added to this
|
|
376
|
+
// table that needed NOTHING staged: `deb`, `rpm` and `flatpak` already wrap
|
|
377
|
+
// this exact payload, so ADR 0024 § 2's "one payload, a handful of layouts"
|
|
378
|
+
// is here a row plus a packer and no change to `plan.ts` at all. ADR 0024
|
|
379
|
+
// § 9 deferred it and § A24 lifts the deferral; `appimage.ts`'s header
|
|
380
|
+
// carries the argument, and the short form is that the two promises an
|
|
381
|
+
// AppImage can keep with no relocatable Linux closure — no install, no root
|
|
382
|
+
// — are the two nothing else in this table offers, and the third is
|
|
383
|
+
// DECLARED rather than implied.
|
|
384
|
+
appimage: {
|
|
385
|
+
id: 'appimage',
|
|
386
|
+
layoutOs: 'linux',
|
|
387
|
+
// `/usr`, exactly as the `.deb` — and that is the whole layout difference
|
|
388
|
+
// between the two formats. An AppImage mounts its squashfs and the AppDir's
|
|
389
|
+
// `usr/` becomes the prefix, which the staged launcher computes for itself
|
|
390
|
+
// (ADR 0024 § 3): `readlink -f "$0"` then two `dirname`s, measured inside a
|
|
391
|
+
// mount as `/tmp/.mount_xxxx/usr`. So the same `bin/<name>` works under
|
|
392
|
+
// `/usr`, under `/app` and under a mountpoint nobody chose.
|
|
393
|
+
prefix: '/usr',
|
|
394
|
+
host: {
|
|
395
|
+
// Linux-bound the way the Flatpak row is, and NOT the way the `.dmg`
|
|
396
|
+
// is: the container is an ELF runtime for Linux with a Linux filesystem
|
|
397
|
+
// behind it, so the format is bound the way the application is.
|
|
398
|
+
finishOn: ['linux'],
|
|
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],
|
|
438
|
+
// A URL AND NOT A PACKAGE NAME, and it is the first hint in this table
|
|
439
|
+
// that has to be. `flatpak-builder` and `msitools` are in Fedora and in
|
|
440
|
+
// Debian; `appimagetool` is in neither, so pointing at a distro would
|
|
441
|
+
// send the reader to an `E: Unable to locate package`. The `--stage`
|
|
442
|
+
// half is repeated here because it is the answer for a CI image that is
|
|
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 ' +
|
|
446
|
+
'https://github.com/AppImage/appimagetool/releases, `chmod +x` it and put it on PATH as ' +
|
|
447
|
+
'`appimagetool`; or assemble here with `gjsify ship linux --stage` and pack where it is. ' +
|
|
448
|
+
`The schema compiler is packaged: ${SCHEMA_COMPILER_HINT}`,
|
|
449
|
+
oracle: {
|
|
450
|
+
// TWO READERS, NEITHER OF THEM appimagetool AND NEITHER OF THEM THE
|
|
451
|
+
// ARTIFACT'S OWN RUNTIME — which is what `--appimage-offset` and
|
|
452
|
+
// `--appimage-extract` would be, the format reading what the format
|
|
453
|
+
// wrote (ADR 0024 § A3).
|
|
454
|
+
//
|
|
455
|
+
// * CPython reads the ELF SECTION-HEADER TABLE for the offset the
|
|
456
|
+
// filesystem starts at — `e_shoff + e_shnum * e_shentsize`,
|
|
457
|
+
// twelve bytes of `struct.unpack_from`, straight out of the ELF
|
|
458
|
+
// specification. Measured against `--appimage-offset` on a real
|
|
459
|
+
// artifact: 944632 both ways. That agreement is the
|
|
460
|
+
// discriminator for the whole container, because a truncated or
|
|
461
|
+
// empty image has no squashfs superblock there. `python3` and
|
|
462
|
+
// NOT `readelf`, which the first draft of this row declared and
|
|
463
|
+
// `verify-appimage.py` never execs — the field names the tools
|
|
464
|
+
// the oracle RUNS, and binutils is not one of them here. Same
|
|
465
|
+
// reader family the two `macos-app` rows already declare.
|
|
466
|
+
// * `unsquashfs` (squashfs-tools) then lists the filesystem at
|
|
467
|
+
// that offset with names, modes and sizes, which
|
|
468
|
+
// `.github/ship-oracle/verify-appimage.py` compares against the
|
|
469
|
+
// stage manifest. A different PROGRAM from the bundled
|
|
470
|
+
// mksquashfs appimagetool packs with, and the leg that runs it
|
|
471
|
+
// is the Linux one that already reads the `.deb`.
|
|
472
|
+
readWith: ['python3', 'unsquashfs'],
|
|
473
|
+
readOn: ['linux'],
|
|
474
|
+
selfReading: false,
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
// No `Depends:` FIELD — which is not the same as no dependencies, and this
|
|
478
|
+
// row is the one place in the table where that distinction has teeth. A
|
|
479
|
+
// `.deb` declares `gir1.2-gtk-4.0` and apt refuses to install without it;
|
|
480
|
+
// an AppImage has nowhere to write that down, so `appImageHostRequirements`
|
|
481
|
+
// derives the same set from the same scan and `ship` PRINTS it while
|
|
482
|
+
// `AppRun` carries the interpreter half as a refusal. See `depends.ts`'s
|
|
483
|
+
// `DistroFormatId`: this is `null` because there is no field, never because
|
|
484
|
+
// there is nothing to say.
|
|
485
|
+
depends: null,
|
|
486
|
+
// BOTH, like `deb` and `rpm` and for the same reason: the interpreter comes
|
|
487
|
+
// from the machine. The difference from those two is that nothing declares
|
|
488
|
+
// it — see the `depends` note above and `interpreterGap` below.
|
|
489
|
+
interpreters: ['gjs', 'node'],
|
|
490
|
+
interpreterGap: 'an AppImage carries the application and takes its interpreter from the host, exactly as a distro ' +
|
|
491
|
+
'package does — but with no dependency field to declare it in, so the requirement is printed at ' +
|
|
492
|
+
'pack time and checked by `AppRun` at launch instead',
|
|
493
|
+
licenseDest: SHARE_LICENSE_DEST,
|
|
494
|
+
licenseKind: 'plain',
|
|
495
|
+
archName: appImageArch,
|
|
496
|
+
// The BINARY name, like the `.dmg` and the two zips and for their reason:
|
|
497
|
+
// this artifact is a DOWNLOAD that lands in a browser's folder beside other
|
|
498
|
+
// files, so it carries the version and the architecture and avoids the
|
|
499
|
+
// spaces a display name may contain. Not the app id, which is the Flatpak's
|
|
500
|
+
// convention because a Flatpak ref IS the id; an AppImage installs as
|
|
501
|
+
// nothing and is addressed by its path.
|
|
502
|
+
fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.${archLabel}.AppImage`,
|
|
503
|
+
artifactKind: 'file',
|
|
504
|
+
// THE ONLY ROW THAT SETS IT. appimagetool refuses an AppDir with no
|
|
505
|
+
// `.desktop` at its root, and `kind: 'cli'` stages none by design — a
|
|
506
|
+
// refusal `gjsify.ship.kind` decides on its own, so it belongs before the
|
|
507
|
+
// build beside the tool gate rather than after it inside the packer.
|
|
508
|
+
requiresDesktopEntry: true,
|
|
509
|
+
},
|
|
347
510
|
// ── macOS (#1354 M2a) ────────────────────────────────────────────────
|
|
348
511
|
//
|
|
349
512
|
// TWO ROWS FOR ONE TREE, and the pair is what `layoutOs` was added for. The
|
|
@@ -995,6 +1158,25 @@ export function configuredFormats(raw, layout) {
|
|
|
995
1158
|
* the two questions are separate, and a `.app` is precisely the item Apple's
|
|
996
1159
|
* remedy above tells you to staple after submitting the archive around it.
|
|
997
1160
|
*/
|
|
1161
|
+
/**
|
|
1162
|
+
* Refuse a project whose `kind` no chosen container can hold.
|
|
1163
|
+
*
|
|
1164
|
+
* DERIVED FROM THE TABLE (`FormatDescriptor.requiresDesktopEntry`), never from a
|
|
1165
|
+
* list of format ids here — a fourth format that also needs an entry would
|
|
1166
|
+
* otherwise be refused by appimagetool's own "Desktop file not found, aborting"
|
|
1167
|
+
* after a full build, which is the message this replaced.
|
|
1168
|
+
*/
|
|
1169
|
+
export function assertKindCanPack(formats, kind) {
|
|
1170
|
+
if (kind === 'app')
|
|
1171
|
+
return;
|
|
1172
|
+
const needsEntry = formats.filter((format) => format.requiresDesktopEntry);
|
|
1173
|
+
if (needsEntry.length === 0)
|
|
1174
|
+
return;
|
|
1175
|
+
throw new Error(`gjsify ship: ${needsEntry.map((format) => format.id).join(', ')} cannot package a ` +
|
|
1176
|
+
'`kind: "cli"` project — the container needs a desktop entry at its root and a CLI stages none ' +
|
|
1177
|
+
'by design. Set `gjsify.ship.kind` to "app" if this is a GUI application, or ship it as a `.deb`, ' +
|
|
1178
|
+
'an `.rpm` or a Flatpak, none of which need one.');
|
|
1179
|
+
}
|
|
998
1180
|
export function canCarryTicket(id) {
|
|
999
1181
|
switch (id) {
|
|
1000
1182
|
case 'macos-app':
|
|
@@ -1007,6 +1189,7 @@ export function canCarryTicket(id) {
|
|
|
1007
1189
|
case 'deb':
|
|
1008
1190
|
case 'rpm':
|
|
1009
1191
|
case 'flatpak':
|
|
1192
|
+
case 'appimage':
|
|
1010
1193
|
case 'windows-dir':
|
|
1011
1194
|
case 'windows-dir-zip':
|
|
1012
1195
|
case 'msi':
|
|
@@ -59,3 +59,49 @@ export interface CompileSchemasInput {
|
|
|
59
59
|
* anyone claims otherwise.
|
|
60
60
|
*/
|
|
61
61
|
export declare function compileSchemasForStage(input: CompileSchemasInput): Promise<StagedFile[]>;
|
|
62
|
+
/**
|
|
63
|
+
* The same compile, driven from a PAYLOAD instead of from the project's files.
|
|
64
|
+
*
|
|
65
|
+
* WHY A SECOND ENTRY POINT AND NOT A SECOND CALLER OF THE FIRST. The AppImage is
|
|
66
|
+
* the one format whose LAYOUT has an install step and whose ARTIFACT does not
|
|
67
|
+
* (`formats.ts`, the `appimage` row), so the compile cannot move to the place
|
|
68
|
+
* {@link compileSchemasForStage} runs: the Linux stage is SHARED with the `.deb`
|
|
69
|
+
* and the `.rpm`, whose `share/glib-2.0/schemas` is the SYSTEM directory. A cache
|
|
70
|
+
* staged there is our 700 bytes installed over every other package's schemas —
|
|
71
|
+
* regenerated by the postinst on dpkg, and REMOVED from the system on `rpm -e`.
|
|
72
|
+
* So the cache is produced on the AppImage's own pack path, into the AppDir, and
|
|
73
|
+
* nowhere else.
|
|
74
|
+
*
|
|
75
|
+
* FROM THE PAYLOAD AND NOT FROM `settings.schemaFiles`, which is the input that
|
|
76
|
+
* makes `--from-stage` work: a stage that crossed from another host carries the
|
|
77
|
+
* `.gschema.xml` bytes and not the project directory they came from. The same
|
|
78
|
+
* argument `packOne`'s header already makes for every other container here.
|
|
79
|
+
*
|
|
80
|
+
* `undefined` RATHER THAN AN EMPTY LIST when the payload has no schema, because
|
|
81
|
+
* the caller has to tell "nothing to compile" from "a cache is expected" — see
|
|
82
|
+
* `assertAppImageIsPackable`, which refuses the second case.
|
|
83
|
+
*/
|
|
84
|
+
export declare function compileSchemasForPayload(input: {
|
|
85
|
+
payload: readonly {
|
|
86
|
+
path: string;
|
|
87
|
+
data: Uint8Array;
|
|
88
|
+
}[];
|
|
89
|
+
workDir: string;
|
|
90
|
+
}): Promise<{
|
|
91
|
+
path: string;
|
|
92
|
+
mode: number;
|
|
93
|
+
data: Uint8Array;
|
|
94
|
+
} | undefined>;
|
|
95
|
+
/**
|
|
96
|
+
* Whether a prefix-relative payload path is a GSettings schema SOURCE.
|
|
97
|
+
*
|
|
98
|
+
* Exported because two modules ask it of the same payload and a second spelling
|
|
99
|
+
* would let them disagree: this module decides what to compile, and
|
|
100
|
+
* `assertAppImageIsPackable` decides whether a compiled cache was owed.
|
|
101
|
+
*
|
|
102
|
+
* The extension is `glib-compile-schemas`'s own — it reads `*.gschema.xml` from
|
|
103
|
+
* the directory it is given and ignores everything else — so a payload that
|
|
104
|
+
* staged `foo.xml` under `share/glib-2.0/schemas` is a file GSettings never reads
|
|
105
|
+
* and this correctly says nothing about it.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isSchemaSource(path: string): boolean;
|
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
// installs and does not start, which is the class this whole command exists
|
|
11
11
|
// against.
|
|
12
12
|
//
|
|
13
|
+
// AND THE PREDICATE IS THE ARTIFACT, NOT THE LAYOUT — which is what the first
|
|
14
|
+
// version of this module got wrong and what the AppImage measured. The condition
|
|
15
|
+
// was written as `layout.os === 'linux' ? [] : compile` (`commands/ship.ts`), and
|
|
16
|
+
// it stood for a year because on Linux every format DID have an install step.
|
|
17
|
+
// An AppImage has none — that is the entire point of the format — so it inherited
|
|
18
|
+
// the Linux layout, inherited the skip, and shipped an artifact that died on
|
|
19
|
+
// `Gio.Settings.new()` at its first line. Learn6502 0.8.0's AppImage, measured:
|
|
20
|
+
//
|
|
21
|
+
// JS ERROR: Error: GSettings schema eu.jumplink.Learn6502 not found
|
|
22
|
+
//
|
|
23
|
+
// A layout is not a claim about install steps. {@link compileSchemasForPayload}
|
|
24
|
+
// is what the format that has none calls for itself.
|
|
25
|
+
//
|
|
13
26
|
// AT STAGE TIME, not at pack time, and that is the decision worth stating.
|
|
14
27
|
// A stage is a deliverable in its own right — it is what crosses to the host
|
|
15
28
|
// that finishes a `.dmg` or an `.msi` — and the warning `gjsify ship` prints
|
|
@@ -25,8 +38,8 @@
|
|
|
25
38
|
// declare it: they wrap a tree that is already assembled, so a `--from-stage`
|
|
26
39
|
// pack of one needs no GLib at all (the `macos-app-dmg` row says so).
|
|
27
40
|
import { execFile } from 'node:child_process';
|
|
28
|
-
import { basename, join } from 'node:path';
|
|
29
|
-
import { copyFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
41
|
+
import { basename, join, posix } from 'node:path';
|
|
42
|
+
import { copyFileSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
30
43
|
import { promisify } from 'node:util';
|
|
31
44
|
import { SHARE } from './share-dirs.js';
|
|
32
45
|
const execFileAsync = promisify(execFile);
|
|
@@ -90,13 +103,88 @@ export async function compileSchemasForStage(input) {
|
|
|
90
103
|
mkdirSync(input.workDir, { recursive: true });
|
|
91
104
|
for (const schema of input.schemaFiles)
|
|
92
105
|
copyFileSync(schema, join(input.workDir, basename(schema)));
|
|
106
|
+
await runSchemaCompiler(input.workDir);
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
path: SCHEMA_CACHE,
|
|
110
|
+
mode: 0o644,
|
|
111
|
+
source: { kind: 'file', path: join(input.workDir, 'gschemas.compiled') },
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The same compile, driven from a PAYLOAD instead of from the project's files.
|
|
117
|
+
*
|
|
118
|
+
* WHY A SECOND ENTRY POINT AND NOT A SECOND CALLER OF THE FIRST. The AppImage is
|
|
119
|
+
* the one format whose LAYOUT has an install step and whose ARTIFACT does not
|
|
120
|
+
* (`formats.ts`, the `appimage` row), so the compile cannot move to the place
|
|
121
|
+
* {@link compileSchemasForStage} runs: the Linux stage is SHARED with the `.deb`
|
|
122
|
+
* and the `.rpm`, whose `share/glib-2.0/schemas` is the SYSTEM directory. A cache
|
|
123
|
+
* staged there is our 700 bytes installed over every other package's schemas —
|
|
124
|
+
* regenerated by the postinst on dpkg, and REMOVED from the system on `rpm -e`.
|
|
125
|
+
* So the cache is produced on the AppImage's own pack path, into the AppDir, and
|
|
126
|
+
* nowhere else.
|
|
127
|
+
*
|
|
128
|
+
* FROM THE PAYLOAD AND NOT FROM `settings.schemaFiles`, which is the input that
|
|
129
|
+
* makes `--from-stage` work: a stage that crossed from another host carries the
|
|
130
|
+
* `.gschema.xml` bytes and not the project directory they came from. The same
|
|
131
|
+
* argument `packOne`'s header already makes for every other container here.
|
|
132
|
+
*
|
|
133
|
+
* `undefined` RATHER THAN AN EMPTY LIST when the payload has no schema, because
|
|
134
|
+
* the caller has to tell "nothing to compile" from "a cache is expected" — see
|
|
135
|
+
* `assertAppImageIsPackable`, which refuses the second case.
|
|
136
|
+
*/
|
|
137
|
+
export async function compileSchemasForPayload(input) {
|
|
138
|
+
const sources = input.payload.filter((entry) => isSchemaSource(entry.path));
|
|
139
|
+
if (sources.length === 0)
|
|
140
|
+
return undefined;
|
|
141
|
+
rmSync(input.workDir, { recursive: true, force: true, maxRetries: 5 });
|
|
142
|
+
mkdirSync(input.workDir, { recursive: true });
|
|
143
|
+
for (const source of sources) {
|
|
144
|
+
// `posix.basename` AND NOT `basename`, which the function above correctly
|
|
145
|
+
// uses one screen up. The two take different kinds of string: that one is
|
|
146
|
+
// handed a path read off THIS filesystem, where the host is the authority;
|
|
147
|
+
// this one is handed a `PayloadEntry.path`, which is POSIX-separated by its
|
|
148
|
+
// own type contract and built with `posix.join`. On win32 the host-aware
|
|
149
|
+
// `basename` also separates on `\`, so a Linux schema whose filename
|
|
150
|
+
// legitimately contains a backslash would be silently truncated while
|
|
151
|
+
// assembling a payload that is not the host's.
|
|
152
|
+
writeFileSync(join(input.workDir, posix.basename(source.path)), source.data);
|
|
153
|
+
}
|
|
154
|
+
await runSchemaCompiler(input.workDir);
|
|
155
|
+
return { path: SCHEMA_CACHE, mode: 0o644, data: readFileSync(join(input.workDir, 'gschemas.compiled')) };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether a prefix-relative payload path is a GSettings schema SOURCE.
|
|
159
|
+
*
|
|
160
|
+
* Exported because two modules ask it of the same payload and a second spelling
|
|
161
|
+
* would let them disagree: this module decides what to compile, and
|
|
162
|
+
* `assertAppImageIsPackable` decides whether a compiled cache was owed.
|
|
163
|
+
*
|
|
164
|
+
* The extension is `glib-compile-schemas`'s own — it reads `*.gschema.xml` from
|
|
165
|
+
* the directory it is given and ignores everything else — so a payload that
|
|
166
|
+
* staged `foo.xml` under `share/glib-2.0/schemas` is a file GSettings never reads
|
|
167
|
+
* and this correctly says nothing about it.
|
|
168
|
+
*/
|
|
169
|
+
export function isSchemaSource(path) {
|
|
170
|
+
return path.startsWith(`${SHARE.schemas}/`) && path.endsWith('.gschema.xml');
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* `glib-compile-schemas --strict` over a directory, with the two failures named.
|
|
174
|
+
*
|
|
175
|
+
* Split out when the AppImage needed the same compile from different bytes. The
|
|
176
|
+
* MESSAGES are the reason it is one function rather than two copies: they are the
|
|
177
|
+
* whole value of this module over a bare `execFile`, and an ENOENT that named the
|
|
178
|
+
* package on one path and not the other is the drift this shape cannot have.
|
|
179
|
+
*/
|
|
180
|
+
async function runSchemaCompiler(workDir) {
|
|
93
181
|
try {
|
|
94
|
-
await execFileAsync(SCHEMA_COMPILER, ['--strict', `--targetdir=${
|
|
182
|
+
await execFileAsync(SCHEMA_COMPILER, ['--strict', `--targetdir=${workDir}`, workDir]);
|
|
95
183
|
}
|
|
96
184
|
catch (error) {
|
|
97
185
|
const failure = error;
|
|
98
186
|
if (failure.code === 'ENOENT') {
|
|
99
|
-
throw new Error(`gjsify ship: this
|
|
187
|
+
throw new Error(`gjsify ship: this artifact has no install step, so \`${SCHEMA_COMPILER}\` has to run HERE to ` +
|
|
100
188
|
`turn the payload's schemas into \`${SCHEMA_CACHE}\` — and it is not on PATH. ` +
|
|
101
189
|
`Install it (${SCHEMA_COMPILER_HINT}). Without the compiled cache the launcher points ` +
|
|
102
190
|
'XDG_DATA_DIRS at a schema directory GSettings aborts on, so the bundle would build and ' +
|
|
@@ -108,11 +196,4 @@ export async function compileSchemasForStage(input) {
|
|
|
108
196
|
'cache\n is written without it, so the app still aborts on the schema that was dropped. ' +
|
|
109
197
|
'Fix the\n file it names, or drop it from `gjsify.ship.schemas`.');
|
|
110
198
|
}
|
|
111
|
-
return [
|
|
112
|
-
{
|
|
113
|
-
path: SCHEMA_CACHE,
|
|
114
|
-
mode: 0o644,
|
|
115
|
-
source: { kind: 'file', path: join(input.workDir, 'gschemas.compiled') },
|
|
116
|
-
},
|
|
117
|
-
];
|
|
118
199
|
}
|
|
@@ -144,6 +144,20 @@ export interface ResolvedSettings {
|
|
|
144
144
|
metadata: AppMetadata;
|
|
145
145
|
warnings: string[];
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* `gjsify.ship.kind`, falling back to `gjsify.flatpak`'s and then to `'app'`.
|
|
149
|
+
*
|
|
150
|
+
* LIFTED OUT because a second caller appeared BEFORE this file runs: `ship`
|
|
151
|
+
* refuses a `kind: 'cli'` project asking for a format whose container needs a
|
|
152
|
+
* desktop entry, and it does that ahead of the project's build, where
|
|
153
|
+
* `resolveShipSettings` has not run and its `discovered` input does not exist
|
|
154
|
+
* yet. The fallback chain is `{ ...flatpak, ...definedOnly(ship) }`'s for exactly
|
|
155
|
+
* this key, and a second spelling of it is the kind that stays right until
|
|
156
|
+
* somebody changes the first one — here with the artifact-shaped consequence that
|
|
157
|
+
* a project would be refused before the build for a `kind` the stage then
|
|
158
|
+
* resolves differently.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveShipKind(ship: ConfigDataShip, flatpak: ConfigDataFlatpak): 'app' | 'cli';
|
|
147
161
|
export declare function resolveShipSettings(input: SettingsInput): ResolvedSettings;
|
|
148
162
|
/** The entry one ship target installs, which key named it, and what it must NOT carry. */
|
|
149
163
|
export interface ResolvedShipBundle {
|
|
@@ -111,6 +111,22 @@ const CATEGORY_SECTIONS = [
|
|
|
111
111
|
['System', { section: 'admin', group: 'Applications/System' }],
|
|
112
112
|
['Utility', { section: 'utils', group: 'Applications/System' }],
|
|
113
113
|
];
|
|
114
|
+
/**
|
|
115
|
+
* `gjsify.ship.kind`, falling back to `gjsify.flatpak`'s and then to `'app'`.
|
|
116
|
+
*
|
|
117
|
+
* LIFTED OUT because a second caller appeared BEFORE this file runs: `ship`
|
|
118
|
+
* refuses a `kind: 'cli'` project asking for a format whose container needs a
|
|
119
|
+
* desktop entry, and it does that ahead of the project's build, where
|
|
120
|
+
* `resolveShipSettings` has not run and its `discovered` input does not exist
|
|
121
|
+
* yet. The fallback chain is `{ ...flatpak, ...definedOnly(ship) }`'s for exactly
|
|
122
|
+
* this key, and a second spelling of it is the kind that stays right until
|
|
123
|
+
* somebody changes the first one — here with the artifact-shaped consequence that
|
|
124
|
+
* a project would be refused before the build for a `kind` the stage then
|
|
125
|
+
* resolves differently.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveShipKind(ship, flatpak) {
|
|
128
|
+
return ship.kind ?? flatpak.kind ?? 'app';
|
|
129
|
+
}
|
|
114
130
|
export function resolveShipSettings(input) {
|
|
115
131
|
const warnings = [];
|
|
116
132
|
const { pkg, ship, flatpak, discovered } = input;
|
|
@@ -132,7 +148,9 @@ export function resolveShipSettings(input) {
|
|
|
132
148
|
const binaryName = ship.binaryName ?? deriveBinaryName(pkg.name);
|
|
133
149
|
const appId = ship.appId ?? flatpak.appId ?? reverseDnsOrThrow(pkg.name, binaryName);
|
|
134
150
|
const name = metadata.name ?? titleCase(binaryName);
|
|
135
|
-
|
|
151
|
+
// Through the shared resolver, so the PRE-BUILD gate and this function cannot
|
|
152
|
+
// answer "is this a GUI application" differently — see {@link resolveShipKind}.
|
|
153
|
+
const kind = resolveShipKind(ship, flatpak);
|
|
136
154
|
// NOT resolved here: `resolveShipApp` already did, before the format list was
|
|
137
155
|
// decided from it. A second defaulting rule in this function would be the
|
|
138
156
|
// second path those two answers could come apart on.
|
|
@@ -61,7 +61,7 @@ export interface ShipArtifact {
|
|
|
61
61
|
* TEXTUALLY and fails if a comment gives it a second match, which is why this one
|
|
62
62
|
* describes the union without quoting its head.
|
|
63
63
|
*/
|
|
64
|
-
export type FormatId = 'deb' | 'rpm' | 'flatpak' | 'macos-app' | 'macos-app-zip' | 'macos-app-dmg' | 'windows-dir' | 'windows-dir-zip' | 'msi';
|
|
64
|
+
export type FormatId = 'deb' | 'rpm' | 'flatpak' | 'appimage' | 'macos-app' | 'macos-app-zip' | 'macos-app-dmg' | 'windows-dir' | 'windows-dir-zip' | 'msi';
|
|
65
65
|
/**
|
|
66
66
|
* The formats whose ARTIFACT carries a distro dependency list.
|
|
67
67
|
*
|
|
@@ -264,6 +264,22 @@ export interface FormatDescriptor {
|
|
|
264
264
|
* makes this a property of the CONTAINER rather than "macOS is special".
|
|
265
265
|
*/
|
|
266
266
|
artifactKind: 'file' | 'directory';
|
|
267
|
+
/**
|
|
268
|
+
* This container cannot hold a `kind: 'cli'` project at all.
|
|
269
|
+
*
|
|
270
|
+
* A ROW'S FACT, so the refusal fires BEFORE the project's build like
|
|
271
|
+
* `assertCanPack`'s two do, and for the identical reason: `gjsify.ship.kind`
|
|
272
|
+
* is configuration, so the answer is knowable up front and discovering it
|
|
273
|
+
* after `runProjectBuild` costs the whole build for a refusal nothing learned
|
|
274
|
+
* from building. `appimage` is the only row that sets it — appimagetool
|
|
275
|
+
* refuses an AppDir with no `.desktop` at its root, and a CLI project stages
|
|
276
|
+
* none by design. A `.deb`, an `.rpm` and a Flatpak all ship a CLI perfectly.
|
|
277
|
+
*
|
|
278
|
+
* NOT the same question as the ICON half of `assertAppImageIsPackable`, which
|
|
279
|
+
* stays on the pack path: an icon is DISCOVERED, and discovery legitimately
|
|
280
|
+
* reads files the build produces.
|
|
281
|
+
*/
|
|
282
|
+
requiresDesktopEntry?: true;
|
|
267
283
|
}
|
|
268
284
|
/** One shared-mime-info type definition (`gjsify.ship.mimeTypes`). */
|
|
269
285
|
export interface ShipMimeType {
|