@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.
Files changed (73) hide show
  1. package/dist/cli.gjs.mjs +192 -165
  2. package/lib/commands/gettext.d.ts +2 -1
  3. package/lib/commands/gettext.js +105 -47
  4. package/lib/commands/onboard.d.ts +7 -0
  5. package/lib/commands/onboard.js +349 -96
  6. package/lib/commands/ship.d.ts +3 -0
  7. package/lib/commands/ship.js +833 -54
  8. package/lib/commands/trust.d.ts +6 -0
  9. package/lib/commands/trust.js +1 -1
  10. package/lib/types/config-data.d.ts +63 -1
  11. package/lib/utils/msgfmt-merge.d.ts +35 -0
  12. package/lib/utils/msgfmt-merge.js +102 -0
  13. package/lib/utils/npm-otp.d.ts +27 -2
  14. package/lib/utils/npm-otp.js +82 -7
  15. package/lib/utils/onboard-discovery.d.ts +68 -0
  16. package/lib/utils/onboard-discovery.js +178 -0
  17. package/lib/utils/onboard-probe.d.ts +118 -1
  18. package/lib/utils/onboard-probe.js +153 -4
  19. package/lib/utils/prompt-output.d.ts +12 -0
  20. package/lib/utils/prompt-output.js +54 -0
  21. package/lib/utils/prompt.js +22 -2
  22. package/lib/utils/publishable-packages.d.ts +1 -1
  23. package/lib/utils/publishable-packages.js +1 -0
  24. package/lib/utils/resolve-npm-package.d.ts +56 -0
  25. package/lib/utils/resolve-npm-package.js +69 -0
  26. package/lib/utils/ship/app-runtime.d.ts +214 -0
  27. package/lib/utils/ship/app-runtime.js +429 -0
  28. package/lib/utils/ship/depends.d.ts +50 -0
  29. package/lib/utils/ship/depends.js +117 -3
  30. package/lib/utils/ship/discover.js +18 -1
  31. package/lib/utils/ship/dmg.d.ts +71 -0
  32. package/lib/utils/ship/dmg.js +181 -0
  33. package/lib/utils/ship/flatpak.js +1 -1
  34. package/lib/utils/ship/formats.d.ts +85 -7
  35. package/lib/utils/ship/formats.js +734 -15
  36. package/lib/utils/ship/launcher.d.ts +13 -3
  37. package/lib/utils/ship/launcher.js +362 -17
  38. package/lib/utils/ship/layout.d.ts +268 -0
  39. package/lib/utils/ship/layout.js +351 -0
  40. package/lib/utils/ship/localize-metadata.d.ts +22 -0
  41. package/lib/utils/ship/localize-metadata.js +126 -0
  42. package/lib/utils/ship/mime.js +1 -9
  43. package/lib/utils/ship/msi.d.ts +126 -0
  44. package/lib/utils/ship/msi.js +417 -0
  45. package/lib/utils/ship/node-runtime.d.ts +71 -0
  46. package/lib/utils/ship/node-runtime.js +122 -0
  47. package/lib/utils/ship/payload.d.ts +156 -5
  48. package/lib/utils/ship/payload.js +580 -14
  49. package/lib/utils/ship/plan.d.ts +21 -0
  50. package/lib/utils/ship/plan.js +20 -7
  51. package/lib/utils/ship/plist.d.ts +58 -0
  52. package/lib/utils/ship/plist.js +198 -0
  53. package/lib/utils/ship/rpm.js +9 -3
  54. package/lib/utils/ship/schemas.d.ts +61 -0
  55. package/lib/utils/ship/schemas.js +118 -0
  56. package/lib/utils/ship/scripts.js +7 -3
  57. package/lib/utils/ship/settings.d.ts +19 -1
  58. package/lib/utils/ship/settings.js +12 -1
  59. package/lib/utils/ship/share-dirs.d.ts +23 -0
  60. package/lib/utils/ship/share-dirs.js +46 -0
  61. package/lib/utils/ship/signing.d.ts +195 -0
  62. package/lib/utils/ship/signing.js +317 -0
  63. package/lib/utils/ship/stage-manifest.d.ts +40 -12
  64. package/lib/utils/ship/stage-manifest.js +99 -13
  65. package/lib/utils/ship/stage-writer.d.ts +48 -0
  66. package/lib/utils/ship/stage-writer.js +71 -1
  67. package/lib/utils/ship/types.d.ts +158 -13
  68. package/lib/utils/ship/xml.d.ts +7 -0
  69. package/lib/utils/ship/xml.js +25 -0
  70. package/lib/utils/ship/zip.d.ts +69 -0
  71. package/lib/utils/ship/zip.js +245 -0
  72. package/lib/utils/trust-registry.js +5 -0
  73. package/package.json +19 -19
