@gjsify/cli 0.43.0 → 0.45.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 +453 -425
- package/lib/actions/build.js +2 -1
- package/lib/commands/affected-classify.js +7 -5
- package/lib/commands/build.js +6 -0
- package/lib/commands/flatpak/ci.js +10 -2
- package/lib/commands/flatpak/init.js +15 -16
- package/lib/commands/flatpak/utils.d.ts +0 -26
- package/lib/commands/flatpak/utils.js +4 -29
- 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 +911 -70
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/config.js +25 -0
- package/lib/types/cli-build-options.d.ts +11 -0
- package/lib/types/config-data.d.ts +137 -3
- package/lib/utils/flatpak-runtime.d.ts +41 -0
- package/lib/utils/flatpak-runtime.js +54 -0
- 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 +54 -7
- package/lib/utils/ship/depends.js +120 -6
- package/lib/utils/ship/discover.js +18 -1
- package/lib/utils/ship/dmg.d.ts +71 -0
- package/lib/utils/ship/dmg.js +181 -0
- package/lib/utils/ship/flatpak-config.d.ts +66 -0
- package/lib/utils/ship/flatpak-config.js +132 -0
- package/lib/utils/ship/flatpak.d.ts +45 -0
- package/lib/utils/ship/flatpak.js +148 -0
- package/lib/utils/ship/formats.d.ts +116 -3
- package/lib/utils/ship/formats.js +869 -11
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +362 -17
- package/lib/utils/ship/layout.d.ts +268 -0
- package/lib/utils/ship/layout.js +351 -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 +417 -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 +580 -14
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +20 -7
- package/lib/utils/ship/plist.d.ts +58 -0
- package/lib/utils/ship/plist.js +198 -0
- package/lib/utils/ship/rpm.js +9 -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 +19 -1
- package/lib/utils/ship/settings.js +19 -1
- package/lib/utils/ship/share-dirs.d.ts +23 -0
- package/lib/utils/ship/share-dirs.js +46 -0
- package/lib/utils/ship/signing.d.ts +195 -0
- package/lib/utils/ship/signing.js +317 -0
- package/lib/utils/ship/stage-manifest.d.ts +50 -13
- package/lib/utils/ship/stage-manifest.js +137 -14
- package/lib/utils/ship/stage-writer.d.ts +48 -0
- package/lib/utils/ship/stage-writer.js +71 -1
- package/lib/utils/ship/types.d.ts +264 -16
- 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/trust-registry.js +5 -0
- package/package.json +19 -19
|
@@ -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';
|
|
@@ -114,3 +114,73 @@ export function readStage(roots, plan) {
|
|
|
114
114
|
}
|
|
115
115
|
return [...byPath.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Materialise a PAYLOAD — bytes already in hand — under `root`, replacing it.
|
|
119
|
+
*
|
|
120
|
+
* The twin of {@link writeStage}, and the difference is what each takes.
|
|
121
|
+
* `writeStage` writes a PLAN, whose sources are paths on the assembling host or
|
|
122
|
+
* strings; this writes what {@link readStage} handed back, which is the only shape
|
|
123
|
+
* available on a host that has the stage and nothing else.
|
|
124
|
+
*
|
|
125
|
+
* It exists for the one artifact in `FORMATS` that is a DIRECTORY: a `<App>.app`
|
|
126
|
+
* is not a container around the payload, it IS the payload plus the format's own
|
|
127
|
+
* overlay, laid out on disk. Writing it through this function rather than by
|
|
128
|
+
* copying the stage directory keeps the same two properties every other packer
|
|
129
|
+
* has — the modes come from the plan (`readStage` has already applied them, and
|
|
130
|
+
* the artifact upload that flattens them cannot reach in between), and the
|
|
131
|
+
* stage's own `.gjsify-ship-stage.json` stays out, because it was never in the
|
|
132
|
+
* payload.
|
|
133
|
+
*
|
|
134
|
+
* `root` is WIPED first, exactly as `writeStage` wipes: a `.app` left over from a
|
|
135
|
+
* previous run with a file the current payload no longer names would otherwise be
|
|
136
|
+
* signed, zipped and shipped.
|
|
137
|
+
*
|
|
138
|
+
* `stripPrefix` is what makes it the ARTIFACT rather than a directory holding
|
|
139
|
+
* one, and leaving it out produced exactly that: every staged darwin path already
|
|
140
|
+
* begins with `<App>.app/` — that is what `Layout.root` means — so writing them
|
|
141
|
+
* verbatim under an artifact called `<App>.app` gave
|
|
142
|
+
* `out/Ship Demo.app/Ship Demo.app/Contents/…`. The Finder shows the outer
|
|
143
|
+
* directory as a plain folder, so the failure is a bundle that is not one, and it
|
|
144
|
+
* is invisible to every reader that walks the tree looking for `Contents/`.
|
|
145
|
+
* Measured on the WIP this replaces; the discriminator is the depth assertion in
|
|
146
|
+
* `tests/e2e/ship-macos`.
|
|
147
|
+
*
|
|
148
|
+
* A path OUTSIDE `stripPrefix` throws rather than being written somewhere near
|
|
149
|
+
* the artifact. The alternative — pass it through — would put a file beside the
|
|
150
|
+
* bundle instead of inside it, which is the shape ADR 0024 § A4 records
|
|
151
|
+
* `codesign` refusing, and it would be silent.
|
|
152
|
+
*/
|
|
153
|
+
export function writePayload(root, payload, stripPrefix) {
|
|
154
|
+
// `''` means "the payload is already artifact-relative"; anything else is a
|
|
155
|
+
// directory name and gets its separator here rather than at three call sites.
|
|
156
|
+
const strip = stripPrefix === '' ? '' : `${stripPrefix}/`;
|
|
157
|
+
rmSync(root, { recursive: true, force: true, maxRetries: 5 });
|
|
158
|
+
mkdirSync(root, { recursive: true });
|
|
159
|
+
for (const entry of payload) {
|
|
160
|
+
if (!entry.path.startsWith(strip)) {
|
|
161
|
+
throw new Error(`gjsify ship: ${entry.path} is outside ${stripPrefix || 'the artifact'}, which this artifact ` +
|
|
162
|
+
'IS — so there is nowhere inside it for the file to go. A payload file that escaped the ' +
|
|
163
|
+
'bundle would ship beside it, unsigned and unfound.');
|
|
164
|
+
}
|
|
165
|
+
const target = join(root, entry.path.slice(strip.length).split('/').join(sep));
|
|
166
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
167
|
+
writeFileSync(target, entry.data);
|
|
168
|
+
chmodSync(target, entry.mode);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Total bytes of the regular files under `root`.
|
|
173
|
+
*
|
|
174
|
+
* What `packOne` reports for a `FormatDescriptor.artifactKind: 'directory'`.
|
|
175
|
+
* `statSync` on a directory answers the directory ENTRY's size — 4096 on ext4 —
|
|
176
|
+
* so a `.app` carrying a 20 MiB bundle would be printed as "4096 bytes", which is
|
|
177
|
+
* not a rounding error but a different number. Directory entries themselves are
|
|
178
|
+
* deliberately not counted: what a user wants to know is how much payload they
|
|
179
|
+
* are about to move.
|
|
180
|
+
*/
|
|
181
|
+
export function directorySize(root) {
|
|
182
|
+
let total = 0;
|
|
183
|
+
for (const rel of listFilesRecursive(root))
|
|
184
|
+
total += statSync(join(root, rel.split('/').join(sep))).size;
|
|
185
|
+
return total;
|
|
186
|
+
}
|
|
@@ -36,7 +36,108 @@ export interface ShipArtifact {
|
|
|
36
36
|
/** Size in bytes. */
|
|
37
37
|
size: number;
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The closed vocabulary of formats.
|
|
41
|
+
*
|
|
42
|
+
* The two macOS members are spelled `macos-…` rather than `app` / `app-zip`, and
|
|
43
|
+
* the reason is collision rather than length: the bare word `app` is already
|
|
44
|
+
* taken three times over in this subsystem — the `gjsify.app` config key,
|
|
45
|
+
* `ShipSettings.app`, `PackSettings.app` — and every one of them means the
|
|
46
|
+
* INTERPRETER. A member of this union is also a directory name (`overlay/<id>/`)
|
|
47
|
+
* and a `--target` value a user types, so the ambiguity would have reached both a
|
|
48
|
+
* path and a command line.
|
|
49
|
+
*
|
|
50
|
+
* `scripts/check-ship-format-vocabulary.mjs` reads the declaration below
|
|
51
|
+
* TEXTUALLY and fails if a comment gives it a second match, which is why this one
|
|
52
|
+
* describes the union without quoting its head.
|
|
53
|
+
*/
|
|
54
|
+
export type FormatId = 'deb' | 'rpm' | 'flatpak' | 'macos-app' | 'macos-app-zip' | 'macos-app-dmg' | 'windows-dir' | 'windows-dir-zip' | 'msi';
|
|
55
|
+
/**
|
|
56
|
+
* The formats whose ARTIFACT carries a distro dependency list.
|
|
57
|
+
*
|
|
58
|
+
* Not every `FormatId`, and this is where the difference is written down once
|
|
59
|
+
* instead of being rediscovered per call site. A `.deb` says
|
|
60
|
+
* `Depends: gir1.2-gtk-4.0`, an `.rpm` says `Requires: gtk4`, and both are
|
|
61
|
+
* package names in a distribution's namespace. A Flatpak has no such field: it
|
|
62
|
+
* declares ONE dependency, its runtime (`org.gnome.Platform//50`), in the
|
|
63
|
+
* manifest — and inside that runtime a typelib is either present or nothing on
|
|
64
|
+
* the system provides it, so there is no package to name and no gap a name
|
|
65
|
+
* could close.
|
|
66
|
+
*
|
|
67
|
+
* `Extract<…>` rather than a fresh literal union, so this cannot outlive
|
|
68
|
+
* `FormatId`: dropping a member there shrinks this and reds every table keyed on
|
|
69
|
+
* it. The other direction is covered by {@link FormatDescriptor.depends} being
|
|
70
|
+
* REQUIRED — a new format cannot be added without saying which side it is on.
|
|
71
|
+
*/
|
|
72
|
+
export type DistroFormatId = Extract<FormatId, 'deb' | 'rpm'>;
|
|
73
|
+
/** An operating system in the repo-wide `process.platform` spelling (root AGENTS.md § Runtime & platform model). */
|
|
74
|
+
export type HostOs = 'linux' | 'darwin' | 'win32';
|
|
75
|
+
/**
|
|
76
|
+
* Commands a packer EXECS — a flat LIST when every host runs the same ones, and
|
|
77
|
+
* a per-OS MAP when the backend is host-SELECTED.
|
|
78
|
+
*
|
|
79
|
+
* The map arrived with the `.msi` and nothing else needs it, which is exactly why
|
|
80
|
+
* it is a union rather than a map everywhere: five of the eight rows exec one tool
|
|
81
|
+
* or none, and spelling `{ linux: [], darwin: [], win32: [] }` on each of them
|
|
82
|
+
* would make five rows say three times over what one row has to say once.
|
|
83
|
+
*
|
|
84
|
+
* The `.msi` cannot use the flat form and be honest. It is produced by `wixl` on
|
|
85
|
+
* Linux and by WiX v3 on Windows out of ONE authored `.wxs` (ADR 0024 § A6), so a
|
|
86
|
+
* flat list is wrong in both directions: the union of the two demands `wixl` of a
|
|
87
|
+
* Windows host that will never run it, and either half alone silently claims the
|
|
88
|
+
* other OS needs nothing. {@link requiredToolsOn} is the one resolver, and
|
|
89
|
+
* `assertToolsInstalled` takes the host it resolves for.
|
|
90
|
+
*/
|
|
91
|
+
export type RequiredTools = readonly string[] | Readonly<Partial<Record<HostOs, readonly string[]>>>;
|
|
92
|
+
/**
|
|
93
|
+
* Where a format can be FINISHED, and who can read the result back (ADR 0024 § A3).
|
|
94
|
+
*
|
|
95
|
+
* Three fields rather than one `hostOs`, because the three questions have
|
|
96
|
+
* different answers. `finishOn` is not the LAYOUT — a `<App>.app` tree is
|
|
97
|
+
* assembled anywhere while the `.dmg` around it needs macOS. `requiredTools` is
|
|
98
|
+
* not implied by `finishOn` — `.deb` and `.rpm` are written by this tree and
|
|
99
|
+
* exec nothing, which is the whole reason `rpm` could become an independent
|
|
100
|
+
* oracle and catch the dpkg-`$1` defect in the first artifact. And `oracle` is
|
|
101
|
+
* derivable from neither: a format built by the platform's own tool forfeits
|
|
102
|
+
* that independence unless a reader from a DIFFERENT implementation family
|
|
103
|
+
* exists, which is exactly what `selfReading` is the honest confession of.
|
|
104
|
+
*/
|
|
105
|
+
export interface HostRequirement {
|
|
106
|
+
/** OSes whose tooling can finish this format. `'any'` = pure JS, under GJS, offline. */
|
|
107
|
+
finishOn: 'any' | readonly HostOs[];
|
|
108
|
+
/** Commands the packer EXECS. Empty iff this tree writes the format itself. */
|
|
109
|
+
requiredTools: RequiredTools;
|
|
110
|
+
/**
|
|
111
|
+
* How to install {@link requiredTools}, in this format's own words.
|
|
112
|
+
*
|
|
113
|
+
* DATA, because the refusal that names it is one generic function. The
|
|
114
|
+
* first cut hardcoded `sudo dnf install flatpak flatpak-builder` inside
|
|
115
|
+
* `assertToolsInstalled`, which is correct for exactly one format and would
|
|
116
|
+
* have told the first `.dmg` or `.msi` user to install flatpak — from the
|
|
117
|
+
* same branchless dispatch this whole table exists to keep branchless.
|
|
118
|
+
* Required whenever `requiredTools` is non-empty on ANY host; `flatpak.spec.ts`
|
|
119
|
+
* holds that, the same way it holds `selfReading`.
|
|
120
|
+
*
|
|
121
|
+
* ONE string even for a host-selected backend, and that is not a shortcut: the
|
|
122
|
+
* refusal already names the tool that is missing, so a hint covering both routes
|
|
123
|
+
* ("Fedora: … / Windows: …") answers the reader's question without a second
|
|
124
|
+
* per-OS field to keep in step with the first.
|
|
125
|
+
*/
|
|
126
|
+
installHint?: string;
|
|
127
|
+
oracle: {
|
|
128
|
+
/** Readers from a DIFFERENT implementation family than the packer. */
|
|
129
|
+
readWith: readonly string[];
|
|
130
|
+
/** Where each reader runs. A Linux-runnable reader is worth more — every CI leg has one. */
|
|
131
|
+
readOn: readonly HostOs[];
|
|
132
|
+
/**
|
|
133
|
+
* No independent discriminator yet. Legal to DECLARE while a format is
|
|
134
|
+
* being built; illegal to release — `flatpak.spec.ts` is what turns that
|
|
135
|
+
* sentence into a red test, so flipping it is a decision somebody makes
|
|
136
|
+
* rather than a field nobody reads.
|
|
137
|
+
*/
|
|
138
|
+
selfReading: boolean;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
40
141
|
/**
|
|
41
142
|
* Everything that is per-FORMAT, as data.
|
|
42
143
|
*
|
|
@@ -46,8 +147,71 @@ export type FormatId = 'deb' | 'rpm';
|
|
|
46
147
|
*/
|
|
47
148
|
export interface FormatDescriptor {
|
|
48
149
|
id: FormatId;
|
|
150
|
+
/**
|
|
151
|
+
* The OS whose LAYOUT this format wraps.
|
|
152
|
+
*
|
|
153
|
+
* Not `host.finishOn`, and the two must not be collapsed: `finishOn` says
|
|
154
|
+
* where the container can be PRODUCED, this says which staged tree it is a
|
|
155
|
+
* container FOR. A `.dmg` is `finishOn: ['darwin']` and `layoutOs: 'darwin'`,
|
|
156
|
+
* which makes them look like one field — but the `.app` zip beside it is
|
|
157
|
+
* `finishOn: 'any'` with the same `layoutOs`, and that is the pair that
|
|
158
|
+
* proves they are different questions.
|
|
159
|
+
*
|
|
160
|
+
* This is what gives `defaultFormatIds` its second criterion. Without it, a
|
|
161
|
+
* `.app` and a Windows program directory — both `finishOn: 'any'` — would
|
|
162
|
+
* make a bare `gjsify ship` on Linux emit five artifacts, three of which are
|
|
163
|
+
* for operating systems the caller did not ask about.
|
|
164
|
+
*/
|
|
165
|
+
layoutOs: HostOs;
|
|
49
166
|
/** Install prefix the format's contents hang under. */
|
|
50
167
|
prefix: string;
|
|
168
|
+
/** Where this format can be packed, what it execs, and who reads it back. */
|
|
169
|
+
host: HostRequirement;
|
|
170
|
+
/**
|
|
171
|
+
* The key this format's dependency list is spelled under, or `null` when its
|
|
172
|
+
* dependencies are not a package list at all — see {@link DistroFormatId}.
|
|
173
|
+
*/
|
|
174
|
+
depends: DistroFormatId | null;
|
|
175
|
+
/**
|
|
176
|
+
* The interpreters this format's runtime can actually provide at RUN time.
|
|
177
|
+
*
|
|
178
|
+
* A list and not a boolean, and on the descriptor rather than in a branch,
|
|
179
|
+
* because the answer is a property of the format's runtime and nothing else
|
|
180
|
+
* can be asked for it. `deb`/`rpm` carry both: a distribution ships `gjs` and
|
|
181
|
+
* `nodejs`, and the package declares whichever it execs.
|
|
182
|
+
*
|
|
183
|
+
* ⚠️ `flatpak` carries `gjs` ALONE, and that is a measured limit rather than
|
|
184
|
+
* a conservative default. `org.gnome.Platform` ships `gjs` and no `node`;
|
|
185
|
+
* Node exists only as `org.freedesktop.Sdk.Extension.node2x`, which puts
|
|
186
|
+
* `/usr/lib/sdk/node24/bin` on the **build** PATH — this repo's own
|
|
187
|
+
* `guides/flatpak-cli-tool.md` says so in those words. So a `--app node`
|
|
188
|
+
* Flatpak would install and die at `exec node`.
|
|
189
|
+
*
|
|
190
|
+
* That case was reachable: `assertLauncherMatchesInterpreter` is about the
|
|
191
|
+
* launcher agreeing with the DEPENDENCY, and a Flatpak has no dependency
|
|
192
|
+
* list, so the check is vacuous here — it compares `settings.app` against a
|
|
193
|
+
* launcher rendered from `settings.app`. Only the RUNTIME knows, and only
|
|
194
|
+
* this row can say.
|
|
195
|
+
*/
|
|
196
|
+
interpreters: readonly ('gjs' | 'node')[];
|
|
197
|
+
/**
|
|
198
|
+
* Why this format's runtime provides {@link interpreters} and no more — the
|
|
199
|
+
* body of the refusal a project on the wrong interpreter gets.
|
|
200
|
+
*
|
|
201
|
+
* On the row rather than inside the refusal, and the incident is one line
|
|
202
|
+
* old: the refusal carried Flatpak's paragraph about `org.gnome.Platform`
|
|
203
|
+
* hardcoded, and the first macOS row printed it — telling somebody shipping a
|
|
204
|
+
* `.app` that `org.freedesktop.Sdk.Extension.node2x` puts node on the build
|
|
205
|
+
* path. Exactly the shape `installHint` moved out of `assertToolsInstalled`
|
|
206
|
+
* for, one field over.
|
|
207
|
+
*
|
|
208
|
+
* REQUIRED, including on the two rows that accept both interpreters and can
|
|
209
|
+
* therefore never print it. A row that answers "nothing here is restricted,
|
|
210
|
+
* and this is why" has ANSWERED; an optional field is one a sixth format can
|
|
211
|
+
* leave undefined while restricting `interpreters`, and the refusal would go
|
|
212
|
+
* back to having nothing to say.
|
|
213
|
+
*/
|
|
214
|
+
interpreterGap: string;
|
|
51
215
|
/** Where the project's licence text goes in this format's overlay. */
|
|
52
216
|
licenseDest: (binaryName: string) => string;
|
|
53
217
|
/**
|
|
@@ -66,6 +230,19 @@ export interface FormatDescriptor {
|
|
|
66
230
|
archName: (arch: string, archIndependent: boolean) => string;
|
|
67
231
|
/** Artifact filename, given the pack-time settings and the resolved arch label. */
|
|
68
232
|
fileName: (settings: PackSettings, archLabel: string) => string;
|
|
233
|
+
/**
|
|
234
|
+
* Whether {@link fileName} names a FILE or a DIRECTORY.
|
|
235
|
+
*
|
|
236
|
+
* A `.deb`, an `.rpm` and a `.flatpak` are single files; a `<App>.app` is a
|
|
237
|
+
* directory that macOS treats as one object, and it is the first artifact in
|
|
238
|
+
* this table that is not a file. Recorded rather than inferred from the
|
|
239
|
+
* suffix, because `packOne` reports a SIZE and `statSync` on a directory
|
|
240
|
+
* answers 4096 on ext4 — a `.app` carrying a 20 MiB bundle would be printed
|
|
241
|
+
* as "4096 bytes", which is not a rounding error but a different number
|
|
242
|
+
* entirely. The `.dmg` over that same tree is `'file'` again, which is what
|
|
243
|
+
* makes this a property of the CONTAINER rather than "macOS is special".
|
|
244
|
+
*/
|
|
245
|
+
artifactKind: 'file' | 'directory';
|
|
69
246
|
}
|
|
70
247
|
/** One shared-mime-info type definition (`gjsify.ship.mimeTypes`). */
|
|
71
248
|
export interface ShipMimeType {
|
|
@@ -109,8 +286,16 @@ export interface ShipMimeType {
|
|
|
109
286
|
* `share/locale/<rel>`, and no packer reads the field. So translations
|
|
110
287
|
* cross in the payload, where they belong, and the `abs` path does not
|
|
111
288
|
* cross at all. This list being explicit is what caught that.
|
|
112
|
-
* - `
|
|
113
|
-
*
|
|
289
|
+
* - `kind`, `execArgs` — read by the planner and the metadata renderers, all of
|
|
290
|
+
* which have run by the time a stage exists. `appId` was on this line and
|
|
291
|
+
* MOVED: the Flatpak packer needs it at pack time twice over — it is the
|
|
292
|
+
* manifest's `id` and the ref `flatpak build-bundle` exports — and neither is
|
|
293
|
+
* derivable from the payload, because the desktop entry that carries the id
|
|
294
|
+
* is only staged for `kind: 'app'`. `name` was on it too and moved for the
|
|
295
|
+
* same kind of reason one milestone later: the macOS artifact IS
|
|
296
|
+
* `<name>.app`, so the packing host has to be able to NAME it, and no other
|
|
297
|
+
* field in this object can spell it (`binaryName` is the executable inside
|
|
298
|
+
* the bundle, not the bundle).
|
|
114
299
|
* - `arch` — carried by the manifest's `target`, which is also the value a
|
|
115
300
|
* mismatched stage is refused on, so it lives in exactly one place.
|
|
116
301
|
* - `mimeTypes` — phase 1 renders the shared-mime-info document into the
|
|
@@ -121,6 +306,19 @@ export interface ShipMimeType {
|
|
|
121
306
|
export interface PackSettings {
|
|
122
307
|
/** Package name: the `bin/` entry, the deb `Package:`, the rpm `Name:`. */
|
|
123
308
|
binaryName: string;
|
|
309
|
+
/** Reverse-DNS application id — the Flatpak manifest's `id` and the exported ref. */
|
|
310
|
+
appId: string;
|
|
311
|
+
/**
|
|
312
|
+
* Human-readable display name — and, on macOS, the `<App>.app` directory itself.
|
|
313
|
+
*
|
|
314
|
+
* On this side of the host boundary because the darwin packers cannot work
|
|
315
|
+
* without it: `macos-app`'s artifact is a directory called `${name}.app` and
|
|
316
|
+
* `macos-app-zip` wraps that directory, so a finishing host with the stage and
|
|
317
|
+
* nothing else still has to be able to spell it. Also what `Info.plist`'s
|
|
318
|
+
* `CFBundleName`/`CFBundleDisplayName` carry — see `layout.ts`'s
|
|
319
|
+
* `LayoutMetadataInput`.
|
|
320
|
+
*/
|
|
321
|
+
name: string;
|
|
124
322
|
/** Upstream version, normalised for package managers. */
|
|
125
323
|
version: string;
|
|
126
324
|
/** Package revision within one upstream version (deb revision / rpm release). */
|
|
@@ -139,26 +337,65 @@ export interface PackSettings {
|
|
|
139
337
|
/** rpm `Group:`. */
|
|
140
338
|
group: string;
|
|
141
339
|
/** Extra runtime dependencies per format, appended to the derived set. */
|
|
142
|
-
extraDepends:
|
|
143
|
-
deb: string[];
|
|
144
|
-
rpm: string[];
|
|
145
|
-
};
|
|
340
|
+
extraDepends: Record<DistroFormatId, string[]>;
|
|
146
341
|
/** Project-supplied GI-namespace → package rows, filling gaps in the built-in table. */
|
|
147
|
-
typelibPackages: Record<string,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
342
|
+
typelibPackages: Record<string, Record<DistroFormatId, string>>;
|
|
343
|
+
/**
|
|
344
|
+
* The interpreter the launcher execs, and therefore the one the package
|
|
345
|
+
* depends on. From `gjsify.app`; default `'gjs'`.
|
|
346
|
+
*
|
|
347
|
+
* ON `PackSettings`, not on `ShipSettings` alone, because BOTH halves need it
|
|
348
|
+
* and they must not answer it differently: `renderLauncher` writes
|
|
349
|
+
* `exec gjs -m` or `exec node` from this field, and `deriveDepends` seeds
|
|
350
|
+
* `gjs >= …` or `nodejs >= …` from the same one. Two sources here is the
|
|
351
|
+
* defect this field replaces — a filename heuristic once produced
|
|
352
|
+
* `Depends: gjs (>= 1.86), nodejs (>= 24)` on a package whose launcher execed
|
|
353
|
+
* gjs.
|
|
354
|
+
*/
|
|
355
|
+
app: 'gjs' | 'node';
|
|
356
|
+
/** Minimum GJS the emitted dependency asks for. Used only for `app: 'gjs'`. */
|
|
152
357
|
minGjsVersion: string;
|
|
358
|
+
/** Minimum Node major. Used only for `app: 'node'`. */
|
|
359
|
+
minNodeVersion: string;
|
|
360
|
+
/** The Flatpak manifest's non-payload half, fully defaulted at stage time. */
|
|
361
|
+
flatpak: ShipFlatpakSettings;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* What the generated Flatpak manifest says about everything that is not the payload.
|
|
365
|
+
*
|
|
366
|
+
* Resolved — defaults applied — while the project is still in reach, because
|
|
367
|
+
* the defaults depend on `kind` and on `gjsify.flatpak`/`gjsify.ship.flatpak`,
|
|
368
|
+
* and the finishing host has neither.
|
|
369
|
+
*
|
|
370
|
+
* There is deliberately no `modules` / `extraModules` here. Under `ship` the
|
|
371
|
+
* module list IS the staged payload (ADR 0024 § 2: "a target that needs an
|
|
372
|
+
* extra file gets an overlay, never a branch in the staging code"), and an
|
|
373
|
+
* escape hatch that injects arbitrary build modules would put a second staging
|
|
374
|
+
* model back in the tree — which is the one thing § 8 gates the whole migration
|
|
375
|
+
* on. A project that genuinely needs to BUILD something inside the sandbox
|
|
376
|
+
* still has `gjsify flatpak init` + `gjsify flatpak build`, unchanged.
|
|
377
|
+
*/
|
|
378
|
+
export interface ShipFlatpakSettings {
|
|
379
|
+
/** Runtime id, e.g. `org.gnome.Platform`. */
|
|
380
|
+
runtime: string;
|
|
381
|
+
/** Runtime/SDK version, e.g. `50`. */
|
|
382
|
+
runtimeVersion: string;
|
|
383
|
+
/** SDK id, e.g. `org.gnome.Sdk`. */
|
|
384
|
+
sdk: string;
|
|
385
|
+
/** The branch the app is exported under — the last segment of `app/<id>/<arch>/<branch>`. */
|
|
386
|
+
branch: string;
|
|
387
|
+
sdkExtensions: string[];
|
|
388
|
+
/** Path components prepended to `PATH` inside the build sandbox. */
|
|
389
|
+
appendPath: string[];
|
|
390
|
+
/** Capabilities the finished app is granted. */
|
|
391
|
+
finishArgs: string[];
|
|
392
|
+
/** Cleanup globs applied to `/app` after the module has run. */
|
|
393
|
+
cleanup: string[];
|
|
153
394
|
}
|
|
154
395
|
/** The resolved, fully defaulted configuration for one `gjsify ship` run. */
|
|
155
396
|
export interface ShipSettings extends PackSettings {
|
|
156
397
|
/** Absolute path to the project being shipped. */
|
|
157
398
|
projectDir: string;
|
|
158
|
-
/** Reverse-DNS application id — desktop entry, metainfo and icon basename. */
|
|
159
|
-
appId: string;
|
|
160
|
-
/** Human-readable display name. */
|
|
161
|
-
name: string;
|
|
162
399
|
/** Absolute path to the licence file, when one was found. */
|
|
163
400
|
licenseFile?: string;
|
|
164
401
|
/** `'app'` stages a desktop entry and an icon; `'cli'` stages neither. */
|
|
@@ -203,4 +440,15 @@ export interface ShipSettings extends PackSettings {
|
|
|
203
440
|
outDir: string;
|
|
204
441
|
/** Target architecture in `process.arch` spelling. */
|
|
205
442
|
arch: string;
|
|
443
|
+
/**
|
|
444
|
+
* The OS whose LAYOUT this run assembles, from the `gjsify ship <os>`
|
|
445
|
+
* positional (ADR 0024 § A2).
|
|
446
|
+
*
|
|
447
|
+
* Beside `arch` and for the same reason: together they are the stage's
|
|
448
|
+
* `target`, the string `--expect-target <os>-<arch>` is compared against, and
|
|
449
|
+
* the pair a matrix leg is identified by. Deliberately NOT in
|
|
450
|
+
* {@link PackSettings} — the manifest's `target` carries it, so it lives in
|
|
451
|
+
* exactly one place on the far side of the host boundary.
|
|
452
|
+
*/
|
|
453
|
+
layoutOs: HostOs;
|
|
206
454
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a value for XML element content or an attribute value.
|
|
3
|
+
*
|
|
4
|
+
* `&` FIRST, or the ampersands the four later replacements introduce are escaped
|
|
5
|
+
* again and `Ship & Co` comes out as `Ship &amp; Co`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function xmlEscape(value: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The XML escape `gjsify ship`'s document renderers share.
|
|
2
|
+
//
|
|
3
|
+
// TWO COPIES BECAME ONE. `mime.ts` and `msi.ts` each grew their own — five
|
|
4
|
+
// replacements, same entities, same order, byte-identical bodies — one milestone
|
|
5
|
+
// apart, by people who never saw the other file. The second copy is where the
|
|
6
|
+
// helper goes.
|
|
7
|
+
//
|
|
8
|
+
// `plist.ts` keeps its own and that divergence CARRIES something: a plist value
|
|
9
|
+
// lands only in element CONTENT, so `"` and `'` are left as themselves rather
|
|
10
|
+
// than becoming entities a human reading `Info.plist` has to decode. This one is
|
|
11
|
+
// used in ATTRIBUTES too — a `.wxs` is almost nothing else — so it escapes both.
|
|
12
|
+
/**
|
|
13
|
+
* Escape a value for XML element content or an attribute value.
|
|
14
|
+
*
|
|
15
|
+
* `&` FIRST, or the ampersands the four later replacements introduce are escaped
|
|
16
|
+
* again and `Ship & Co` comes out as `Ship &amp; Co`.
|
|
17
|
+
*/
|
|
18
|
+
export function xmlEscape(value) {
|
|
19
|
+
return value
|
|
20
|
+
.replace(/&/g, '&')
|
|
21
|
+
.replace(/</g, '<')
|
|
22
|
+
.replace(/>/g, '>')
|
|
23
|
+
.replace(/"/g, '"')
|
|
24
|
+
.replace(/'/g, ''');
|
|
25
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PayloadEntry } from './payload.js';
|
|
2
|
+
/** CRC-32 of a byte range, in ZIP's spelling (pre- and post-inverted). */
|
|
3
|
+
export declare function crc32(data: Uint8Array): number;
|
|
4
|
+
/**
|
|
5
|
+
* A Unix epoch second as the MS-DOS date and time pair ZIP stores.
|
|
6
|
+
*
|
|
7
|
+
* The format is from 1980 and cannot say anything earlier, and its seconds field
|
|
8
|
+
* holds HALF-seconds, so an odd second is not representable. Neither is refused:
|
|
9
|
+
* the stamp is metadata no reader in this repository asserts on, and failing a
|
|
10
|
+
* build because `SOURCE_DATE_EPOCH` named 1979 would reject an artifact over a
|
|
11
|
+
* display field. Earlier than 1980 is CLAMPED to it; the odd second is floored.
|
|
12
|
+
*
|
|
13
|
+
* UTC deliberately, not local time. A DOS stamp carries no zone, so reading it
|
|
14
|
+
* back as local time is the only convention available — and using the packing
|
|
15
|
+
* host's zone would mean two runners in different regions produce different bytes
|
|
16
|
+
* from one stage, which is exactly the property this module exists to hold.
|
|
17
|
+
*/
|
|
18
|
+
export declare function dosDateTime(epochSeconds: number): {
|
|
19
|
+
date: number;
|
|
20
|
+
time: number;
|
|
21
|
+
};
|
|
22
|
+
/** One entry to store, addressed by its path INSIDE the archive. */
|
|
23
|
+
export interface ZipEntry {
|
|
24
|
+
/** Archive-relative, POSIX-separated. ZIP stores `/` on every platform. */
|
|
25
|
+
path: string;
|
|
26
|
+
/** POSIX mode bits, written into the external attributes' high 16 bits. */
|
|
27
|
+
mode: number;
|
|
28
|
+
data: Uint8Array;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a ZIP archive: local headers, then the central directory, then the EOCD.
|
|
32
|
+
*
|
|
33
|
+
* `mtime` is one stamp for every entry, taken from the caller. Per-file mtimes
|
|
34
|
+
* are deliberately not read off disk: an artifact upload does not carry them
|
|
35
|
+
* (`stage-manifest.ts`'s header), so re-stat'ing the stage on the packing host
|
|
36
|
+
* would stamp the archive with "whenever the download finished" and two hosts
|
|
37
|
+
* packing one stage would disagree.
|
|
38
|
+
*
|
|
39
|
+
* ENTRIES ARE SORTED BY PATH, and the caller's order is not trusted for the same
|
|
40
|
+
* reason `plan.ts` sorts: two runs over one payload must produce one byte
|
|
41
|
+
* sequence, and a `readdir` order is a filesystem's opinion.
|
|
42
|
+
*
|
|
43
|
+
* NO DIRECTORY ENTRIES. A ZIP may carry them and `unzip` recreates the tree from
|
|
44
|
+
* the file paths alone, so emitting them would add entries no reader here
|
|
45
|
+
* inspects and one more thing to keep deterministic. The consequence is stated
|
|
46
|
+
* rather than left to be discovered: an EMPTY directory inside a `.app` would not
|
|
47
|
+
* survive the archive — nothing in the payload can produce one, because
|
|
48
|
+
* `writeStage` only ever writes files.
|
|
49
|
+
*
|
|
50
|
+
* ZIP64 is NOT implemented, and the refusal below is what keeps that honest:
|
|
51
|
+
* past 4 GiB or 65535 entries the 32-bit fields silently wrap and the archive
|
|
52
|
+
* that comes out is well-formed and wrong. A loud limit is the better failure —
|
|
53
|
+
* the same argument `assertPayloadMatchesArch` makes one field over, where a
|
|
54
|
+
* label nothing compared to the payload produced an artifact `rpm` confirmed.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildZip(entries: readonly ZipEntry[], mtime: number): Uint8Array;
|
|
57
|
+
/**
|
|
58
|
+
* The payload as zip entries, keeping each file's planned mode.
|
|
59
|
+
*
|
|
60
|
+
* `topLevel` names a directory every entry goes UNDER, and `''` means the payload
|
|
61
|
+
* paths already carry one. The two callers differ because their layouts do:
|
|
62
|
+
* a `<App>.app`'s staged paths already begin with the bundle directory
|
|
63
|
+
* (`Layout.root`), while a Windows program directory's do not — the stage IS its
|
|
64
|
+
* contents, because an installer chooses the parent. An archive without a top
|
|
65
|
+
* level expands into whatever directory the user happened to be in, scattering
|
|
66
|
+
* `app\`, `share\` and the launcher across it, and no listing of NAMES reads as
|
|
67
|
+
* wrong: every entry is individually correct.
|
|
68
|
+
*/
|
|
69
|
+
export declare function zipEntriesFromPayload(payload: readonly PayloadEntry[], topLevel?: string): ZipEntry[];
|