@gjsify/cli 0.44.0 → 0.46.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 +202 -167
- package/lib/commands/gettext.d.ts +2 -1
- package/lib/commands/gettext.js +105 -47
- package/lib/commands/onboard.d.ts +7 -0
- package/lib/commands/onboard.js +349 -96
- package/lib/commands/ship.d.ts +3 -0
- package/lib/commands/ship.js +850 -58
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/commands/upgrade.d.ts +23 -0
- package/lib/commands/upgrade.js +95 -12
- package/lib/templates/install.mjs.tmpl +2 -2
- package/lib/types/config-data.d.ts +127 -1
- package/lib/utils/gi-search-path.js +11 -3
- package/lib/utils/msgfmt-merge.d.ts +35 -0
- package/lib/utils/msgfmt-merge.js +102 -0
- package/lib/utils/npm-otp.d.ts +27 -2
- package/lib/utils/npm-otp.js +82 -7
- package/lib/utils/onboard-discovery.d.ts +68 -0
- package/lib/utils/onboard-discovery.js +178 -0
- package/lib/utils/onboard-probe.d.ts +118 -1
- package/lib/utils/onboard-probe.js +153 -4
- package/lib/utils/prompt-output.d.ts +12 -0
- package/lib/utils/prompt-output.js +54 -0
- package/lib/utils/prompt.js +22 -2
- package/lib/utils/publishable-packages.d.ts +1 -1
- package/lib/utils/publishable-packages.js +1 -0
- package/lib/utils/resolve-npm-package.d.ts +56 -0
- package/lib/utils/resolve-npm-package.js +69 -0
- package/lib/utils/ship/app-runtime.d.ts +214 -0
- package/lib/utils/ship/app-runtime.js +429 -0
- package/lib/utils/ship/depends.d.ts +50 -0
- package/lib/utils/ship/depends.js +117 -3
- package/lib/utils/ship/discover.js +103 -1
- package/lib/utils/ship/dmg.d.ts +71 -0
- package/lib/utils/ship/dmg.js +181 -0
- package/lib/utils/ship/flatpak.js +1 -1
- package/lib/utils/ship/formats.d.ts +108 -7
- package/lib/utils/ship/formats.js +778 -15
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +419 -17
- package/lib/utils/ship/layout.d.ts +330 -0
- package/lib/utils/ship/layout.js +504 -0
- package/lib/utils/ship/localize-metadata.d.ts +22 -0
- package/lib/utils/ship/localize-metadata.js +126 -0
- package/lib/utils/ship/mime.js +1 -9
- package/lib/utils/ship/msi.d.ts +126 -0
- package/lib/utils/ship/msi.js +434 -0
- package/lib/utils/ship/node-runtime.d.ts +71 -0
- package/lib/utils/ship/node-runtime.js +122 -0
- package/lib/utils/ship/payload.d.ts +156 -5
- package/lib/utils/ship/payload.js +599 -14
- package/lib/utils/ship/pe-launcher.d.ts +29 -0
- package/lib/utils/ship/pe-launcher.js +663 -0
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +55 -7
- package/lib/utils/ship/plist.d.ts +98 -0
- package/lib/utils/ship/plist.js +256 -0
- package/lib/utils/ship/rpm.js +16 -3
- package/lib/utils/ship/schemas.d.ts +61 -0
- package/lib/utils/ship/schemas.js +118 -0
- package/lib/utils/ship/scripts.js +7 -3
- package/lib/utils/ship/settings.d.ts +91 -2
- package/lib/utils/ship/settings.js +93 -1
- package/lib/utils/ship/share-dirs.d.ts +53 -0
- package/lib/utils/ship/share-dirs.js +76 -0
- package/lib/utils/ship/signing.d.ts +311 -0
- package/lib/utils/ship/signing.js +506 -0
- package/lib/utils/ship/stage-manifest.d.ts +40 -12
- package/lib/utils/ship/stage-manifest.js +99 -13
- package/lib/utils/ship/stage-writer.d.ts +48 -0
- package/lib/utils/ship/stage-writer.js +91 -2
- package/lib/utils/ship/types.d.ts +191 -14
- package/lib/utils/ship/xml.d.ts +7 -0
- package/lib/utils/ship/xml.js +25 -0
- package/lib/utils/ship/zip.d.ts +69 -0
- package/lib/utils/ship/zip.js +245 -0
- package/lib/utils/system-gi.d.ts +20 -5
- package/lib/utils/system-gi.js +36 -13
- package/lib/utils/trust-registry.js +5 -0
- package/package.json +19 -19
|
@@ -38,6 +38,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
|
38
38
|
import { join, sep } from 'node:path';
|
|
39
39
|
import { cliVersion } from '../publish-headers.js';
|
|
40
40
|
import { FORMAT_IDS } from './formats.js';
|
|
41
|
+
import { layoutForOs, LAYOUTS, LAYOUT_NAMES } from './layout.js';
|
|
41
42
|
/** The one file a stage carries that is not payload. Lives at the stage ROOT. */
|
|
42
43
|
export const STAGE_MANIFEST_FILE = '.gjsify-ship-stage.json';
|
|
43
44
|
/**
|
|
@@ -55,18 +56,45 @@ export const STAGE_MANIFEST_FILE = '.gjsify-ship-stage.json';
|
|
|
55
56
|
* `settings.flatpak` would not skip a field, it would pack a Flatpak against
|
|
56
57
|
* whatever runtime IT defaults to — a different `org.gnome.Platform` version
|
|
57
58
|
* than the project asked for, at exit 0.
|
|
58
|
-
*/
|
|
59
|
-
export const STAGE_SCHEMA_VERSION = 2;
|
|
60
|
-
/**
|
|
61
|
-
* The OS whose layout `planStage` produces.
|
|
62
59
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
60
|
+
* 3 added `settings.minNodeVersion` and 4 added `settings.app`. Same case as 2
|
|
61
|
+
* for the same reason: an older reader ignoring `app` does not skip a field, it
|
|
62
|
+
* has no concept of an interpreter choice at all, so it seeds `gjs >= …` for a
|
|
63
|
+
* stage whose launcher — already rendered, already in the tree it is packing —
|
|
64
|
+
* execs `node`. The package then declares one interpreter and runs another.
|
|
65
|
+
*
|
|
66
|
+
* Stated as a consequence and not as an incident, because it never happened:
|
|
67
|
+
* `app` and the launcher landed together. An earlier version of this comment
|
|
68
|
+
* claimed the incident anyway, which is worth leaving on the record — a rule
|
|
69
|
+
* with an invented reason gets "simplified" back out the first time somebody
|
|
70
|
+
* notices the reason is not real.
|
|
71
|
+
*
|
|
72
|
+
* WHY TWO BUMPS IN ONE CHANGE, when neither was ever released. Because 3 and 4
|
|
73
|
+
* are DIFFERENT SHAPES and 3 briefly existed: the intermediate commit wrote
|
|
74
|
+
* `schema: 3` with no `app`. Folding `app` into 3 would have made this reader
|
|
75
|
+
* meet that stage and fail on `settings.app must be "gjs" or "node", got
|
|
76
|
+
* undefined` — fail-closed, but naming a field instead of the one thing the
|
|
77
|
+
* reader can do about it. At 4 the schema check above catches it first and says
|
|
78
|
+
* "re-run the `--stage` phase with this gjsify", which is the whole reason this
|
|
79
|
+
* constant's header is an argument for bumping whenever a reader could mis-read.
|
|
80
|
+
*
|
|
81
|
+
* 5 added `settings.name`, and it was decided against the criterion above rather
|
|
82
|
+
* than reflexively — #1354 M2a changed two things and only one of them warrants a
|
|
83
|
+
* bump:
|
|
84
|
+
*
|
|
85
|
+
* - NOT the two new format ids. `formats[]` and `overlay`'s keys carry new
|
|
86
|
+
* VALUES in fields whose meaning is unchanged, and `readStageManifest` already
|
|
87
|
+
* refuses a `formats[i]` it does not know BY NAME. An older gjsify meeting a
|
|
88
|
+
* macOS stage therefore fails closed with a message about the format, which is
|
|
89
|
+
* the right message. Adding a member to a validated enum is the case this
|
|
90
|
+
* header says not to bump for.
|
|
91
|
+
* - YES `settings.name`. It is REQUIRED, so this reader meeting a schema-4 stage
|
|
92
|
+
* would fail on `settings.name must be a string` — naming a field instead of
|
|
93
|
+
* the one thing the reader can do about it. That is the same shape the 3-to-4
|
|
94
|
+
* paragraph describes, and at 5 the schema check above fires first and says
|
|
95
|
+
* "re-run the `--stage` phase with this gjsify".
|
|
68
96
|
*/
|
|
69
|
-
export const
|
|
97
|
+
export const STAGE_SCHEMA_VERSION = 5;
|
|
70
98
|
/**
|
|
71
99
|
* The half of {@link ShipSettings} that crosses the host boundary.
|
|
72
100
|
*
|
|
@@ -84,6 +112,7 @@ export function toPackSettings(settings) {
|
|
|
84
112
|
return {
|
|
85
113
|
binaryName: settings.binaryName,
|
|
86
114
|
appId: settings.appId,
|
|
115
|
+
name: settings.name,
|
|
87
116
|
version: settings.version,
|
|
88
117
|
release: settings.release,
|
|
89
118
|
maintainer: settings.maintainer,
|
|
@@ -95,7 +124,9 @@ export function toPackSettings(settings) {
|
|
|
95
124
|
group: settings.group,
|
|
96
125
|
extraDepends: settings.extraDepends,
|
|
97
126
|
typelibPackages: settings.typelibPackages,
|
|
127
|
+
app: settings.app,
|
|
98
128
|
minGjsVersion: settings.minGjsVersion,
|
|
129
|
+
minNodeVersion: settings.minNodeVersion,
|
|
99
130
|
flatpak: settings.flatpak,
|
|
100
131
|
};
|
|
101
132
|
}
|
|
@@ -132,7 +163,13 @@ export function writeStageManifest(input) {
|
|
|
132
163
|
const manifest = {
|
|
133
164
|
schema: STAGE_SCHEMA_VERSION,
|
|
134
165
|
tool: { name: '@gjsify/cli', version: cliVersion() },
|
|
135
|
-
|
|
166
|
+
// The POSITIONAL's value, not a constant. It used to be the literal
|
|
167
|
+
// `'linux'`, correct while the planner produced exactly one layout and
|
|
168
|
+
// wrong the moment it produced three — and wrong SILENTLY, because a
|
|
169
|
+
// darwin stage labelled `linux-arm64` passes every structural check in
|
|
170
|
+
// `readStageManifest` and would be accepted by `--expect-target
|
|
171
|
+
// linux-arm64` on a leg that wanted the Linux one.
|
|
172
|
+
target: { os: input.settings.layoutOs, arch: input.settings.arch },
|
|
136
173
|
formats: input.formats.map((format) => format.id),
|
|
137
174
|
mtime: input.mtime,
|
|
138
175
|
namespaces: [...input.namespaces],
|
|
@@ -203,7 +240,16 @@ export function readStageManifest(stageDir) {
|
|
|
203
240
|
version: expectString(tool.version, at('tool.version')),
|
|
204
241
|
},
|
|
205
242
|
target: {
|
|
206
|
-
|
|
243
|
+
// `layoutForOs`, which takes the `process.platform` spelling and NO
|
|
244
|
+
// alias. `resolveLayout` — the positional's resolver — also accepts
|
|
245
|
+
// `windows`, and routing this field through it would mean a manifest
|
|
246
|
+
// literally saying `"windows"` was accepted and then COMPARED as
|
|
247
|
+
// `win32`: two files with different bytes in the one field
|
|
248
|
+
// `--expect-target` exists to compare would both match it, on a wire
|
|
249
|
+
// format whose entire job is to catch a job that downloaded the wrong
|
|
250
|
+
// artifact. Refused here rather than reaching the layout lookup as a
|
|
251
|
+
// lie, and named the way every other field in this function is.
|
|
252
|
+
os: expectLayoutOs(expectString(target.os, at('target.os')), at('target.os')),
|
|
207
253
|
arch: expectString(target.arch, at('target.arch')),
|
|
208
254
|
},
|
|
209
255
|
formats: expectArray(data.formats, at('formats')).map((entry, index) => {
|
|
@@ -258,6 +304,26 @@ export function readStageManifest(stageDir) {
|
|
|
258
304
|
}
|
|
259
305
|
return manifest;
|
|
260
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* `target.os`, resolved to a layout, in the one spelling this field may carry.
|
|
309
|
+
*
|
|
310
|
+
* The catch REPLACES the message rather than swallowing it: `layoutForOs` speaks
|
|
311
|
+
* about a token, and this is a named field in a file that arrived from another
|
|
312
|
+
* host, so the fix is different and so is the sentence. Every other field in
|
|
313
|
+
* `readStageManifest` names its own location the same way.
|
|
314
|
+
*/
|
|
315
|
+
function expectLayoutOs(value, where) {
|
|
316
|
+
try {
|
|
317
|
+
return layoutForOs(value).os;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
const known = LAYOUT_NAMES.map((name) => LAYOUTS[name].os).join(', ');
|
|
321
|
+
throw new Error(`gjsify ship: ${where} is "${value}", which is not a layout this gjsify can pack. It carries ` +
|
|
322
|
+
`the \`process.platform\` spelling and only that — ${known}, so \`win32\` and never ` +
|
|
323
|
+
"`windows`. A stage holds exactly one OS's layout and every path in it has that OS's shape, " +
|
|
324
|
+
'so there is nothing to pack here. Re-run the `--stage` phase.');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
261
327
|
/** The overlay for one format, in the shape `writeStage` takes. */
|
|
262
328
|
export function overlayFiles(manifest, format) {
|
|
263
329
|
return (manifest.overlay[format] ?? []).map((file) => ({
|
|
@@ -318,7 +384,11 @@ export function assertFormatsStaged(manifest, formats) {
|
|
|
318
384
|
const missing = formats.filter((format) => !manifest.formats.includes(format.id)).map((format) => format.id);
|
|
319
385
|
if (missing.length === 0)
|
|
320
386
|
return;
|
|
321
|
-
throw new Error(
|
|
387
|
+
throw new Error(
|
|
388
|
+
// `no format at all` and not an empty join: a stage CAN record zero formats
|
|
389
|
+
// (`gjsify.ship.targets` filtered to a layout none of them wrap), and the
|
|
390
|
+
// join printed that as nothing — "assembled for , and --target names …".
|
|
391
|
+
`gjsify ship: this stage was assembled for ${manifest.formats.length === 0 ? 'no format at all' : manifest.formats.join(', ')}, and --target names ` +
|
|
322
392
|
`${missing.join(', ')}. Phase one renders each format's overlay — rpm wants the licence at ` +
|
|
323
393
|
'`share/licenses/<pkg>/LICENSE`, deb wants a machine-readable copyright at ' +
|
|
324
394
|
'`share/doc/<pkg>/copyright` — so a format the stage never saw would pack without it, and Debian ' +
|
|
@@ -367,6 +437,7 @@ function readPackSettings(data, at) {
|
|
|
367
437
|
return {
|
|
368
438
|
binaryName: expectString(data.binaryName, field('binaryName')),
|
|
369
439
|
appId: expectString(data.appId, field('appId')),
|
|
440
|
+
name: expectString(data.name, field('name')),
|
|
370
441
|
version: expectString(data.version, field('version')),
|
|
371
442
|
release: expectString(data.release, field('release')),
|
|
372
443
|
maintainer: expectString(data.maintainer, field('maintainer')),
|
|
@@ -390,7 +461,9 @@ function readPackSettings(data, at) {
|
|
|
390
461
|
},
|
|
391
462
|
];
|
|
392
463
|
})),
|
|
464
|
+
app: expectApp(data.app, field('app')),
|
|
393
465
|
minGjsVersion: expectString(data.minGjsVersion, field('minGjsVersion')),
|
|
466
|
+
minNodeVersion: expectString(data.minNodeVersion, field('minNodeVersion')),
|
|
394
467
|
flatpak: readFlatpakSettings(record(data.flatpak, field('flatpak')), field('flatpak')),
|
|
395
468
|
};
|
|
396
469
|
}
|
|
@@ -430,6 +503,19 @@ function expectArray(value, at) {
|
|
|
430
503
|
}
|
|
431
504
|
return value;
|
|
432
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* The interpreter field, checked against the two values that exist.
|
|
508
|
+
*
|
|
509
|
+
* Not `expectString`: this one decides which interpreter the package DEPENDS on,
|
|
510
|
+
* so an unknown value must stop the pack rather than flow into a `Depends:` line
|
|
511
|
+
* as itself. The only way to reach it is a stage from a gjsify that knows a third
|
|
512
|
+
* target, which the schema check above already refuses — this is the second wall.
|
|
513
|
+
*/
|
|
514
|
+
function expectApp(value, at) {
|
|
515
|
+
if (value === 'gjs' || value === 'node')
|
|
516
|
+
return value;
|
|
517
|
+
throw new Error(`gjsify ship: ${at} must be "gjs" or "node", got ${JSON.stringify(value)}.`);
|
|
518
|
+
}
|
|
433
519
|
function expectString(value, at) {
|
|
434
520
|
if (typeof value !== 'string') {
|
|
435
521
|
throw new Error(`gjsify ship: ${at} must be a string. Re-run the \`--stage\` phase.`);
|
|
@@ -42,3 +42,51 @@ export declare function writeStage(root: string, files: readonly StagedFile[]):
|
|
|
42
42
|
* nothing.
|
|
43
43
|
*/
|
|
44
44
|
export declare function readStage(roots: readonly string[], plan: readonly StagePlanEntry[]): PayloadEntry[];
|
|
45
|
+
/**
|
|
46
|
+
* Materialise a PAYLOAD — bytes already in hand — under `root`, replacing it.
|
|
47
|
+
*
|
|
48
|
+
* The twin of {@link writeStage}, and the difference is what each takes.
|
|
49
|
+
* `writeStage` writes a PLAN, whose sources are paths on the assembling host or
|
|
50
|
+
* strings; this writes what {@link readStage} handed back, which is the only shape
|
|
51
|
+
* available on a host that has the stage and nothing else.
|
|
52
|
+
*
|
|
53
|
+
* It exists for the one artifact in `FORMATS` that is a DIRECTORY: a `<App>.app`
|
|
54
|
+
* is not a container around the payload, it IS the payload plus the format's own
|
|
55
|
+
* overlay, laid out on disk. Writing it through this function rather than by
|
|
56
|
+
* copying the stage directory keeps the same two properties every other packer
|
|
57
|
+
* has — the modes come from the plan (`readStage` has already applied them, and
|
|
58
|
+
* the artifact upload that flattens them cannot reach in between), and the
|
|
59
|
+
* stage's own `.gjsify-ship-stage.json` stays out, because it was never in the
|
|
60
|
+
* payload.
|
|
61
|
+
*
|
|
62
|
+
* `root` is WIPED first, exactly as `writeStage` wipes: a `.app` left over from a
|
|
63
|
+
* previous run with a file the current payload no longer names would otherwise be
|
|
64
|
+
* signed, zipped and shipped.
|
|
65
|
+
*
|
|
66
|
+
* `stripPrefix` is what makes it the ARTIFACT rather than a directory holding
|
|
67
|
+
* one, and leaving it out produced exactly that: every staged darwin path already
|
|
68
|
+
* begins with `<App>.app/` — that is what `Layout.root` means — so writing them
|
|
69
|
+
* verbatim under an artifact called `<App>.app` gave
|
|
70
|
+
* `out/Ship Demo.app/Ship Demo.app/Contents/…`. The Finder shows the outer
|
|
71
|
+
* directory as a plain folder, so the failure is a bundle that is not one, and it
|
|
72
|
+
* is invisible to every reader that walks the tree looking for `Contents/`.
|
|
73
|
+
* Measured on the WIP this replaces; the discriminator is the depth assertion in
|
|
74
|
+
* `tests/e2e/ship-macos`.
|
|
75
|
+
*
|
|
76
|
+
* A path OUTSIDE `stripPrefix` throws rather than being written somewhere near
|
|
77
|
+
* the artifact. The alternative — pass it through — would put a file beside the
|
|
78
|
+
* bundle instead of inside it, which is the shape ADR 0024 § A4 records
|
|
79
|
+
* `codesign` refusing, and it would be silent.
|
|
80
|
+
*/
|
|
81
|
+
export declare function writePayload(root: string, payload: readonly PayloadEntry[], stripPrefix: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Total bytes of the regular files under `root`.
|
|
84
|
+
*
|
|
85
|
+
* What `packOne` reports for a `FormatDescriptor.artifactKind: 'directory'`.
|
|
86
|
+
* `statSync` on a directory answers the directory ENTRY's size — 4096 on ext4 —
|
|
87
|
+
* so a `.app` carrying a 20 MiB bundle would be printed as "4096 bytes", which is
|
|
88
|
+
* not a rounding error but a different number. Directory entries themselves are
|
|
89
|
+
* deliberately not counted: what a user wants to know is how much payload they
|
|
90
|
+
* are about to move.
|
|
91
|
+
*/
|
|
92
|
+
export declare function directorySize(root: string): number;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// A stage now arrives from another machine, so "the tree and the plan disagree"
|
|
13
13
|
// stopped being impossible and became a transfer failure — and every way of
|
|
14
14
|
// papering over it produces an artifact that installs.
|
|
15
|
-
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { dirname, join, sep } from 'node:path';
|
|
17
17
|
import { listFilesRecursive } from './discover.js';
|
|
18
18
|
import { STAGE_MANIFEST_FILE } from './stage-manifest.js';
|
|
@@ -45,7 +45,26 @@ export function writeStage(root, files) {
|
|
|
45
45
|
for (const file of files) {
|
|
46
46
|
const target = join(root, file.path.split('/').join(sep));
|
|
47
47
|
mkdirSync(dirname(target), { recursive: true });
|
|
48
|
-
|
|
48
|
+
// A `switch` with a `never` guard rather than a ternary chain: the third
|
|
49
|
+
// source kind arrived because a LAYOUT started owning a binary, and a
|
|
50
|
+
// fourth would otherwise be written as whatever the last branch happens
|
|
51
|
+
// to be — the same closed-vocabulary hazard `packOne` uses one for.
|
|
52
|
+
let data;
|
|
53
|
+
switch (file.source.kind) {
|
|
54
|
+
case 'text':
|
|
55
|
+
data = new TextEncoder().encode(file.source.text);
|
|
56
|
+
break;
|
|
57
|
+
case 'file':
|
|
58
|
+
data = new Uint8Array(readFileSync(file.source.path));
|
|
59
|
+
break;
|
|
60
|
+
case 'bytes':
|
|
61
|
+
data = file.source.data;
|
|
62
|
+
break;
|
|
63
|
+
default: {
|
|
64
|
+
const unhandled = file.source;
|
|
65
|
+
throw new Error(`gjsify ship: no writer for the "${JSON.stringify(unhandled)}" source kind.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
49
68
|
writeFileSync(target, data);
|
|
50
69
|
chmodSync(target, file.mode);
|
|
51
70
|
}
|
|
@@ -114,3 +133,73 @@ export function readStage(roots, plan) {
|
|
|
114
133
|
}
|
|
115
134
|
return [...byPath.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
116
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Materialise a PAYLOAD — bytes already in hand — under `root`, replacing it.
|
|
138
|
+
*
|
|
139
|
+
* The twin of {@link writeStage}, and the difference is what each takes.
|
|
140
|
+
* `writeStage` writes a PLAN, whose sources are paths on the assembling host or
|
|
141
|
+
* strings; this writes what {@link readStage} handed back, which is the only shape
|
|
142
|
+
* available on a host that has the stage and nothing else.
|
|
143
|
+
*
|
|
144
|
+
* It exists for the one artifact in `FORMATS` that is a DIRECTORY: a `<App>.app`
|
|
145
|
+
* is not a container around the payload, it IS the payload plus the format's own
|
|
146
|
+
* overlay, laid out on disk. Writing it through this function rather than by
|
|
147
|
+
* copying the stage directory keeps the same two properties every other packer
|
|
148
|
+
* has — the modes come from the plan (`readStage` has already applied them, and
|
|
149
|
+
* the artifact upload that flattens them cannot reach in between), and the
|
|
150
|
+
* stage's own `.gjsify-ship-stage.json` stays out, because it was never in the
|
|
151
|
+
* payload.
|
|
152
|
+
*
|
|
153
|
+
* `root` is WIPED first, exactly as `writeStage` wipes: a `.app` left over from a
|
|
154
|
+
* previous run with a file the current payload no longer names would otherwise be
|
|
155
|
+
* signed, zipped and shipped.
|
|
156
|
+
*
|
|
157
|
+
* `stripPrefix` is what makes it the ARTIFACT rather than a directory holding
|
|
158
|
+
* one, and leaving it out produced exactly that: every staged darwin path already
|
|
159
|
+
* begins with `<App>.app/` — that is what `Layout.root` means — so writing them
|
|
160
|
+
* verbatim under an artifact called `<App>.app` gave
|
|
161
|
+
* `out/Ship Demo.app/Ship Demo.app/Contents/…`. The Finder shows the outer
|
|
162
|
+
* directory as a plain folder, so the failure is a bundle that is not one, and it
|
|
163
|
+
* is invisible to every reader that walks the tree looking for `Contents/`.
|
|
164
|
+
* Measured on the WIP this replaces; the discriminator is the depth assertion in
|
|
165
|
+
* `tests/e2e/ship-macos`.
|
|
166
|
+
*
|
|
167
|
+
* A path OUTSIDE `stripPrefix` throws rather than being written somewhere near
|
|
168
|
+
* the artifact. The alternative — pass it through — would put a file beside the
|
|
169
|
+
* bundle instead of inside it, which is the shape ADR 0024 § A4 records
|
|
170
|
+
* `codesign` refusing, and it would be silent.
|
|
171
|
+
*/
|
|
172
|
+
export function writePayload(root, payload, stripPrefix) {
|
|
173
|
+
// `''` means "the payload is already artifact-relative"; anything else is a
|
|
174
|
+
// directory name and gets its separator here rather than at three call sites.
|
|
175
|
+
const strip = stripPrefix === '' ? '' : `${stripPrefix}/`;
|
|
176
|
+
rmSync(root, { recursive: true, force: true, maxRetries: 5 });
|
|
177
|
+
mkdirSync(root, { recursive: true });
|
|
178
|
+
for (const entry of payload) {
|
|
179
|
+
if (!entry.path.startsWith(strip)) {
|
|
180
|
+
throw new Error(`gjsify ship: ${entry.path} is outside ${stripPrefix || 'the artifact'}, which this artifact ` +
|
|
181
|
+
'IS — so there is nowhere inside it for the file to go. A payload file that escaped the ' +
|
|
182
|
+
'bundle would ship beside it, unsigned and unfound.');
|
|
183
|
+
}
|
|
184
|
+
const target = join(root, entry.path.slice(strip.length).split('/').join(sep));
|
|
185
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
186
|
+
writeFileSync(target, entry.data);
|
|
187
|
+
chmodSync(target, entry.mode);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Total bytes of the regular files under `root`.
|
|
192
|
+
*
|
|
193
|
+
* What `packOne` reports for a `FormatDescriptor.artifactKind: 'directory'`.
|
|
194
|
+
* `statSync` on a directory answers the directory ENTRY's size — 4096 on ext4 —
|
|
195
|
+
* so a `.app` carrying a 20 MiB bundle would be printed as "4096 bytes", which is
|
|
196
|
+
* not a rounding error but a different number. Directory entries themselves are
|
|
197
|
+
* deliberately not counted: what a user wants to know is how much payload they
|
|
198
|
+
* are about to move.
|
|
199
|
+
*/
|
|
200
|
+
export function directorySize(root) {
|
|
201
|
+
let total = 0;
|
|
202
|
+
for (const rel of listFilesRecursive(root))
|
|
203
|
+
total += statSync(join(root, rel.split('/').join(sep))).size;
|
|
204
|
+
return total;
|
|
205
|
+
}
|
|
@@ -7,13 +7,23 @@ export interface StagedMode {
|
|
|
7
7
|
}
|
|
8
8
|
/** One file in the payload, addressed relative to the install prefix. */
|
|
9
9
|
export interface StagedFile extends StagedMode {
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Where the bytes come from.
|
|
12
|
+
*
|
|
13
|
+
* `bytes` is the third kind because a layout can now OWN a binary: the
|
|
14
|
+
* windows row emits a GUI-subsystem launcher (`utils/ship/pe-launcher.ts`)
|
|
15
|
+
* that exists nowhere on disk and is not text. Spelling it as `text` would
|
|
16
|
+
* round-trip it through UTF-8 and rewrite every byte above 0x7f.
|
|
17
|
+
*/
|
|
11
18
|
source: {
|
|
12
19
|
kind: 'text';
|
|
13
20
|
text: string;
|
|
14
21
|
} | {
|
|
15
22
|
kind: 'file';
|
|
16
23
|
path: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'bytes';
|
|
26
|
+
data: Uint8Array;
|
|
17
27
|
};
|
|
18
28
|
}
|
|
19
29
|
/**
|
|
@@ -36,7 +46,22 @@ export interface ShipArtifact {
|
|
|
36
46
|
/** Size in bytes. */
|
|
37
47
|
size: number;
|
|
38
48
|
}
|
|
39
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The closed vocabulary of formats.
|
|
51
|
+
*
|
|
52
|
+
* The two macOS members are spelled `macos-…` rather than `app` / `app-zip`, and
|
|
53
|
+
* the reason is collision rather than length: the bare word `app` is already
|
|
54
|
+
* taken three times over in this subsystem — the `gjsify.app` config key,
|
|
55
|
+
* `ShipSettings.app`, `PackSettings.app` — and every one of them means the
|
|
56
|
+
* INTERPRETER. A member of this union is also a directory name (`overlay/<id>/`)
|
|
57
|
+
* and a `--target` value a user types, so the ambiguity would have reached both a
|
|
58
|
+
* path and a command line.
|
|
59
|
+
*
|
|
60
|
+
* `scripts/check-ship-format-vocabulary.mjs` reads the declaration below
|
|
61
|
+
* TEXTUALLY and fails if a comment gives it a second match, which is why this one
|
|
62
|
+
* describes the union without quoting its head.
|
|
63
|
+
*/
|
|
64
|
+
export type FormatId = 'deb' | 'rpm' | 'flatpak' | 'macos-app' | 'macos-app-zip' | 'macos-app-dmg' | 'windows-dir' | 'windows-dir-zip' | 'msi';
|
|
40
65
|
/**
|
|
41
66
|
* The formats whose ARTIFACT carries a distro dependency list.
|
|
42
67
|
*
|
|
@@ -57,6 +82,23 @@ export type FormatId = 'deb' | 'rpm' | 'flatpak';
|
|
|
57
82
|
export type DistroFormatId = Extract<FormatId, 'deb' | 'rpm'>;
|
|
58
83
|
/** An operating system in the repo-wide `process.platform` spelling (root AGENTS.md § Runtime & platform model). */
|
|
59
84
|
export type HostOs = 'linux' | 'darwin' | 'win32';
|
|
85
|
+
/**
|
|
86
|
+
* Commands a packer EXECS — a flat LIST when every host runs the same ones, and
|
|
87
|
+
* a per-OS MAP when the backend is host-SELECTED.
|
|
88
|
+
*
|
|
89
|
+
* The map arrived with the `.msi` and nothing else needs it, which is exactly why
|
|
90
|
+
* it is a union rather than a map everywhere: five of the eight rows exec one tool
|
|
91
|
+
* or none, and spelling `{ linux: [], darwin: [], win32: [] }` on each of them
|
|
92
|
+
* would make five rows say three times over what one row has to say once.
|
|
93
|
+
*
|
|
94
|
+
* The `.msi` cannot use the flat form and be honest. It is produced by `wixl` on
|
|
95
|
+
* Linux and by WiX v3 on Windows out of ONE authored `.wxs` (ADR 0024 § A6), so a
|
|
96
|
+
* flat list is wrong in both directions: the union of the two demands `wixl` of a
|
|
97
|
+
* Windows host that will never run it, and either half alone silently claims the
|
|
98
|
+
* other OS needs nothing. {@link requiredToolsOn} is the one resolver, and
|
|
99
|
+
* `assertToolsInstalled` takes the host it resolves for.
|
|
100
|
+
*/
|
|
101
|
+
export type RequiredTools = readonly string[] | Readonly<Partial<Record<HostOs, readonly string[]>>>;
|
|
60
102
|
/**
|
|
61
103
|
* Where a format can be FINISHED, and who can read the result back (ADR 0024 § A3).
|
|
62
104
|
*
|
|
@@ -74,7 +116,7 @@ export interface HostRequirement {
|
|
|
74
116
|
/** OSes whose tooling can finish this format. `'any'` = pure JS, under GJS, offline. */
|
|
75
117
|
finishOn: 'any' | readonly HostOs[];
|
|
76
118
|
/** Commands the packer EXECS. Empty iff this tree writes the format itself. */
|
|
77
|
-
requiredTools:
|
|
119
|
+
requiredTools: RequiredTools;
|
|
78
120
|
/**
|
|
79
121
|
* How to install {@link requiredTools}, in this format's own words.
|
|
80
122
|
*
|
|
@@ -83,8 +125,13 @@ export interface HostRequirement {
|
|
|
83
125
|
* `assertToolsInstalled`, which is correct for exactly one format and would
|
|
84
126
|
* have told the first `.dmg` or `.msi` user to install flatpak — from the
|
|
85
127
|
* same branchless dispatch this whole table exists to keep branchless.
|
|
86
|
-
* Required whenever `requiredTools` is non-empty; `flatpak.spec.ts`
|
|
87
|
-
* that, the same way it holds `selfReading`.
|
|
128
|
+
* Required whenever `requiredTools` is non-empty on ANY host; `flatpak.spec.ts`
|
|
129
|
+
* holds that, the same way it holds `selfReading`.
|
|
130
|
+
*
|
|
131
|
+
* ONE string even for a host-selected backend, and that is not a shortcut: the
|
|
132
|
+
* refusal already names the tool that is missing, so a hint covering both routes
|
|
133
|
+
* ("Fedora: … / Windows: …") answers the reader's question without a second
|
|
134
|
+
* per-OS field to keep in step with the first.
|
|
88
135
|
*/
|
|
89
136
|
installHint?: string;
|
|
90
137
|
oracle: {
|
|
@@ -110,6 +157,22 @@ export interface HostRequirement {
|
|
|
110
157
|
*/
|
|
111
158
|
export interface FormatDescriptor {
|
|
112
159
|
id: FormatId;
|
|
160
|
+
/**
|
|
161
|
+
* The OS whose LAYOUT this format wraps.
|
|
162
|
+
*
|
|
163
|
+
* Not `host.finishOn`, and the two must not be collapsed: `finishOn` says
|
|
164
|
+
* where the container can be PRODUCED, this says which staged tree it is a
|
|
165
|
+
* container FOR. A `.dmg` is `finishOn: ['darwin']` and `layoutOs: 'darwin'`,
|
|
166
|
+
* which makes them look like one field — but the `.app` zip beside it is
|
|
167
|
+
* `finishOn: 'any'` with the same `layoutOs`, and that is the pair that
|
|
168
|
+
* proves they are different questions.
|
|
169
|
+
*
|
|
170
|
+
* This is what gives `defaultFormatIds` its second criterion. Without it, a
|
|
171
|
+
* `.app` and a Windows program directory — both `finishOn: 'any'` — would
|
|
172
|
+
* make a bare `gjsify ship` on Linux emit five artifacts, three of which are
|
|
173
|
+
* for operating systems the caller did not ask about.
|
|
174
|
+
*/
|
|
175
|
+
layoutOs: HostOs;
|
|
113
176
|
/** Install prefix the format's contents hang under. */
|
|
114
177
|
prefix: string;
|
|
115
178
|
/** Where this format can be packed, what it execs, and who reads it back. */
|
|
@@ -119,6 +182,46 @@ export interface FormatDescriptor {
|
|
|
119
182
|
* dependencies are not a package list at all — see {@link DistroFormatId}.
|
|
120
183
|
*/
|
|
121
184
|
depends: DistroFormatId | null;
|
|
185
|
+
/**
|
|
186
|
+
* The interpreters this format's runtime can actually provide at RUN time.
|
|
187
|
+
*
|
|
188
|
+
* A list and not a boolean, and on the descriptor rather than in a branch,
|
|
189
|
+
* because the answer is a property of the format's runtime and nothing else
|
|
190
|
+
* can be asked for it. `deb`/`rpm` carry both: a distribution ships `gjs` and
|
|
191
|
+
* `nodejs`, and the package declares whichever it execs.
|
|
192
|
+
*
|
|
193
|
+
* ⚠️ `flatpak` carries `gjs` ALONE, and that is a measured limit rather than
|
|
194
|
+
* a conservative default. `org.gnome.Platform` ships `gjs` and no `node`;
|
|
195
|
+
* Node exists only as `org.freedesktop.Sdk.Extension.node2x`, which puts
|
|
196
|
+
* `/usr/lib/sdk/node24/bin` on the **build** PATH — this repo's own
|
|
197
|
+
* `guides/flatpak-cli-tool.md` says so in those words. So a `--app node`
|
|
198
|
+
* Flatpak would install and die at `exec node`.
|
|
199
|
+
*
|
|
200
|
+
* That case was reachable: `assertLauncherMatchesInterpreter` is about the
|
|
201
|
+
* launcher agreeing with the DEPENDENCY, and a Flatpak has no dependency
|
|
202
|
+
* list, so the check is vacuous here — it compares `settings.app` against a
|
|
203
|
+
* launcher rendered from `settings.app`. Only the RUNTIME knows, and only
|
|
204
|
+
* this row can say.
|
|
205
|
+
*/
|
|
206
|
+
interpreters: readonly ('gjs' | 'node')[];
|
|
207
|
+
/**
|
|
208
|
+
* Why this format's runtime provides {@link interpreters} and no more — the
|
|
209
|
+
* body of the refusal a project on the wrong interpreter gets.
|
|
210
|
+
*
|
|
211
|
+
* On the row rather than inside the refusal, and the incident is one line
|
|
212
|
+
* old: the refusal carried Flatpak's paragraph about `org.gnome.Platform`
|
|
213
|
+
* hardcoded, and the first macOS row printed it — telling somebody shipping a
|
|
214
|
+
* `.app` that `org.freedesktop.Sdk.Extension.node2x` puts node on the build
|
|
215
|
+
* path. Exactly the shape `installHint` moved out of `assertToolsInstalled`
|
|
216
|
+
* for, one field over.
|
|
217
|
+
*
|
|
218
|
+
* REQUIRED, including on the two rows that accept both interpreters and can
|
|
219
|
+
* therefore never print it. A row that answers "nothing here is restricted,
|
|
220
|
+
* and this is why" has ANSWERED; an optional field is one a sixth format can
|
|
221
|
+
* leave undefined while restricting `interpreters`, and the refusal would go
|
|
222
|
+
* back to having nothing to say.
|
|
223
|
+
*/
|
|
224
|
+
interpreterGap: string;
|
|
122
225
|
/** Where the project's licence text goes in this format's overlay. */
|
|
123
226
|
licenseDest: (binaryName: string) => string;
|
|
124
227
|
/**
|
|
@@ -137,6 +240,19 @@ export interface FormatDescriptor {
|
|
|
137
240
|
archName: (arch: string, archIndependent: boolean) => string;
|
|
138
241
|
/** Artifact filename, given the pack-time settings and the resolved arch label. */
|
|
139
242
|
fileName: (settings: PackSettings, archLabel: string) => string;
|
|
243
|
+
/**
|
|
244
|
+
* Whether {@link fileName} names a FILE or a DIRECTORY.
|
|
245
|
+
*
|
|
246
|
+
* A `.deb`, an `.rpm` and a `.flatpak` are single files; a `<App>.app` is a
|
|
247
|
+
* directory that macOS treats as one object, and it is the first artifact in
|
|
248
|
+
* this table that is not a file. Recorded rather than inferred from the
|
|
249
|
+
* suffix, because `packOne` reports a SIZE and `statSync` on a directory
|
|
250
|
+
* answers 4096 on ext4 — a `.app` carrying a 20 MiB bundle would be printed
|
|
251
|
+
* as "4096 bytes", which is not a rounding error but a different number
|
|
252
|
+
* entirely. The `.dmg` over that same tree is `'file'` again, which is what
|
|
253
|
+
* makes this a property of the CONTAINER rather than "macOS is special".
|
|
254
|
+
*/
|
|
255
|
+
artifactKind: 'file' | 'directory';
|
|
140
256
|
}
|
|
141
257
|
/** One shared-mime-info type definition (`gjsify.ship.mimeTypes`). */
|
|
142
258
|
export interface ShipMimeType {
|
|
@@ -180,12 +296,22 @@ export interface ShipMimeType {
|
|
|
180
296
|
* `share/locale/<rel>`, and no packer reads the field. So translations
|
|
181
297
|
* cross in the payload, where they belong, and the `abs` path does not
|
|
182
298
|
* cross at all. This list being explicit is what caught that.
|
|
183
|
-
* - `
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
299
|
+
* - `fontFiles` — the same shape one milestone later (ADR 0038). `planStage`
|
|
300
|
+
* copies each face into `share/fonts/<appId>/`, no packer reads the field, and
|
|
301
|
+
* the only pack-time question — "does this payload carry fonts" — is answered
|
|
302
|
+
* by the payload's own paths. There is no scriptlet to key off it either:
|
|
303
|
+
* fontconfig scans the directory and caches lazily per user, measured rather
|
|
304
|
+
* than assumed.
|
|
305
|
+
* - `kind`, `execArgs` — read by the planner and the metadata renderers, all of
|
|
306
|
+
* which have run by the time a stage exists. `appId` was on this line and
|
|
307
|
+
* MOVED: the Flatpak packer needs it at pack time twice over — it is the
|
|
308
|
+
* manifest's `id` and the ref `flatpak build-bundle` exports — and neither is
|
|
309
|
+
* derivable from the payload, because the desktop entry that carries the id
|
|
310
|
+
* is only staged for `kind: 'app'`. `name` was on it too and moved for the
|
|
311
|
+
* same kind of reason one milestone later: the macOS artifact IS
|
|
312
|
+
* `<name>.app`, so the packing host has to be able to NAME it, and no other
|
|
313
|
+
* field in this object can spell it (`binaryName` is the executable inside
|
|
314
|
+
* the bundle, not the bundle).
|
|
189
315
|
* - `arch` — carried by the manifest's `target`, which is also the value a
|
|
190
316
|
* mismatched stage is refused on, so it lives in exactly one place.
|
|
191
317
|
* - `mimeTypes` — phase 1 renders the shared-mime-info document into the
|
|
@@ -198,6 +324,17 @@ export interface PackSettings {
|
|
|
198
324
|
binaryName: string;
|
|
199
325
|
/** Reverse-DNS application id — the Flatpak manifest's `id` and the exported ref. */
|
|
200
326
|
appId: string;
|
|
327
|
+
/**
|
|
328
|
+
* Human-readable display name — and, on macOS, the `<App>.app` directory itself.
|
|
329
|
+
*
|
|
330
|
+
* On this side of the host boundary because the darwin packers cannot work
|
|
331
|
+
* without it: `macos-app`'s artifact is a directory called `${name}.app` and
|
|
332
|
+
* `macos-app-zip` wraps that directory, so a finishing host with the stage and
|
|
333
|
+
* nothing else still has to be able to spell it. Also what `Info.plist`'s
|
|
334
|
+
* `CFBundleName`/`CFBundleDisplayName` carry — see `layout.ts`'s
|
|
335
|
+
* `LayoutMetadataInput`.
|
|
336
|
+
*/
|
|
337
|
+
name: string;
|
|
201
338
|
/** Upstream version, normalised for package managers. */
|
|
202
339
|
version: string;
|
|
203
340
|
/** Package revision within one upstream version (deb revision / rpm release). */
|
|
@@ -219,8 +356,29 @@ export interface PackSettings {
|
|
|
219
356
|
extraDepends: Record<DistroFormatId, string[]>;
|
|
220
357
|
/** Project-supplied GI-namespace → package rows, filling gaps in the built-in table. */
|
|
221
358
|
typelibPackages: Record<string, Record<DistroFormatId, string>>;
|
|
222
|
-
/**
|
|
359
|
+
/**
|
|
360
|
+
* The interpreter the launcher execs, and therefore the one the package
|
|
361
|
+
* depends on. THIS TARGET's runtime, resolved once by `resolveShipApp` out of
|
|
362
|
+
* `gjsify.ship.app.<os>` falling back to `gjsify.app`; default `'gjs'`.
|
|
363
|
+
*
|
|
364
|
+
* PER TARGET and never the project field, which is #1486's fix and whose
|
|
365
|
+
* measurement `resolveShipApp` carries. That the stage manifest records the
|
|
366
|
+
* RESOLVED value is what keeps `--from-stage` honest: the packing host has no
|
|
367
|
+
* project and needs none.
|
|
368
|
+
*
|
|
369
|
+
* ON `PackSettings`, not on `ShipSettings` alone, because BOTH halves need it
|
|
370
|
+
* and they must not answer it differently: `renderLauncher` writes
|
|
371
|
+
* `exec gjs -m` or `exec node` from this field, and `deriveDepends` seeds
|
|
372
|
+
* `gjs >= …` or `nodejs >= …` from the same one. Two sources here is the
|
|
373
|
+
* defect this field replaces — a filename heuristic once produced
|
|
374
|
+
* `Depends: gjs (>= 1.86), nodejs (>= 24)` on a package whose launcher execed
|
|
375
|
+
* gjs.
|
|
376
|
+
*/
|
|
377
|
+
app: 'gjs' | 'node';
|
|
378
|
+
/** Minimum GJS the emitted dependency asks for. Used only for `app: 'gjs'`. */
|
|
223
379
|
minGjsVersion: string;
|
|
380
|
+
/** Minimum Node major. Used only for `app: 'node'`. */
|
|
381
|
+
minNodeVersion: string;
|
|
224
382
|
/** The Flatpak manifest's non-payload half, fully defaulted at stage time. */
|
|
225
383
|
flatpak: ShipFlatpakSettings;
|
|
226
384
|
}
|
|
@@ -260,8 +418,6 @@ export interface ShipFlatpakSettings {
|
|
|
260
418
|
export interface ShipSettings extends PackSettings {
|
|
261
419
|
/** Absolute path to the project being shipped. */
|
|
262
420
|
projectDir: string;
|
|
263
|
-
/** Human-readable display name. */
|
|
264
|
-
name: string;
|
|
265
421
|
/** Absolute path to the licence file, when one was found. */
|
|
266
422
|
licenseFile?: string;
|
|
267
423
|
/** `'app'` stages a desktop entry and an icon; `'cli'` stages neither. */
|
|
@@ -298,6 +454,16 @@ export interface ShipSettings extends PackSettings {
|
|
|
298
454
|
rel: string;
|
|
299
455
|
abs: string;
|
|
300
456
|
}[];
|
|
457
|
+
/**
|
|
458
|
+
* Absolute paths of the font faces the application ships (`gjsify.ship.fonts`).
|
|
459
|
+
*
|
|
460
|
+
* Read by `planStage` alone, which copies each into `share/fonts/<appId>/` — the
|
|
461
|
+
* one directory fontconfig reaches through the launcher's existing
|
|
462
|
+
* `XDG_DATA_DIRS` (ADR 0038). Nothing in the pack path reads it, which is why it
|
|
463
|
+
* is not on {@link PackSettings}; the fonts cross in the payload, like the
|
|
464
|
+
* catalogues.
|
|
465
|
+
*/
|
|
466
|
+
fontFiles: string[];
|
|
301
467
|
/** `<dest relative to prefix>` → absolute source path. */
|
|
302
468
|
extraFiles: Record<string, string>;
|
|
303
469
|
/** Arguments appended to the launcher's `exec` line. */
|
|
@@ -306,4 +472,15 @@ export interface ShipSettings extends PackSettings {
|
|
|
306
472
|
outDir: string;
|
|
307
473
|
/** Target architecture in `process.arch` spelling. */
|
|
308
474
|
arch: string;
|
|
475
|
+
/**
|
|
476
|
+
* The OS whose LAYOUT this run assembles, from the `gjsify ship <os>`
|
|
477
|
+
* positional (ADR 0024 § A2).
|
|
478
|
+
*
|
|
479
|
+
* Beside `arch` and for the same reason: together they are the stage's
|
|
480
|
+
* `target`, the string `--expect-target <os>-<arch>` is compared against, and
|
|
481
|
+
* the pair a matrix leg is identified by. Deliberately NOT in
|
|
482
|
+
* {@link PackSettings} — the manifest's `target` carries it, so it lives in
|
|
483
|
+
* exactly one place on the far side of the host boundary.
|
|
484
|
+
*/
|
|
485
|
+
layoutOs: HostOs;
|
|
309
486
|
}
|