@@ -52,7 +52,24 @@ function discoverTypelibs(projectDir, dirs) {
52
52
  'A missing directory here means the package would install without the typelib it promises.');
53
53
  }
54
54
  for (const file of listFilesRecursive(root)) {
55
- if (/\.typelib$|\.so(\.\d+)*$/.test(file))
55
+ // Every shared-library spelling, not only ELF's. A typelib is
56
+ // useless without its library on macOS and Windows too, and the
57
+ // `.so`-only test dropped `libgwebgl.dylib` and `gwebgl-0.dll` while
58
+ // staging the `.typelib` beside them — a package that installs and
59
+ // dies at the first import, with nothing in the output saying so.
60
+ // The layout axis (ADR 0024 § 2) is what put those two files in
61
+ // reach: they land in `Contents/Frameworks` and in the program
62
+ // directory's `lib\`.
63
+ // Case-insensitive for the extensions that come off case-preserving-
64
+ // but-INSENSITIVE filesystems — `LIBFOO.DLL` and `Foo.Dylib` are
65
+ // ordinary names on macOS and Windows, and a lowercase-only test drops
66
+ // them into exactly the silent failure the paragraph above describes.
67
+ //
68
+ // NOT for `.so`, and the asymmetry is the reason rather than an
69
+ // oversight: ELF sonames come off case-SENSITIVE filesystems where
70
+ // `libfoo.so` is the only spelling, and `/i` there additionally makes
71
+ // `README.SO` a shared library. One rationale, one scope.
72
+ if (/\.(typelib|dylib|dll)$/i.test(file) || /\.so(\.\d+)*$/.test(file))
56
73
  out.push(join(root, file));
57
74
  }
58
75
  }
