@gjsify/cli 0.44.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 +192 -165
- 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 +833 -54
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/types/config-data.d.ts +63 -1
- 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 +18 -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 +85 -7
- package/lib/utils/ship/formats.js +734 -15
- 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 +12 -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 +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 +71 -1
- package/lib/utils/ship/types.d.ts +158 -13
- 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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { HostOs, StagedFile } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The positional `gjsify ship <os>` takes, in ADR 0024 § A2's spelling.
|
|
4
|
+
*
|
|
5
|
+
* NOT the same vocabulary as {@link HostOs}, and the difference is deliberate
|
|
6
|
+
* rather than sloppy: § A2 writes `gjsify ship windows --stage`, while the
|
|
7
|
+
* repo-wide target spelling is `${process.platform}-${process.arch}` and
|
|
8
|
+
* therefore `win32-x64` (root AGENTS.md § Runtime & platform model). Both are
|
|
9
|
+
* accepted on the command line and exactly one is ever RECORDED — the stage
|
|
10
|
+
* manifest's `target.os` is a `HostOs`, so `--expect-target win32-x64` keeps
|
|
11
|
+
* meaning what it meant.
|
|
12
|
+
*/
|
|
13
|
+
export type LayoutName = 'linux' | 'darwin' | 'windows';
|
|
14
|
+
/** The layout-affecting half of `ShipSettings` — a display name and a binary name, nothing else. */
|
|
15
|
+
export interface LayoutIdentity {
|
|
16
|
+
/** Package name: the launcher's own filename. */
|
|
17
|
+
binaryName: string;
|
|
18
|
+
/** Human-readable display name — the `<App>.app` directory a user sees in Finder. */
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What a layout needs to render the metadata it OWNS.
|
|
23
|
+
*
|
|
24
|
+
* `LayoutIdentity` plus the three fields an `Info.plist` names the app by. It
|
|
25
|
+
* EXTENDS the identity rather than sitting beside it so `placeStage` keeps taking
|
|
26
|
+
* one argument and `ShipSettings` satisfies it structurally — no call site has to
|
|
27
|
+
* assemble a second object, and adding a field here is a compile error at exactly
|
|
28
|
+
* the rows that would have to answer for it.
|
|
29
|
+
*
|
|
30
|
+
* Everything here comes from the consumer's own `gjsify.ship` block. Nothing in
|
|
31
|
+
* this file is derived from gjsify's tree and nothing has a gjsify-specific
|
|
32
|
+
* default, which is the same line {@link LayoutDirs} draws for directories.
|
|
33
|
+
*/
|
|
34
|
+
export interface LayoutMetadataInput extends LayoutIdentity {
|
|
35
|
+
/** Reverse-DNS application id — `CFBundleIdentifier`, and what the data tree is keyed on. */
|
|
36
|
+
appId: string;
|
|
37
|
+
/** Upstream version — `CFBundleShortVersionString`. */
|
|
38
|
+
version: string;
|
|
39
|
+
/** Packaging revision within one upstream version — the second half of `CFBundleVersion`. */
|
|
40
|
+
release: string;
|
|
41
|
+
}
|
|
42
|
+
/** The four places a payload file can belong, as stage-relative directories. */
|
|
43
|
+
export interface LayoutDirs {
|
|
44
|
+
/** Where the launcher lives. */
|
|
45
|
+
launcher: string;
|
|
46
|
+
/** Where the app's own bundle tree lives. */
|
|
47
|
+
bundle: string;
|
|
48
|
+
/** Where the typelibs and shared libraries the app CARRIES live. */
|
|
49
|
+
native: string;
|
|
50
|
+
/** Where the XDG-shaped data tree lives (icons, schemas, metainfo, locale). */
|
|
51
|
+
data: string;
|
|
52
|
+
/**
|
|
53
|
+
* Where a prefix-relative path that sorts into none of the above goes.
|
|
54
|
+
*
|
|
55
|
+
* Reachable only through `gjsify.ship.extraFiles`, whose destinations are
|
|
56
|
+
* documented as prefix-relative and may name anything. On macOS that has to
|
|
57
|
+
* land inside `Contents/`, or the file sits beside it where nothing in the
|
|
58
|
+
* bundle can address it and `codesign` later refuses the bundle.
|
|
59
|
+
*/
|
|
60
|
+
other: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One operating system's layout.
|
|
64
|
+
*
|
|
65
|
+
* Data, not code: adding a fourth OS is a row here plus a launcher form, the same
|
|
66
|
+
* way adding a format is a row in `FORMATS`. The one function is {@link place},
|
|
67
|
+
* and it is shared by all three.
|
|
68
|
+
*/
|
|
69
|
+
export interface Layout {
|
|
70
|
+
/** The positional that selects this layout. */
|
|
71
|
+
name: LayoutName;
|
|
72
|
+
/** The `process.platform` token recorded in the stage manifest's `target.os`. */
|
|
73
|
+
os: HostOs;
|
|
74
|
+
/**
|
|
75
|
+
* The runtime ADR 0024 § 4 DERIVES for a shipped artifact on this OS.
|
|
76
|
+
*
|
|
77
|
+
* Recorded, not obeyed: it is what #1354 M0's platform packages implement,
|
|
78
|
+
* and it is NOT what the staged launcher execs today. Linux takes GJS from
|
|
79
|
+
* the distribution and the emitted `Depends: gjs` backs it; both other OSes
|
|
80
|
+
* take Node, because Node + `@gjsify/node-gi` + `@gjsify/gtk-runtime-<os>-<arch>`
|
|
81
|
+
* is the only combination either OS's CI proves.
|
|
82
|
+
*
|
|
83
|
+
* Keeping it as DATA rather than as a launcher decision is the whole of what
|
|
84
|
+
* M1 may honestly claim. A layout with no packer produces no installable
|
|
85
|
+
* artifact, so writing `exec node` into a stage assembled from a `--app gjs`
|
|
86
|
+
* bundle would put a runtime that cannot read the payload in front of it —
|
|
87
|
+
* the exact defect `assertShippableTarget` exists to prevent, produced by the
|
|
88
|
+
* code meant to prevent it. Measured before this was data: `gjsify ship
|
|
89
|
+
* darwin --stage` on a project with no `gjsify.app` key exited 0 and staged
|
|
90
|
+
* `exec node "$contents/Resources/lib/gjs.js"` in front of a bundle whose
|
|
91
|
+
* first line is `import Gtk from 'gi://Gtk?version=4.0'`.
|
|
92
|
+
*/
|
|
93
|
+
shippedRuntime: 'gjs' | 'node';
|
|
94
|
+
/**
|
|
95
|
+
* Why the staged launcher cannot name {@link shippedRuntime} yet, or absent
|
|
96
|
+
* when it already does.
|
|
97
|
+
*
|
|
98
|
+
* PRINTED at stage time rather than left as a comment: a tree whose launcher
|
|
99
|
+
* execs an interpreter the target OS may not have is a fact its author has to
|
|
100
|
+
* know, and it is the same fact that retires this field when M0 lands.
|
|
101
|
+
*/
|
|
102
|
+
runtimeGap?: string;
|
|
103
|
+
/** Suffix the launcher's filename carries, `''` where the OS needs none. */
|
|
104
|
+
launcherExt: string;
|
|
105
|
+
/**
|
|
106
|
+
* The directory inside the STAGE that this layout hangs under, or `''` when
|
|
107
|
+
* the stage root IS the layout root.
|
|
108
|
+
*
|
|
109
|
+
* The asymmetry is the OS's, not ours. A `<App>.app` is the artifact — it is
|
|
110
|
+
* dragged to `/Applications` as one unit, so the stage has to carry the
|
|
111
|
+
* directory itself. A Windows program directory is NOT: the installer chooses
|
|
112
|
+
* `C:\Program Files\<Publisher>\<App>` and lays the stage's contents into it,
|
|
113
|
+
* so a wrapper directory here would become a doubled path there.
|
|
114
|
+
*/
|
|
115
|
+
root: (identity: LayoutIdentity) => string;
|
|
116
|
+
/** The four destinations, resolved against one app's names. */
|
|
117
|
+
dirs: (identity: LayoutIdentity) => LayoutDirs;
|
|
118
|
+
/**
|
|
119
|
+
* Files this layout OWNS, already in stage-relative form — `[]` where it owns none.
|
|
120
|
+
*
|
|
121
|
+
* The seam {@link place} structurally cannot provide, and that is why it is a
|
|
122
|
+
* second function rather than another prefix rule. `Contents/Info.plist` has no
|
|
123
|
+
* prefix-relative counterpart to be mapped FROM: `planStage` emits one plan in
|
|
124
|
+
* the Linux/XDG shape, everything unmatched falls to `dirs.other`, and
|
|
125
|
+
* `assertInsidePrefix` forbids a plan entry from escaping upward — so no
|
|
126
|
+
* `gjsify.ship.extraFiles` value and no planner rule can put a file at the
|
|
127
|
+
* bundle root. The layout has to add it.
|
|
128
|
+
*
|
|
129
|
+
* REQUIRED on every row, not optional. An optional field is one a fourth OS can
|
|
130
|
+
* forget; a required one that returns `[]` is a row that has ANSWERED "this
|
|
131
|
+
* layout owns nothing", which is a different statement and the one worth being
|
|
132
|
+
* able to read. Linux and Windows both answer that today — a program directory
|
|
133
|
+
* and a `/usr` prefix have no manifest of their own — so the whole difference
|
|
134
|
+
* between "no metadata yet" and "no metadata ever" stays visible.
|
|
135
|
+
*
|
|
136
|
+
* The result is placed by `placeStage`, which applies the SAME uniqueness check
|
|
137
|
+
* it applies to mapped files: an `extraFiles` destination that lands on
|
|
138
|
+
* `Contents/Info.plist` is refused rather than silently replacing it.
|
|
139
|
+
*/
|
|
140
|
+
metadata: (input: LayoutMetadataInput) => StagedFile[];
|
|
141
|
+
/**
|
|
142
|
+
* The `process.arch` values this layout can be assembled for, or `null` when
|
|
143
|
+
* it imposes no limit of its own.
|
|
144
|
+
*
|
|
145
|
+
* REQUIRED on every row, like {@link metadata}, and for the same reason: `null`
|
|
146
|
+
* is an ANSWER ("this layout constrains nothing"), while an optional field is
|
|
147
|
+
* one a fourth OS forgets and then silently inherits Linux's answer. Linux is
|
|
148
|
+
* the `null` row — a `.deb` exists for every architecture Debian has, and
|
|
149
|
+
* `DEBIAN_ARCH`/`RPM_ARCH` in `formats.ts` already refuse the ones this project
|
|
150
|
+
* has no name for.
|
|
151
|
+
*
|
|
152
|
+
* WHY IT IS ON THE LAYOUT AND NOT ONLY ON THE FORMAT, since
|
|
153
|
+
* {@link assertLayoutSupportsArch} runs in `packOne` — the same phase
|
|
154
|
+
* `FormatDescriptor.archName` does, and this field buys neither an earlier
|
|
155
|
+
* refusal nor a different one. It buys the two things a table lookup cannot:
|
|
156
|
+
*
|
|
157
|
+
* * a REASON. `archName` answers "no Windows architecture is known for
|
|
158
|
+
* `process.arch` \"arm64\"", which reads as a gap in our table. The truth is
|
|
159
|
+
* that gvsbuild publishes no arm64 GTK, the blocker is upstream, and #1117
|
|
160
|
+
* is where it is tracked — and the difference between "unsupported" and
|
|
161
|
+
* "here is what would have to change" is the whole value of the message.
|
|
162
|
+
* * a STAGE-TIME warning at the flag that caused it (`commands/ship.ts`), for
|
|
163
|
+
* a phase that deliberately does not refuse — `tests/e2e/ship-layout`
|
|
164
|
+
* assembles all three layouts from ONE payload on purpose, and that
|
|
165
|
+
* payload's native file has an architecture.
|
|
166
|
+
*
|
|
167
|
+
* The constraint is also a fact about the layout's RUNTIME story — which GTK
|
|
168
|
+
* closure and which interpreter exist for it — rather than about any one
|
|
169
|
+
* container, which is why the format rows read it back instead of restating it.
|
|
170
|
+
*/
|
|
171
|
+
arches: LayoutArches | null;
|
|
172
|
+
}
|
|
173
|
+
/** The architectures one layout is assemblable for, and why the others are not. */
|
|
174
|
+
export interface LayoutArches {
|
|
175
|
+
/** `process.arch` spellings — the same vocabulary `--arch` takes. */
|
|
176
|
+
only: readonly string[];
|
|
177
|
+
/** What a reader must know to act on the refusal: who blocks it, and where it is tracked. */
|
|
178
|
+
why: string;
|
|
179
|
+
}
|
|
180
|
+
export declare const LAYOUTS: Record<LayoutName, Layout>;
|
|
181
|
+
/** Every layout name, derived from the table so a new row cannot be missed. */
|
|
182
|
+
export declare const LAYOUT_NAMES: LayoutName[];
|
|
183
|
+
/** Stage-relative path of the launcher this layout puts in front of the app. */
|
|
184
|
+
export declare function launcherPath(layout: Layout, identity: LayoutIdentity): string;
|
|
185
|
+
/**
|
|
186
|
+
* The layout for one `process.platform` token, refusing every alias.
|
|
187
|
+
*
|
|
188
|
+
* STRICT, and that is the difference from {@link resolveLayout}, which is for a
|
|
189
|
+
* word a human typed. This one is for the stage manifest's `target.os` — a
|
|
190
|
+
* cross-host WIRE FORMAT with exactly one legal spelling per OS. Accepting
|
|
191
|
+
* `"windows"` there and silently comparing it as `win32` would mean
|
|
192
|
+
* `--expect-target win32-x64` no longer compares against the file's content:
|
|
193
|
+
* two manifests with different bytes in that field would both match, and the
|
|
194
|
+
* flag's whole job is to catch a job that downloaded the wrong artifact.
|
|
195
|
+
*/
|
|
196
|
+
export declare function layoutForOs(os: string): Layout;
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the positional to a layout.
|
|
199
|
+
*
|
|
200
|
+
* Accepts BOTH vocabularies (`windows` and `win32`) for the reason
|
|
201
|
+
* {@link LayoutName} records: the ADR writes one and `--expect-target` prints the
|
|
202
|
+
* other, and refusing whichever the user has in front of them is a papercut with
|
|
203
|
+
* no upside. The canonical answer is a single `Layout`, so nothing downstream has
|
|
204
|
+
* two spellings to reconcile. NOT for the stage manifest — see {@link layoutForOs}.
|
|
205
|
+
*/
|
|
206
|
+
export declare function resolveLayout(raw: string): Layout;
|
|
207
|
+
/**
|
|
208
|
+
* The layout for the host this process is running on.
|
|
209
|
+
*
|
|
210
|
+
* What `gjsify ship` picks when the positional is absent, so a bare `gjsify ship`
|
|
211
|
+
* on Linux keeps assembling the Linux layout and packing `.deb` + `.rpm` exactly
|
|
212
|
+
* as before. A host whose platform has no layout is refused BY NAME rather than
|
|
213
|
+
* silently treated as Linux — a payload laid out for `/usr` and labelled for a
|
|
214
|
+
* FreeBSD would install and not run.
|
|
215
|
+
*/
|
|
216
|
+
export declare function hostLayout(platform?: string): Layout;
|
|
217
|
+
/**
|
|
218
|
+
* Refuse an architecture this layout cannot be assembled for, naming the blocker.
|
|
219
|
+
*
|
|
220
|
+
* AT STAGE TIME, which is the whole point — see {@link Layout.arches}. The
|
|
221
|
+
* message carries the row's own `why` rather than a generic "unsupported", because
|
|
222
|
+
* the two refusals a reader can act on are different jobs: `x64` is what a Windows
|
|
223
|
+
* artifact is built for TODAY, and `win32-arm64` is blocked in a repository we do
|
|
224
|
+
* not own. Telling an author "unknown architecture" for the second one would send
|
|
225
|
+
* them to look for a flag.
|
|
226
|
+
*
|
|
227
|
+
* `process.arch` spelling on both sides, so the value quoted back is the one the
|
|
228
|
+
* user typed after `--arch`.
|
|
229
|
+
*/
|
|
230
|
+
export declare function assertLayoutSupportsArch(layout: Layout, arch: string): void;
|
|
231
|
+
/**
|
|
232
|
+
* Map one PREFIX-RELATIVE planned path to where this layout keeps it.
|
|
233
|
+
*
|
|
234
|
+
* The plan is written once, in the Linux/XDG shape, and this is the only place
|
|
235
|
+
* that knows any other. Order matters: `lib/<name>/gi/` is tested before
|
|
236
|
+
* `lib/<name>/`, because on macOS those two go to different directories.
|
|
237
|
+
*/
|
|
238
|
+
export declare function place(layout: Layout, identity: LayoutIdentity, rel: string): string;
|
|
239
|
+
/**
|
|
240
|
+
* Apply a layout to a whole planned payload, and add what the layout itself owns.
|
|
241
|
+
*
|
|
242
|
+
* THREE SOURCES, one destination namespace. The mapped files come from
|
|
243
|
+
* `planStage`'s prefix-relative plan; {@link Layout.metadata} adds the files that
|
|
244
|
+
* have no prefix-relative counterpart at all (`Contents/Info.plist`); `carried`
|
|
245
|
+
* adds the runtime the artifact ships inside itself (#1354 M2b — the interpreter,
|
|
246
|
+
* the relocated GTK closure, the node-gi addon). All three go through the same
|
|
247
|
+
* uniqueness check, which is what makes the other two safe: an `extraFiles`
|
|
248
|
+
* destination that maps onto a metadata path is refused by name instead of
|
|
249
|
+
* silently replacing the file that makes the bundle a bundle.
|
|
250
|
+
*
|
|
251
|
+
* `carried` is already stage-relative and deliberately NOT mapped. A relocated
|
|
252
|
+
* closure's every internal relation is relative — `@loader_path/<leaf>` install
|
|
253
|
+
* names, `@loader_path/../../..` in `loaders.cache`, `@loader_path/gtk/lib` on the
|
|
254
|
+
* addon — so the tree survives the copy only if it is copied as a tree. Routing it
|
|
255
|
+
* through the prefix-relative plan would put it through
|
|
256
|
+
* `gjsify.ship.bundledTypelibs`'s flattening (`plan.ts`: `posix.join(libDir, 'gi',
|
|
257
|
+
* basename(file))`), which destroys all three at once.
|
|
258
|
+
*
|
|
259
|
+
* The uniqueness check is not decoration for the mapped half either. `planStage`
|
|
260
|
+
* deduplicates on the PREFIX-RELATIVE path — that is how `gjsify.ship.extraFiles`
|
|
261
|
+
* is documented to override a default — but the MAP can bring two distinct
|
|
262
|
+
* prefix-relative paths together: on Windows the launcher becomes
|
|
263
|
+
* `<binaryName>.cmd` at the program root, which an `extraFiles` destination can
|
|
264
|
+
* name directly. Without this the second one silently wins and which one that is
|
|
265
|
+
* depends on plan order — the "installs cleanly, does nothing" class this command
|
|
266
|
+
* is built against.
|
|
267
|
+
*/
|
|
268
|
+
export declare function placeStage(layout: Layout, identity: LayoutMetadataInput, files: readonly StagedFile[], carried?: readonly StagedFile[]): StagedFile[];
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// WHERE a payload's files land, per operating system (ADR 0024 § 2, § A2).
|
|
2
|
+
//
|
|
3
|
+
// The payload is produced ONCE and is identical everywhere; what differs is the
|
|
4
|
+
// layout. Up to here that difference was a single string — `FormatDescriptor.prefix`,
|
|
5
|
+
// `/usr` for deb and rpm, `/app` for a Flatpak — because every Linux format keeps
|
|
6
|
+
// the same `bin/`, `lib/<name>/`, `share/…` shape underneath it. `Contents/MacOS`
|
|
7
|
+
// is the first layout that is NOT a prefix substitution: the launcher, the bundle,
|
|
8
|
+
// the shared libraries and the data tree each move somewhere different, and on
|
|
9
|
+
// macOS the native files leave the bundle directory altogether for
|
|
10
|
+
// `Contents/Frameworks`.
|
|
11
|
+
//
|
|
12
|
+
// So the layout is a MAP from the prefix-relative plan to a stage-relative path,
|
|
13
|
+
// and `planStage` keeps producing the prefix-relative plan for all three. That
|
|
14
|
+
// split is what makes the claim checkable: one payload, three layouts, and the
|
|
15
|
+
// file SET has to be identical modulo this map (`tests/e2e/ship-layout`).
|
|
16
|
+
//
|
|
17
|
+
// It also draws the line a third-party app author needs. Everything structural
|
|
18
|
+
// here belongs to the OS — `Contents/MacOS` is Apple's, `%~dp0` is cmd.exe's.
|
|
19
|
+
// Everything with a NAME in it comes from the consumer's own `gjsify.ship` block:
|
|
20
|
+
// the `<App>.app` directory is `name`, the launcher inside it is `binaryName`, and
|
|
21
|
+
// the data tree is addressed by `appId`. Nothing here is derived from gjsify's own
|
|
22
|
+
// tree, and nothing here has a gjsify-specific default.
|
|
23
|
+
import { posix } from 'node:path';
|
|
24
|
+
import { BUNDLE_INFO_PLIST, BUNDLE_PKGINFO, renderInfoPlist, renderPkgInfo } from './plist.js';
|
|
25
|
+
/**
|
|
26
|
+
* Characters a `<App>.app` directory name may not contain.
|
|
27
|
+
*
|
|
28
|
+
* `/` is the POSIX separator and `:` is HFS+'s — the Finder shows a stored `:` as
|
|
29
|
+
* a `/` and vice versa, so a name carrying either produces a bundle whose
|
|
30
|
+
* directory is not the one its own metadata names. Refused here rather than at
|
|
31
|
+
* `hdiutil` time, which is three milestones away and on another host.
|
|
32
|
+
*/
|
|
33
|
+
const BUNDLE_NAME_FORBIDDEN = /[/:\\]/;
|
|
34
|
+
function appBundleDir(identity) {
|
|
35
|
+
if (BUNDLE_NAME_FORBIDDEN.test(identity.name)) {
|
|
36
|
+
throw new Error(`gjsify ship: the macOS layout would put this app in "${identity.name}.app", and a bundle ` +
|
|
37
|
+
'directory may contain no "/", "\\" or ":" — HFS+ and the Finder swap the first two, so the ' +
|
|
38
|
+
'bundle would not be the one its own metadata names. Set `gjsify.ship.name` to the display ' +
|
|
39
|
+
'name you want on disk.');
|
|
40
|
+
}
|
|
41
|
+
return `${identity.name}.app`;
|
|
42
|
+
}
|
|
43
|
+
export const LAYOUTS = {
|
|
44
|
+
linux: {
|
|
45
|
+
name: 'linux',
|
|
46
|
+
os: 'linux',
|
|
47
|
+
shippedRuntime: 'gjs',
|
|
48
|
+
launcherExt: '',
|
|
49
|
+
root: () => '',
|
|
50
|
+
// The app id in the directory name is what makes `/usr/lib/<pkg>/` a
|
|
51
|
+
// directory the PACKAGE owns — the prefix is shared with every other
|
|
52
|
+
// package on the system. Inside a `.app` or a program directory nothing
|
|
53
|
+
// is shared, which is why the other two rows drop it.
|
|
54
|
+
dirs: (identity) => ({
|
|
55
|
+
launcher: 'bin',
|
|
56
|
+
bundle: `lib/${identity.binaryName}`,
|
|
57
|
+
native: `lib/${identity.binaryName}/gi`,
|
|
58
|
+
data: 'share',
|
|
59
|
+
other: '',
|
|
60
|
+
}),
|
|
61
|
+
// A `/usr` prefix has no manifest of its own — everything a Linux package
|
|
62
|
+
// says about itself is in the `.deb`/`.rpm` header or in the freedesktop
|
|
63
|
+
// files the payload already carries.
|
|
64
|
+
metadata: () => [],
|
|
65
|
+
// Every architecture Debian and rpm have a name for, which is what
|
|
66
|
+
// `DEBIAN_ARCH`/`RPM_ARCH` already enumerate — the layout adds nothing.
|
|
67
|
+
arches: null,
|
|
68
|
+
},
|
|
69
|
+
darwin: {
|
|
70
|
+
name: 'darwin',
|
|
71
|
+
os: 'darwin',
|
|
72
|
+
shippedRuntime: 'node',
|
|
73
|
+
// GJS does exist on macOS — Homebrew ships it and the `macos` CI leg runs
|
|
74
|
+
// `test:gjs` on both arches — so a launcher naming it is true of a
|
|
75
|
+
// developer's machine and NOT of a `.app` a stranger downloads:
|
|
76
|
+
// `packages/node-gi/scripts/build-gtk-runtime-darwin.mjs` records that GJS
|
|
77
|
+
// ships no relocation, which is why § 4 derives Node here.
|
|
78
|
+
runtimeGap: 'the staged launcher execs an interpreter off `PATH`, which a downloaded `.app` cannot assume — ' +
|
|
79
|
+
'macOS ships no Node, and there is no RELOCATABLE GJS (`build-gtk-runtime-darwin.mjs`: "GJS ' +
|
|
80
|
+
'ships no relocation"), so ADR 0024 § 4 derives Node here. A self-contained bundle needs `@gjsify/node-runtime-darwin-<arch>` and a ' +
|
|
81
|
+
'`--app node` payload (#1354 M0), or `@gjsify/gjs-runtime-darwin-<arch>` (ADR 0024 stage 7).',
|
|
82
|
+
launcherExt: '',
|
|
83
|
+
root: appBundleDir,
|
|
84
|
+
// Apple's, all four. `Contents/MacOS` holds executables, `Contents/Resources`
|
|
85
|
+
// everything that is not, and `Contents/Frameworks` the dylibs — which is
|
|
86
|
+
// the split that makes this more than a prefix: the `gi/` directory that
|
|
87
|
+
// sits INSIDE `lib/<name>/` on Linux leaves the bundle directory entirely.
|
|
88
|
+
dirs: (identity) => {
|
|
89
|
+
const app = appBundleDir(identity);
|
|
90
|
+
return {
|
|
91
|
+
launcher: `${app}/Contents/MacOS`,
|
|
92
|
+
bundle: `${app}/Contents/Resources/lib`,
|
|
93
|
+
native: `${app}/Contents/Frameworks`,
|
|
94
|
+
data: `${app}/Contents/Resources/share`,
|
|
95
|
+
other: `${app}/Contents/Resources`,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
// The two files that make the directory a BUNDLE. Without `Info.plist`
|
|
99
|
+
// nothing tells LaunchServices which file under `Contents/MacOS` to exec,
|
|
100
|
+
// and a `*.app` with no `Info.plist` is a folder with a suffix — which is
|
|
101
|
+
// exactly what M1 staged. `plist.ts` carries the per-key citations.
|
|
102
|
+
metadata: (input) => [
|
|
103
|
+
{
|
|
104
|
+
path: `${appBundleDir(input)}/${BUNDLE_INFO_PLIST}`,
|
|
105
|
+
mode: 0o644,
|
|
106
|
+
source: { kind: 'text', text: renderInfoPlist(input) },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
path: `${appBundleDir(input)}/${BUNDLE_PKGINFO}`,
|
|
110
|
+
mode: 0o644,
|
|
111
|
+
source: { kind: 'text', text: renderPkgInfo() },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
// The two architectures macOS runs on, which is also exactly what
|
|
115
|
+
// `@gjsify/gtk-runtime-darwin-*` and `@gjsify/node-runtime-darwin-*` are
|
|
116
|
+
// published for. Nothing upstream blocks a third; there is no third.
|
|
117
|
+
arches: {
|
|
118
|
+
only: ['x64', 'arm64'],
|
|
119
|
+
why: 'macOS runs on x86_64 and Apple silicon and nothing else, so those are the two ' +
|
|
120
|
+
'`@gjsify/gtk-runtime-darwin-*` and `@gjsify/node-runtime-darwin-*` targets that exist',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
windows: {
|
|
124
|
+
name: 'windows',
|
|
125
|
+
os: 'win32',
|
|
126
|
+
shippedRuntime: 'node',
|
|
127
|
+
// The stronger of the two gaps, and not a relocation question: there is
|
|
128
|
+
// no GJS host on Windows AT ALL, so nothing on that OS can run a
|
|
129
|
+
// `--app gjs` payload until M0's bundled Node exists and the payload is
|
|
130
|
+
// built for it. Cited to ADR 0024 § 4, which is where the fact is
|
|
131
|
+
// established — an earlier version of this string cited
|
|
132
|
+
// `docs/ci-selective.md`, which contains no occurrence of it and whose
|
|
133
|
+
// four `gjs` mentions are all about the affected-classifier bundle. The
|
|
134
|
+
// bad citation came in from the ADR's own § 4 table and was promoted into
|
|
135
|
+
// a user-visible string, which is the worst place for one.
|
|
136
|
+
runtimeGap: 'the staged launcher execs an interpreter off `PATH`, which a downloaded program directory ' +
|
|
137
|
+
'cannot assume — Windows ships neither, and there is NO GJS host on Windows at all ' +
|
|
138
|
+
'(ADR 0024 § 4), so § 4 derives Node here. A self-contained directory needs ' +
|
|
139
|
+
'`@gjsify/node-runtime-win32-x64` and a `--app node` payload (#1354 M3).',
|
|
140
|
+
// `.cmd`, not `.bat`: the two differ in whether a failing built-in (`set`,
|
|
141
|
+
// `path`, `append`) sets ERRORLEVEL, and `.cmd` is the one where it does.
|
|
142
|
+
launcherExt: '.cmd',
|
|
143
|
+
root: () => '',
|
|
144
|
+
// A program directory has no imposed shape, so this one is chosen for the
|
|
145
|
+
// LOADER: `lib/` is what the launcher prepends to `PATH`, because Windows
|
|
146
|
+
// has no rpath — a DLL is found on `PATH`, in the directory of the image
|
|
147
|
+
// that loaded it, or not at all.
|
|
148
|
+
dirs: () => ({ launcher: '', bundle: 'app', native: 'lib', data: 'share', other: '' }),
|
|
149
|
+
// Nothing, and this is the row that shows the field is not a macOS detail
|
|
150
|
+
// wearing a general name: a Windows installer's metadata lives in the
|
|
151
|
+
// `.msi`'s own tables, not in a file inside the program directory. THREE
|
|
152
|
+
// format rows wrap this layout now, and the installer — the one that could
|
|
153
|
+
// have needed a file here — did not: `msi.ts` puts every such fact in the
|
|
154
|
+
// `Product`/`Package` attributes of the document it renders. So the answer
|
|
155
|
+
// held rather than the question going unasked, and if a fourth row ever
|
|
156
|
+
// needs a file, THIS is where it goes — not a branch in the stager.
|
|
157
|
+
metadata: () => [],
|
|
158
|
+
// ONE, and the blocker is a project we do not own. `wingtk/gvsbuild`
|
|
159
|
+
// hardcodes `self.platform = "x64"` in `utils/base_project.py` and its last
|
|
160
|
+
// five releases publish exactly two assets each, both x64 — so there is no
|
|
161
|
+
// arm64 GTK to build `@gjsify/gtk-runtime-win32-arm64` OUT OF, and on
|
|
162
|
+
// Windows that bundle is the only GTK there is. `@gjsify/node-gi` declares
|
|
163
|
+
// `win32-x64` only for the same reason. Tracked in #1117, which also
|
|
164
|
+
// records why an exploratory arm64 leg must NOT be added: its first step
|
|
165
|
+
// downloads a ZIP that does not exist, so it would be red by construction.
|
|
166
|
+
arches: {
|
|
167
|
+
only: ['x64'],
|
|
168
|
+
why: 'gvsbuild publishes no arm64 GTK (it hardcodes `self.platform = "x64"`), so there is nothing ' +
|
|
169
|
+
'to build `@gjsify/gtk-runtime-win32-arm64` out of and no GTK for a Windows/ARM artifact to ' +
|
|
170
|
+
'load — the blocker is upstream and is tracked in gjsify/gjsify#1117',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
/** Every layout name, derived from the table so a new row cannot be missed. */
|
|
175
|
+
export const LAYOUT_NAMES = Object.keys(LAYOUTS);
|
|
176
|
+
/**
|
|
177
|
+
* `HostOs` → the layout for it.
|
|
178
|
+
*
|
|
179
|
+
* Derived rather than a second table: `LAYOUTS` is already total over
|
|
180
|
+
* `LayoutName`, so reading its rows back means a fourth OS cannot be added to one
|
|
181
|
+
* list and forgotten in the other.
|
|
182
|
+
*/
|
|
183
|
+
const BY_OS = new Map(LAYOUT_NAMES.map((name) => [LAYOUTS[name].os, LAYOUTS[name]]));
|
|
184
|
+
/** Stage-relative path of the launcher this layout puts in front of the app. */
|
|
185
|
+
export function launcherPath(layout, identity) {
|
|
186
|
+
return posix.join(layout.dirs(identity).launcher, `${identity.binaryName}${layout.launcherExt}`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The layout for one `process.platform` token, refusing every alias.
|
|
190
|
+
*
|
|
191
|
+
* STRICT, and that is the difference from {@link resolveLayout}, which is for a
|
|
192
|
+
* word a human typed. This one is for the stage manifest's `target.os` — a
|
|
193
|
+
* cross-host WIRE FORMAT with exactly one legal spelling per OS. Accepting
|
|
194
|
+
* `"windows"` there and silently comparing it as `win32` would mean
|
|
195
|
+
* `--expect-target win32-x64` no longer compares against the file's content:
|
|
196
|
+
* two manifests with different bytes in that field would both match, and the
|
|
197
|
+
* flag's whole job is to catch a job that downloaded the wrong artifact.
|
|
198
|
+
*/
|
|
199
|
+
export function layoutForOs(os) {
|
|
200
|
+
const layout = BY_OS.get(os);
|
|
201
|
+
if (layout === undefined) {
|
|
202
|
+
throw new Error(`"${os}" is not a \`process.platform\` token this gjsify has a layout for. ` +
|
|
203
|
+
`Known: ${LAYOUT_NAMES.map((name) => LAYOUTS[name].os).join(', ')} — the ` +
|
|
204
|
+
'`${process.platform}-${process.arch}` spelling, so `win32` and never `windows`.');
|
|
205
|
+
}
|
|
206
|
+
return layout;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the positional to a layout.
|
|
210
|
+
*
|
|
211
|
+
* Accepts BOTH vocabularies (`windows` and `win32`) for the reason
|
|
212
|
+
* {@link LayoutName} records: the ADR writes one and `--expect-target` prints the
|
|
213
|
+
* other, and refusing whichever the user has in front of them is a papercut with
|
|
214
|
+
* no upside. The canonical answer is a single `Layout`, so nothing downstream has
|
|
215
|
+
* two spellings to reconcile. NOT for the stage manifest — see {@link layoutForOs}.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveLayout(raw) {
|
|
218
|
+
const name = raw.trim().toLowerCase();
|
|
219
|
+
if (Object.hasOwn(LAYOUTS, name))
|
|
220
|
+
return LAYOUTS[name];
|
|
221
|
+
const byOs = BY_OS.get(name);
|
|
222
|
+
if (byOs !== undefined)
|
|
223
|
+
return byOs;
|
|
224
|
+
throw new Error(`gjsify ship: "${raw}" is not an operating system this command has a layout for. ` +
|
|
225
|
+
`Known: ${LAYOUT_NAMES.join(', ')} (\`win32\` is accepted for \`windows\`, because that is the ` +
|
|
226
|
+
'spelling `--expect-target <os>-<arch>` uses).');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The layout for the host this process is running on.
|
|
230
|
+
*
|
|
231
|
+
* What `gjsify ship` picks when the positional is absent, so a bare `gjsify ship`
|
|
232
|
+
* on Linux keeps assembling the Linux layout and packing `.deb` + `.rpm` exactly
|
|
233
|
+
* as before. A host whose platform has no layout is refused BY NAME rather than
|
|
234
|
+
* silently treated as Linux — a payload laid out for `/usr` and labelled for a
|
|
235
|
+
* FreeBSD would install and not run.
|
|
236
|
+
*/
|
|
237
|
+
export function hostLayout(platform = process.platform) {
|
|
238
|
+
const layout = BY_OS.get(platform);
|
|
239
|
+
if (layout === undefined) {
|
|
240
|
+
throw new Error(`gjsify ship: this host is ${platform}, which has no layout. Name the one you want — ` +
|
|
241
|
+
`\`gjsify ship <${LAYOUT_NAMES.join('|')}> --stage\` — assembly is not host-bound ` +
|
|
242
|
+
'(ADR 0024 § A1).');
|
|
243
|
+
}
|
|
244
|
+
return layout;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Refuse an architecture this layout cannot be assembled for, naming the blocker.
|
|
248
|
+
*
|
|
249
|
+
* AT STAGE TIME, which is the whole point — see {@link Layout.arches}. The
|
|
250
|
+
* message carries the row's own `why` rather than a generic "unsupported", because
|
|
251
|
+
* the two refusals a reader can act on are different jobs: `x64` is what a Windows
|
|
252
|
+
* artifact is built for TODAY, and `win32-arm64` is blocked in a repository we do
|
|
253
|
+
* not own. Telling an author "unknown architecture" for the second one would send
|
|
254
|
+
* them to look for a flag.
|
|
255
|
+
*
|
|
256
|
+
* `process.arch` spelling on both sides, so the value quoted back is the one the
|
|
257
|
+
* user typed after `--arch`.
|
|
258
|
+
*/
|
|
259
|
+
export function assertLayoutSupportsArch(layout, arch) {
|
|
260
|
+
const arches = layout.arches;
|
|
261
|
+
if (arches === null || arches.only.includes(arch))
|
|
262
|
+
return;
|
|
263
|
+
throw new Error(`gjsify ship: the ${layout.name} layout is not assemblable for \`--arch ${arch}\` — ` +
|
|
264
|
+
`${arches.only.length === 1 ? 'the only architecture it has' : 'the architectures it has'} ` +
|
|
265
|
+
`${arches.only.length === 1 ? 'is' : 'are'} ${arches.only.join(', ')}.\n` +
|
|
266
|
+
` ${arches.why}.\n` +
|
|
267
|
+
' `--arch` names the architecture the PAYLOAD was built for and cross-compiles nothing, so ' +
|
|
268
|
+
'there is no\n' +
|
|
269
|
+
' flag that makes this work — the artifact would carry a runtime that does not exist.');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Map one PREFIX-RELATIVE planned path to where this layout keeps it.
|
|
273
|
+
*
|
|
274
|
+
* The plan is written once, in the Linux/XDG shape, and this is the only place
|
|
275
|
+
* that knows any other. Order matters: `lib/<name>/gi/` is tested before
|
|
276
|
+
* `lib/<name>/`, because on macOS those two go to different directories.
|
|
277
|
+
*/
|
|
278
|
+
export function place(layout, identity, rel) {
|
|
279
|
+
const dirs = layout.dirs(identity);
|
|
280
|
+
if (rel === `bin/${identity.binaryName}`)
|
|
281
|
+
return launcherPath(layout, identity);
|
|
282
|
+
const libPrefix = `lib/${identity.binaryName}/`;
|
|
283
|
+
const giPrefix = `${libPrefix}gi/`;
|
|
284
|
+
if (rel.startsWith(giPrefix))
|
|
285
|
+
return posix.join(dirs.native, rel.slice(giPrefix.length));
|
|
286
|
+
if (rel.startsWith(libPrefix))
|
|
287
|
+
return posix.join(dirs.bundle, rel.slice(libPrefix.length));
|
|
288
|
+
if (rel.startsWith('bin/'))
|
|
289
|
+
return posix.join(dirs.launcher, rel.slice('bin/'.length));
|
|
290
|
+
if (rel.startsWith('share/'))
|
|
291
|
+
return posix.join(dirs.data, rel.slice('share/'.length));
|
|
292
|
+
return posix.join(dirs.other, rel);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Apply a layout to a whole planned payload, and add what the layout itself owns.
|
|
296
|
+
*
|
|
297
|
+
* THREE SOURCES, one destination namespace. The mapped files come from
|
|
298
|
+
* `planStage`'s prefix-relative plan; {@link Layout.metadata} adds the files that
|
|
299
|
+
* have no prefix-relative counterpart at all (`Contents/Info.plist`); `carried`
|
|
300
|
+
* adds the runtime the artifact ships inside itself (#1354 M2b — the interpreter,
|
|
301
|
+
* the relocated GTK closure, the node-gi addon). All three go through the same
|
|
302
|
+
* uniqueness check, which is what makes the other two safe: an `extraFiles`
|
|
303
|
+
* destination that maps onto a metadata path is refused by name instead of
|
|
304
|
+
* silently replacing the file that makes the bundle a bundle.
|
|
305
|
+
*
|
|
306
|
+
* `carried` is already stage-relative and deliberately NOT mapped. A relocated
|
|
307
|
+
* closure's every internal relation is relative — `@loader_path/<leaf>` install
|
|
308
|
+
* names, `@loader_path/../../..` in `loaders.cache`, `@loader_path/gtk/lib` on the
|
|
309
|
+
* addon — so the tree survives the copy only if it is copied as a tree. Routing it
|
|
310
|
+
* through the prefix-relative plan would put it through
|
|
311
|
+
* `gjsify.ship.bundledTypelibs`'s flattening (`plan.ts`: `posix.join(libDir, 'gi',
|
|
312
|
+
* basename(file))`), which destroys all three at once.
|
|
313
|
+
*
|
|
314
|
+
* The uniqueness check is not decoration for the mapped half either. `planStage`
|
|
315
|
+
* deduplicates on the PREFIX-RELATIVE path — that is how `gjsify.ship.extraFiles`
|
|
316
|
+
* is documented to override a default — but the MAP can bring two distinct
|
|
317
|
+
* prefix-relative paths together: on Windows the launcher becomes
|
|
318
|
+
* `<binaryName>.cmd` at the program root, which an `extraFiles` destination can
|
|
319
|
+
* name directly. Without this the second one silently wins and which one that is
|
|
320
|
+
* depends on plan order — the "installs cleanly, does nothing" class this command
|
|
321
|
+
* is built against.
|
|
322
|
+
*/
|
|
323
|
+
export function placeStage(layout, identity, files, carried = []) {
|
|
324
|
+
const byPlaced = new Map();
|
|
325
|
+
const out = [];
|
|
326
|
+
const claim = (path, from, file) => {
|
|
327
|
+
const previous = byPlaced.get(path);
|
|
328
|
+
if (previous !== undefined) {
|
|
329
|
+
throw new Error(`gjsify ship: in the ${layout.name} layout both ${previous} and ${from} install as ` +
|
|
330
|
+
`${path}. Two payload files cannot share one destination — one would silently replace the ` +
|
|
331
|
+
'other. Rename the `gjsify.ship.extraFiles` destination that collides.');
|
|
332
|
+
}
|
|
333
|
+
byPlaced.set(path, from);
|
|
334
|
+
out.push({ ...file, path });
|
|
335
|
+
};
|
|
336
|
+
for (const file of files)
|
|
337
|
+
claim(place(layout, identity, file.path), file.path, file);
|
|
338
|
+
// The layout's own files are ALREADY stage-relative — they were never in the
|
|
339
|
+
// prefix-relative plan, so there is nothing to map. `<layout> metadata` rather
|
|
340
|
+
// than a path as the origin label, because a collision message naming
|
|
341
|
+
// `Contents/Info.plist` as both sides would say nothing.
|
|
342
|
+
for (const file of layout.metadata(identity))
|
|
343
|
+
claim(file.path, `the ${layout.name} layout's own metadata`, file);
|
|
344
|
+
// A THIRD source, and stage-relative like the second: the runtime the bundle
|
|
345
|
+
// CARRIES (`utils/ship/app-runtime.ts`). It goes through `claim` for the reason
|
|
346
|
+
// the metadata does — an `extraFiles` destination landing on a staged dylib
|
|
347
|
+
// would otherwise replace it silently, and the bundle would look complete.
|
|
348
|
+
for (const file of carried)
|
|
349
|
+
claim(file.path, `the runtime staged into the ${layout.name} artifact`, file);
|
|
350
|
+
return out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
351
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The generated metadata, before or after translation. */
|
|
2
|
+
export interface LocalizableMetadata {
|
|
3
|
+
/** The rendered AppStream MetaInfo XML. */
|
|
4
|
+
metainfo: string;
|
|
5
|
+
/** The rendered desktop entry — `undefined` for `kind: 'cli'`. */
|
|
6
|
+
desktopEntry?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A staged catalogue, in the `<lang>/LC_MESSAGES/<domain>.mo` shape `discoverLocales` guarantees. */
|
|
9
|
+
export interface StagedCatalogue {
|
|
10
|
+
rel: string;
|
|
11
|
+
abs: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Translate the generated metadata with the catalogues the package ships.
|
|
15
|
+
*
|
|
16
|
+
* Returns the input unchanged when there is nothing to fold in, so a project
|
|
17
|
+
* without a `localeDir` never needs the gettext tools. A project WITH catalogues
|
|
18
|
+
* that lacks them is REFUSED rather than shipped in English: an untranslated
|
|
19
|
+
* `.desktop` is indistinguishable from a correct one at install time, and the
|
|
20
|
+
* package would have promised translations it does not deliver.
|
|
21
|
+
*/
|
|
22
|
+
export declare function localizeMetadata(metadata: LocalizableMetadata, localeFiles: readonly StagedCatalogue[]): LocalizableMetadata;
|