@@ -0,0 +1,71 @@
1
+ import type { PackSettings } from './types.js';
2
+ /** The tool this packer execs. One spelling, read by this module AND by the format row. */
3
+ export declare const DMG_TOOL = "hdiutil";
4
+ /**
5
+ * What the mounted image is called in the Finder's sidebar.
6
+ *
7
+ * The DISPLAY name, like the `.app` inside it — the volume and the bundle are
8
+ * the two things a user reads during the one interaction this format has, and
9
+ * spelling them differently (`Ship Demo.app` inside `ship-demo-1.2.3`) makes the
10
+ * window look like somebody else's download. The FILE keeps the versioned,
11
+ * space-free name, because that one lands in a downloads folder beside others;
12
+ * `FormatDescriptor.fileName` is where that split lives, exactly as it does for
13
+ * the `.app` and its zip.
14
+ *
15
+ * EMPTY IS REFUSED, and it is not defensive: `resolveShipSettings` derives the
16
+ * name as `metadata.name ?? titleCase(binaryName)` and `??` passes `''` straight
17
+ * through — the same hole `windowsProgramDirName` documents. `hdiutil` answers an
18
+ * empty `-volname` by calling the volume `untitled`, at exit 0, so the image
19
+ * would build and mount under a name nothing in the project ever chose.
20
+ */
21
+ export declare function dmgVolumeName(settings: PackSettings): string;
22
+ export interface DmgCreateInput {
23
+ /** The volume label — see {@link dmgVolumeName}. */
24
+ volumeName: string;
25
+ /** The directory whose CONTENTS become the volume root; it holds `<App>.app` and nothing else. */
26
+ sourceDir: string;
27
+ /** Absolute path the finished `.dmg` is written to. */
28
+ target: string;
29
+ }
30
+ /**
31
+ * The exact `hdiutil` invocation, as data.
32
+ *
33
+ * Pure, so every flag is unit-testable from Linux — which is the only kind of
34
+ * host this project's contributors and its `e2e` job have. Same split as
35
+ * `renderShipFlatpakManifest`: what the tool is TOLD is checkable everywhere,
36
+ * what the tool DOES is checkable on the one OS that has it, and the two are not
37
+ * the same test.
38
+ *
39
+ * `-srcfolder` and not `-srcdevice`/`-size`: the folder's contents become the
40
+ * volume root, so `hdiutil` sizes the image itself. Passing a `-size` instead is
41
+ * how a `.dmg` build starts failing on the day the payload grows past a number
42
+ * somebody typed once.
43
+ */
44
+ export declare function hdiutilCreateArgs(input: DmgCreateInput): string[];
45
+ export interface DmgPackInput extends DmgCreateInput {
46
+ verbose: boolean;
47
+ /** Working root, so the log can name the directory that became the volume. */
48
+ workDir: string;
49
+ }
50
+ /**
51
+ * Build the image at `input.target`.
52
+ *
53
+ * Unlike the Flatpak packer's tool, this one cannot be installed where it is
54
+ * missing: `hdiutil` ships with macOS and exists nowhere else. So the `notFound` branch does not say "install
55
+ * it"; it says which host this belongs on and how the payload gets there, which
56
+ * is the same two-phase sentence `assertHostCanFinish` prints one step earlier.
57
+ * Reaching this branch at all means `process.platform` said darwin and `hdiutil`
58
+ * was still absent, so the honest advice is "this macOS installation is broken",
59
+ * not a package name.
60
+ */
61
+ export declare function buildDmgImage(input: DmgPackInput): Promise<void>;
62
+ /**
63
+ * Where the volume is assembled before `hdiutil` turns it into an image.
64
+ *
65
+ * A directory of its own under the run's output root, never `out/` itself: the
66
+ * volume root holds the `.app` and NOTHING ELSE, because `-srcfolder` copies
67
+ * whatever it finds — a stray sibling would ride into the image, and the reader
68
+ * that would catch it is the same listing comparison that would then have to be
69
+ * relaxed to allow it.
70
+ */
71
+ export declare function dmgVolumeDir(outRoot: string): string;
@@ -0,0 +1,181 @@
1
+ // The `.dmg` packer — ADR 0024 § A1/§ A6, issue #1354 M4.
2
+ //
3
+ // THE FIRST FORMAT THAT IS HOST-BOUND IN THE `HostRequirement` SENSE, and the
4
+ // distinction ADR 0024 § A3 drew between its two refusals is what this row is
5
+ // the first subject of. A Flatpak is `finishOn: ['linux']` because flatpak runs
6
+ // on Linux — the format is bound the way the application is. A `.dmg` is bound
7
+ // by the CONTAINER: it is a UDIF image over a real HFS+/APFS volume, no
8
+ // HFS+/APFS writer exists anywhere in this tree, and `hdiutil` is macOS-only.
9
+ // So `assertHostCanFinish` refuses it off darwin and `assertToolsInstalled`
10
+ // names `hdiutil` on a darwin host that somehow lacks it — two failures with
11
+ // two different fixes, which is exactly why they are two functions.
12
+ //
13
+ // A HAND-WRITTEN UDIF WRITER STAYS REJECTED (§ A6), in that section's own
14
+ // words: 1200-2000 lines against a whole existing packer surface of ~1243, and
15
+ // every mistake silent, because the Finder mounts the image and shows an empty
16
+ // window. What we would buy is an independent reader, and 7-Zip already
17
+ // advertises `Dmg`, `HFS` and `APFS` handlers regardless of who wrote the file.
18
+ //
19
+ // WHICH MAKES THE ORACLE THE INTERESTING PART, because `hdiutil verify` is
20
+ // `hdiutil` reading what `hdiutil` wrote. The reader is a chain that runs on
21
+ // LINUX — `7z l` (7-Zip 23.01 on ubuntu-24.04 carries `Dmg`, `HFS` and `APFS`),
22
+ // `7z t` to inflate what it stores, then `dmg2img` to a raw volume and
23
+ // `fsck.hfsplus -f -n` over that — driven by `.github/ship-oracle/verify-dmg.py`
24
+ // and compared against the stage manifest by name, size and MODE. Three
25
+ // implementations, none of them Apple's and none of them ours.
26
+ //
27
+ // TWO hdiutil FLAGS ARE DECISIONS AND NOT DEFAULTS:
28
+ //
29
+ // * `-fs HFS+J`. APFS is what a modern `hdiutil` reaches for when nothing says
30
+ // otherwise, and the whole Linux reader chain above is an HFS+ chain —
31
+ // `dmg2img` writes a raw volume and `fsck.hfsplus` is an HFS+ fsck. An APFS
32
+ // image would leave 7-Zip's handler as the only reader, i.e. one reader for
33
+ // the negative control to fool. Journaling costs a read-only image nothing
34
+ // and `fsck.hfsplus` reads it: measured on ubuntu-24.04 / hfsprogs
35
+ // 540.1.linux3-5build3 against a `mkfs.hfsplus -J` volume, "** Checking
36
+ // Journaled HFS Plus volume … appears to be OK", exit 0.
37
+ // * `-format UDZO`, i.e. zlib. The alternatives are ULFO (LZFSE) and ULMO
38
+ // (LZMA), and the reader that would have to decode them is the OLD one:
39
+ // ubuntu-24.04 ships 7-Zip 23.01, not this workstation's 26.02. zlib is the
40
+ // method its `Dmg` handler has read since long before either.
41
+ //
42
+ // WHAT THIS PACKER DOES NOT PUT IN THE VOLUME: an `/Applications` symlink, the
43
+ // drag-here convention. `writePayload` writes regular files and nothing else —
44
+ // which is a property `tests/e2e/ship` and the `ship-stage` job both assert, and
45
+ // a symlink is also what `actions/upload-artifact` silently dereferences. The
46
+ // artifact is still draggable out of the mounted volume; what it lacks is the
47
+ // arrow. Unblocker: a staged-symlink concept with a reader that can see one, at
48
+ // which point the link and its oracle land together.
49
+ import { join } from 'node:path';
50
+ import { describeExit, spawnToCompletion } from '../spawn.js';
51
+ /** The tool this packer execs. One spelling, read by this module AND by the format row. */
52
+ export const DMG_TOOL = 'hdiutil';
53
+ /**
54
+ * Characters an HFS+ volume name may not contain.
55
+ *
56
+ * Two of the three `layout.ts` refuses in a bundle DIRECTORY name — its
57
+ * `BUNDLE_NAME_FORBIDDEN` is `/[/:\\]/` — and for the same measured reason: `/`
58
+ * is the POSIX separator and `:` is HFS+'s, and the Finder shows a stored `:` as
59
+ * a `/` and vice versa. A volume called `Ship/Demo` mounts as `Ship:Demo`, so the
60
+ * name a user is told to look for is not the name they see. `\` is the one that
61
+ * is NOT here: it is an ordinary character in a volume name, while a bundle
62
+ * directory refuses it too and `windowsProgramDirName` refuses it for Windows'
63
+ * own reasons.
64
+ */
65
+ const VOLUME_NAME_FORBIDDEN = /[/:]/;
66
+ /**
67
+ * What the mounted image is called in the Finder's sidebar.
68
+ *
69
+ * The DISPLAY name, like the `.app` inside it — the volume and the bundle are
70
+ * the two things a user reads during the one interaction this format has, and
71
+ * spelling them differently (`Ship Demo.app` inside `ship-demo-1.2.3`) makes the
72
+ * window look like somebody else's download. The FILE keeps the versioned,
73
+ * space-free name, because that one lands in a downloads folder beside others;
74
+ * `FormatDescriptor.fileName` is where that split lives, exactly as it does for
75
+ * the `.app` and its zip.
76
+ *
77
+ * EMPTY IS REFUSED, and it is not defensive: `resolveShipSettings` derives the
78
+ * name as `metadata.name ?? titleCase(binaryName)` and `??` passes `''` straight
79
+ * through — the same hole `windowsProgramDirName` documents. `hdiutil` answers an
80
+ * empty `-volname` by calling the volume `untitled`, at exit 0, so the image
81
+ * would build and mount under a name nothing in the project ever chose.
82
+ */
83
+ export function dmgVolumeName(settings) {
84
+ const name = settings.name;
85
+ if (name.trim() === '') {
86
+ throw new Error('gjsify ship: the macOS image has no name to call its volume — `gjsify.ship.name` (or the ' +
87
+ 'package name it is derived from) is empty. `hdiutil` would accept that and mount the ' +
88
+ 'image as `untitled`, which is the name every nameless disk image on the machine has.');
89
+ }
90
+ if (VOLUME_NAME_FORBIDDEN.test(name)) {
91
+ throw new Error(`gjsify ship: the macOS image would mount a volume called "${name}", and HFS+ cannot hold it: ` +
92
+ 'the Finder shows a stored ":" as a "/" and a stored "/" as a ":", so the volume would not ' +
93
+ 'be the one this project named. Set `gjsify.ship.name` to the display name you want.');
94
+ }
95
+ return name;
96
+ }
97
+ /**
98
+ * The exact `hdiutil` invocation, as data.
99
+ *
100
+ * Pure, so every flag is unit-testable from Linux — which is the only kind of
101
+ * host this project's contributors and its `e2e` job have. Same split as
102
+ * `renderShipFlatpakManifest`: what the tool is TOLD is checkable everywhere,
103
+ * what the tool DOES is checkable on the one OS that has it, and the two are not
104
+ * the same test.
105
+ *
106
+ * `-srcfolder` and not `-srcdevice`/`-size`: the folder's contents become the
107
+ * volume root, so `hdiutil` sizes the image itself. Passing a `-size` instead is
108
+ * how a `.dmg` build starts failing on the day the payload grows past a number
109
+ * somebody typed once.
110
+ */
111
+ export function hdiutilCreateArgs(input) {
112
+ return [
113
+ 'create',
114
+ // Read-only + zlib. See the header: the alternatives are decodable only
115
+ // by a newer 7-Zip than the reader leg has.
116
+ '-format',
117
+ 'UDZO',
118
+ // HFS+ EXPLICITLY, because the default is the thing the reader chain
119
+ // cannot read. See the header.
120
+ '-fs',
121
+ 'HFS+J',
122
+ '-volname',
123
+ input.volumeName,
124
+ '-srcfolder',
125
+ input.sourceDir,
126
+ // hdiutil REFUSES an existing target rather than replacing it, and
127
+ // `packOne` writes into `out/` which a previous run may have filled. The
128
+ // failure without this is a pack that dies on its second invocation only.
129
+ '-ov',
130
+ // Progress on a TTY is a spinner; in a CI log it is thousands of lines.
131
+ '-quiet',
132
+ input.target,
133
+ ];
134
+ }
135
+ /**
136
+ * Build the image at `input.target`.
137
+ *
138
+ * Unlike the Flatpak packer's tool, this one cannot be installed where it is
139
+ * missing: `hdiutil` ships with macOS and exists nowhere else. So the `notFound` branch does not say "install
140
+ * it"; it says which host this belongs on and how the payload gets there, which
141
+ * is the same two-phase sentence `assertHostCanFinish` prints one step earlier.
142
+ * Reaching this branch at all means `process.platform` said darwin and `hdiutil`
143
+ * was still absent, so the honest advice is "this macOS installation is broken",
144
+ * not a package name.
145
+ */
146
+ export async function buildDmgImage(input) {
147
+ const args = hdiutilCreateArgs(input);
148
+ if (input.verbose)
149
+ console.log(`[gjsify ship] ${DMG_TOOL} ${args.join(' ')}`);
150
+ // `completion: 'return'`, matching the Flatpak packer: `ship` packs the
151
+ // remaining formats and prints the artifact list afterwards, so it cannot end
152
+ // in `process.exit()`. Under GJS that selects the blocking path, which
153
+ // captures the child's output and re-emits it — the documented cost of a
154
+ // spawn a caller has to return from (`utils/spawn.ts`), and why `--verbose`
155
+ // prints the invocation before the silence starts.
156
+ const result = await spawnToCompletion(DMG_TOOL, args, {
157
+ completion: 'return',
158
+ cwd: input.workDir,
159
+ stdio: 'inherit',
160
+ notFound: () => new Error(`gjsify ship: ${DMG_TOOL} is not on PATH. It ships with macOS and cannot be installed ` +
161
+ 'anywhere else, so this is either a broken macOS installation or a host that is not ' +
162
+ 'macOS at all. A `.dmg` is packed on darwin (ADR 0024 § A1); from anywhere else the ' +
163
+ 'route is `gjsify ship darwin --stage` here and `gjsify ship --from-stage <dir> ' +
164
+ '--target macos-app-dmg` there.'),
165
+ });
166
+ if (result.code !== 0) {
167
+ throw new Error(`gjsify ship: ${DMG_TOOL} failed with ${describeExit(result)}.`);
168
+ }
169
+ }
170
+ /**
171
+ * Where the volume is assembled before `hdiutil` turns it into an image.
172
+ *
173
+ * A directory of its own under the run's output root, never `out/` itself: the
174
+ * volume root holds the `.app` and NOTHING ELSE, because `-srcfolder` copies
175
+ * whatever it finds — a stray sibling would ride into the image, and the reader
176
+ * that would catch it is the same listing comparison that would then have to be
177
+ * relaxed to allow it.
178
+ */
179
+ export function dmgVolumeDir(outRoot) {
180
+ return join(outRoot, 'dmg', 'volume');
181
+ }
@@ -143,6 +143,6 @@ async function run(cmd, args, input, notFoundHint) {
143
143
  notFound: () => new Error(`gjsify ship: ${notFoundHint}`),
144
144
  });
145
145
  if (result.code !== 0) {
146
- throw new Error(`gjsify ship: ${cmd} failed${describeExit(result)}.`);
146
+ throw new Error(`gjsify ship: ${cmd} failed with ${describeExit(result)}.`);
147
147
  }
148
148
  }
@@ -1,9 +1,40 @@
1
- import type { FormatDescriptor, FormatId } from './types.js';
1
+ import { type Layout } from './layout.js';
2
+ import type { FormatDescriptor, FormatId, HostOs, PackSettings, RequiredTools } from './types.js';
3
+ /**
4
+ * The directory `windows-dir` produces, and the top level `windows-dir-zip` puts
5
+ * its entries under.
6
+ *
7
+ * ONE function with TWO readers, deliberately: the zip has to expand to the same
8
+ * directory the other row writes, and two literals would be two names a user is
9
+ * asked to believe are one.
10
+ */
11
+ export declare function windowsProgramDirName(settings: PackSettings): string;
12
+ /**
13
+ * The tools ONE host needs for a format, whichever shape the row declares.
14
+ *
15
+ * The one place the union in {@link RequiredTools} is opened, so a caller cannot
16
+ * read the map and get `undefined.filter` or read the array on a row that has a
17
+ * map. An OS the map does not name answers `[]` — not because nothing is needed
18
+ * there, but because {@link assertHostCanFinish} has already refused that host by
19
+ * the time this is asked, and inventing a tool list for a host that cannot finish
20
+ * the format would answer a question nobody may ask.
21
+ */
22
+ export declare function requiredToolsOn(tools: RequiredTools, host: string): readonly string[];
23
+ /**
24
+ * Every tool any host could need for a format — the question `installHint`'s
25
+ * presence is gated on.
26
+ *
27
+ * Deliberately NOT what {@link assertToolsInstalled} checks. "Does this row need a
28
+ * hint at all" is a property of the ROW; "is this tool here" is a question about
29
+ * one machine, and answering the first with the second would let a row declare a
30
+ * Windows-only tool and no hint, because the gate happened to run on Linux.
31
+ */
32
+ export declare function allRequiredTools(tools: RequiredTools): readonly string[];
2
33
  export declare const FORMATS: Record<FormatId, FormatDescriptor>;
3
34
  export declare const FORMAT_IDS: FormatId[];
4
35
  /**
5
- * What `gjsify ship` builds when nothing said otherwise — every format that
6
- * needs no tool and no particular host.
36
+ * What `gjsify ship <os>` builds when nothing said otherwise — every format that
37
+ * wraps THAT OS's layout and needs no tool and no particular host.
7
38
  *
8
39
  * A SECOND derivation from the same table, and it has to be one: `FORMAT_IDS`
9
40
  * used to be both "every format that exists" and "every format to build by
@@ -14,8 +45,22 @@ export declare const FORMAT_IDS: FormatId[];
14
45
  * packs `@gjsify/cli` itself on a bare ubuntu runner. A host-bound format is
15
46
  * opt-in through `--target` or `gjsify.ship.targets`; `--help` still lists all
16
47
  * of them.
48
+ *
49
+ * `layoutOs` is the SECOND criterion, and it is what the layout axis needed: a
50
+ * `.app` and its zip and a Windows program directory are all `finishOn: 'any'`,
51
+ * so on the one criterion a bare `gjsify ship` on Linux would start emitting
52
+ * five artifacts (ADR 0024 issue #1354, open question 3). Answered with § A2's
53
+ * positional rather than instead of it — the positional picks the layout, this
54
+ * picks the formats that wrap it, and `gjsify ship` on Linux keeps emitting
55
+ * exactly `deb` and `rpm`.
56
+ *
57
+ * An EMPTY answer is legal and is what darwin and windows give today: their
58
+ * layouts assemble (`--stage`) and no format wraps them yet. `assemble` refuses
59
+ * a pack with nothing to pack rather than exiting 0 having produced no artifact.
17
60
  */
18
- export declare const DEFAULT_FORMAT_IDS: FormatId[];
61
+ export declare function defaultFormatIds(layoutOs: HostOs): FormatId[];
62
+ /** Every format that wraps one OS's layout, tool-bound ones included. */
63
+ export declare function formatIdsFor(layoutOs: HostOs): FormatId[];
19
64
  /**
20
65
  * Refuse a format this host cannot finish, BEFORE anything is built or written.
21
66
  *
@@ -35,6 +80,39 @@ export declare function assertHostCanFinish(format: FormatDescriptor, host?: str
35
80
  * a build: `gjsify ship` runs the project's `build` script first, and finding
36
81
  * out afterwards that `flatpak-builder` is absent costs the whole build.
37
82
  */
38
- export declare function assertToolsInstalled(format: FormatDescriptor, present?: (cmd: string) => boolean): void;
39
- /** Parse `--target deb,rpm` into a sorted, deduplicated descriptor list. */
40
- export declare function resolveFormats(raw: readonly string[]): FormatDescriptor[];
83
+ export declare function assertToolsInstalled(format: FormatDescriptor, present?: (cmd: string) => boolean, host?: string): void;
84
+ /**
85
+ * Parse an explicit `--target deb,rpm` into a sorted, deduplicated descriptor list.
86
+ *
87
+ * A format belonging to another layout is an ERROR here, not a filter:
88
+ * `gjsify ship darwin --target deb` is a mistake worth naming, and dropping it
89
+ * silently would stage the darwin layout, pack nothing and exit 0 — the shape
90
+ * the empty-list refusal above already exists to prevent, arriving through a
91
+ * different door.
92
+ *
93
+ * The FLAG is what makes that the right answer, and it is the whole difference
94
+ * from {@link configuredFormats}: a value typed on this command line is a claim
95
+ * about THIS run.
96
+ */
97
+ export declare function resolveFormats(raw: readonly string[], layout: Layout): FormatDescriptor[];
98
+ /**
99
+ * The same names from `gjsify.ship.targets`, FILTERED to the current layout.
100
+ *
101
+ * A configured list is a project-level DEFAULT — "when I ship this, build these"
102
+ * — written once and read by every run, so it cannot be a claim about a layout
103
+ * the author had not heard of when they wrote it. Refusing it the way
104
+ * {@link resolveFormats} refuses a flag makes the new positional unusable in
105
+ * every project that has the key, and this repository is the proof: with
106
+ * `targets: ["deb", "rpm"]` in `packages/infra/cli/package.json`,
107
+ * `gjsify ship darwin --stage` exited 1 telling the author to run
108
+ * `gjsify ship darwin --stage`. There was no `--target` value that got a darwin
109
+ * stage out of such a project at all.
110
+ *
111
+ * Filtering is safe HERE and only here, because an empty result is not a silent
112
+ * success: `assemble` refuses a PACK with nothing to pack, and a `--stage` run
113
+ * legitimately wants exactly this — assemble the layout, wrap nothing.
114
+ */
115
+ export declare function configuredFormats(raw: readonly string[], layout: Layout): {
116
+ formats: FormatDescriptor[];
117
+ dropped: FormatId[];
118
+ };