@gjsify/cli 0.43.0 → 0.44.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 +426 -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/ship.js +90 -28
- package/lib/config.js +25 -0
- package/lib/types/cli-build-options.d.ts +11 -0
- package/lib/types/config-data.d.ts +74 -2
- package/lib/utils/flatpak-runtime.d.ts +41 -0
- package/lib/utils/flatpak-runtime.js +54 -0
- package/lib/utils/ship/depends.d.ts +4 -7
- package/lib/utils/ship/depends.js +3 -3
- 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 +35 -0
- package/lib/utils/ship/formats.js +145 -6
- package/lib/utils/ship/settings.js +7 -0
- package/lib/utils/ship/stage-manifest.d.ts +11 -2
- package/lib/utils/ship/stage-manifest.js +39 -2
- package/lib/utils/ship/types.d.ts +116 -13
- package/package.json +19 -19
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
// The per-format table. Everything that differs between
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The per-format table. Everything that differs between the formats outside the
|
|
2
|
+
// archive container itself lives here as DATA, so a new format is a row rather
|
|
3
|
+
// than an edit across the stager, the overlay builder and the artifact namer.
|
|
4
|
+
//
|
|
5
|
+
// Flatpak is the row that proves the claim: ADR 0024 § 2 predicted "the whole
|
|
6
|
+
// difference between a Flatpak and an `.rpm` is a four-line prefix map", and
|
|
7
|
+
// this file is where that turned out to be one `prefix: '/app'`, one arch table
|
|
8
|
+
// and one filename. What it also proved is what the design did NOT predict —
|
|
9
|
+
// a format can be host-bound (§ A3), so `host` is a descriptor field now.
|
|
10
|
+
import { isOnPath } from '../check-system-deps.js';
|
|
5
11
|
// `process.arch` → the format's architecture name. Taken from dpkg's own
|
|
6
12
|
// `data/cputable` and rpm's arch table. `arm` cannot be told apart from
|
|
7
13
|
// `armel` by a running process, and Debian's hard-float port is what anything
|
|
@@ -32,16 +38,49 @@ function lookupArch(table, arch, label) {
|
|
|
32
38
|
}
|
|
33
39
|
return mapped;
|
|
34
40
|
}
|
|
41
|
+
// Flatpak's own vocabulary, which is ostree's: `flatpak --supported-arches` on
|
|
42
|
+
// x86_64 answers `x86_64` then `i386`, and a ref is `app/<id>/<arch>/<branch>`.
|
|
43
|
+
// Deliberately short — a name this table does not know is refused rather than
|
|
44
|
+
// guessed, because the arch is not a label here, it is part of the ref the
|
|
45
|
+
// bundle is exported under and installed from.
|
|
46
|
+
const FLATPAK_ARCH = {
|
|
47
|
+
x64: 'x86_64',
|
|
48
|
+
arm64: 'aarch64',
|
|
49
|
+
ia32: 'i386',
|
|
50
|
+
arm: 'arm',
|
|
51
|
+
};
|
|
35
52
|
function debArch(arch, archIndependent) {
|
|
36
53
|
return archIndependent ? 'all' : lookupArch(DEBIAN_ARCH, arch, 'Debian');
|
|
37
54
|
}
|
|
38
55
|
function rpmArch(arch, archIndependent) {
|
|
39
56
|
return archIndependent ? 'noarch' : lookupArch(RPM_ARCH, arch, 'RPM');
|
|
40
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Flatpak has no `noarch`, so `archIndependent` is ignored — and that is a
|
|
60
|
+
* measured difference, not an oversight.
|
|
61
|
+
*
|
|
62
|
+
* `all`/`noarch` exist because apt and dnf REFUSE a package whose architecture
|
|
63
|
+
* is not the machine's. Flatpak asks the opposite question: every ref names an
|
|
64
|
+
* arch, `flatpak install` resolves the one matching the host, and an app with no
|
|
65
|
+
* arch is not installable at all. So a payload of pure JavaScript still ships as
|
|
66
|
+
* `app/<id>/x86_64/stable` — which is also what Flathub does for every
|
|
67
|
+
* interpreted app it hosts.
|
|
68
|
+
*/
|
|
69
|
+
function flatpakArch(arch, _archIndependent) {
|
|
70
|
+
return lookupArch(FLATPAK_ARCH, arch, 'Flatpak');
|
|
71
|
+
}
|
|
72
|
+
/** `.deb` and `.rpm` are written by this tree, so they exec nothing and read back with GNU tools. */
|
|
73
|
+
const WRITTEN_HERE = (readWith) => ({
|
|
74
|
+
finishOn: 'any',
|
|
75
|
+
requiredTools: [],
|
|
76
|
+
oracle: { readWith, readOn: ['linux'], selfReading: false },
|
|
77
|
+
});
|
|
41
78
|
export const FORMATS = {
|
|
42
79
|
deb: {
|
|
43
80
|
id: 'deb',
|
|
44
81
|
prefix: '/usr',
|
|
82
|
+
host: WRITTEN_HERE(['ar', 'tar', 'dpkg-deb', 'lintian']),
|
|
83
|
+
depends: 'deb',
|
|
45
84
|
// Debian policy § 12.5: every package ships its copyright in
|
|
46
85
|
// /usr/share/doc/<package>/copyright, and lintian errors without it.
|
|
47
86
|
licenseDest: (binaryName) => `share/doc/${binaryName}/copyright`,
|
|
@@ -52,21 +91,121 @@ export const FORMATS = {
|
|
|
52
91
|
rpm: {
|
|
53
92
|
id: 'rpm',
|
|
54
93
|
prefix: '/usr',
|
|
94
|
+
host: WRITTEN_HERE(['rpm', 'rpm2cpio', 'cpio']),
|
|
95
|
+
depends: 'rpm',
|
|
55
96
|
licenseDest: (binaryName) => `share/licenses/${binaryName}/LICENSE`,
|
|
56
97
|
licenseKind: 'plain',
|
|
57
98
|
archName: rpmArch,
|
|
58
99
|
fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.${archLabel}.rpm`,
|
|
59
100
|
},
|
|
101
|
+
flatpak: {
|
|
102
|
+
id: 'flatpak',
|
|
103
|
+
// The one-line difference ADR 0024 § 2 promised. Nothing in the payload
|
|
104
|
+
// changes: the launcher derives its prefix at runtime (§ 3), so the same
|
|
105
|
+
// staged `bin/<name>` works under /usr and under /app.
|
|
106
|
+
prefix: '/app',
|
|
107
|
+
host: {
|
|
108
|
+
// A single-file bundle is an OSTree static delta, and the only writer
|
|
109
|
+
// of one is `flatpak build-bundle`. Unlike the `.dmg` (§ A6) that is
|
|
110
|
+
// not an OS restriction: flatpak runs on Linux, so the format is
|
|
111
|
+
// Linux-bound the way the app itself is.
|
|
112
|
+
finishOn: ['linux'],
|
|
113
|
+
requiredTools: ['flatpak-builder', 'flatpak'],
|
|
114
|
+
installHint: 'Fedora: `sudo dnf install flatpak flatpak-builder`, Debian/Ubuntu: ' +
|
|
115
|
+
'`sudo apt install flatpak flatpak-builder`',
|
|
116
|
+
oracle: {
|
|
117
|
+
// `flatpak build-import-bundle` into a FRESH repo, then
|
|
118
|
+
// `ostree ls -R`: ostree parses the delta this tree never wrote,
|
|
119
|
+
// and prints a path + mode + size per file. Measured on this
|
|
120
|
+
// workstation — that listing is what the e2e suite compares
|
|
121
|
+
// against the staged payload.
|
|
122
|
+
readWith: ['flatpak', 'ostree'],
|
|
123
|
+
readOn: ['linux'],
|
|
124
|
+
selfReading: false,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
// A Flatpak's one dependency is its runtime, named in the manifest. See
|
|
128
|
+
// `DistroFormatId`: there is no `Depends:` field here to under-declare,
|
|
129
|
+
// which also means the unmapped-namespace refusal ADR 0024 § 6 built for
|
|
130
|
+
// deb and rpm has nothing to say about this format.
|
|
131
|
+
depends: null,
|
|
132
|
+
// No policy demands a location, so this follows rpm's — one fewer shape
|
|
133
|
+
// for a reader to learn, and `/app/share/licenses/<name>/LICENSE` is
|
|
134
|
+
// where the equivalent file sits in the `.rpm` built from the same stage.
|
|
135
|
+
licenseDest: (binaryName) => `share/licenses/${binaryName}/LICENSE`,
|
|
136
|
+
licenseKind: 'plain',
|
|
137
|
+
archName: flatpakArch,
|
|
138
|
+
// Named after the APP ID, not the binary: the id is the ref the file
|
|
139
|
+
// installs as, and it is what `flatpak install ./file.flatpak` prints.
|
|
140
|
+
fileName: (s, archLabel) => `${s.appId}-${s.version}-${s.release}.${archLabel}.flatpak`,
|
|
141
|
+
},
|
|
60
142
|
};
|
|
61
143
|
// DERIVED, never a second list. `FORMATS` is `Record<FormatId, …>`, so the
|
|
62
144
|
// compiler already refuses a `FormatId` with no descriptor — reading the keys
|
|
63
145
|
// back inherits that guarantee for free. Written out by hand this was the one
|
|
64
146
|
// unbound copy of the vocabulary: adding a format to `FormatId` and `FORMATS`
|
|
65
147
|
// compiled fine and left this list short, which would have made the new format
|
|
66
|
-
//
|
|
67
|
-
// missing from `--help`, and REFUSED by `readStage` as an unknown id.
|
|
148
|
+
// missing from `--help` and REFUSED by `readStage` as an unknown id.
|
|
68
149
|
// Insertion order is stable for string keys, and `resolveFormats` sorts anyway.
|
|
69
150
|
export const FORMAT_IDS = Object.keys(FORMATS);
|
|
151
|
+
/**
|
|
152
|
+
* What `gjsify ship` builds when nothing said otherwise — every format that
|
|
153
|
+
* needs no tool and no particular host.
|
|
154
|
+
*
|
|
155
|
+
* A SECOND derivation from the same table, and it has to be one: `FORMAT_IDS`
|
|
156
|
+
* used to be both "every format that exists" and "every format to build by
|
|
157
|
+
* default", which was the same list only while every format was `finishOn:
|
|
158
|
+
* 'any'`. Adding Flatpak to `FORMAT_IDS` alone would have made a bare
|
|
159
|
+
* `gjsify ship` demand `flatpak-builder` of every project that ever packaged a
|
|
160
|
+
* `.deb` — and on a host without it, of every `release-cut.yml` run, which
|
|
161
|
+
* packs `@gjsify/cli` itself on a bare ubuntu runner. A host-bound format is
|
|
162
|
+
* opt-in through `--target` or `gjsify.ship.targets`; `--help` still lists all
|
|
163
|
+
* of them.
|
|
164
|
+
*/
|
|
165
|
+
export const DEFAULT_FORMAT_IDS = FORMAT_IDS.filter((id) => FORMATS[id].host.finishOn === 'any');
|
|
166
|
+
/**
|
|
167
|
+
* Refuse a format this host cannot finish, BEFORE anything is built or written.
|
|
168
|
+
*
|
|
169
|
+
* `host` is injected so both branches are unit-testable from any machine — the
|
|
170
|
+
* same reason `resolvePrebuildDirName` and `checkTypeSkew`'s readers are pure.
|
|
171
|
+
* The message names the two-phase split rather than just saying no: the answer
|
|
172
|
+
* to "I am on macOS and want a Flatpak" is `--stage` here and `--from-stage` on
|
|
173
|
+
* a Linux runner, and a refusal that does not say so reads as "unsupported".
|
|
174
|
+
*/
|
|
175
|
+
export function assertHostCanFinish(format, host = process.platform) {
|
|
176
|
+
const { finishOn } = format.host;
|
|
177
|
+
if (finishOn === 'any' || finishOn.includes(host))
|
|
178
|
+
return;
|
|
179
|
+
throw new Error(`gjsify ship: a ${format.id} artifact is packed on ${finishOn.join(' or ')} and ` +
|
|
180
|
+
`this host is ${host} (ADR 0024 § A1: a container is produced where its format's tool lives). ` +
|
|
181
|
+
'Assembly is not host-bound, so the way across is the two-phase split: run ' +
|
|
182
|
+
'`gjsify ship --stage` here, move `ship/stage/` to a ' +
|
|
183
|
+
`${finishOn.join('/')} runner, and finish with ` +
|
|
184
|
+
`\`gjsify ship --from-stage <dir> --target ${format.id}\`.`);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Refuse a format whose tools are not installed, with the tool named.
|
|
188
|
+
*
|
|
189
|
+
* Separate from {@link assertHostCanFinish} because the two failures have
|
|
190
|
+
* different fixes — one needs a different machine, the other needs a package —
|
|
191
|
+
* and separate from the packer's own `notFound` handler because it fires BEFORE
|
|
192
|
+
* a build: `gjsify ship` runs the project's `build` script first, and finding
|
|
193
|
+
* out afterwards that `flatpak-builder` is absent costs the whole build.
|
|
194
|
+
*/
|
|
195
|
+
export function assertToolsInstalled(format, present = isOnPath) {
|
|
196
|
+
const missing = format.host.requiredTools.filter((tool) => !present(tool));
|
|
197
|
+
if (missing.length === 0)
|
|
198
|
+
return;
|
|
199
|
+
// The install instruction is the DESCRIPTOR's, not this function's: hardcoded
|
|
200
|
+
// here it was `dnf install flatpak flatpak-builder` for every format that
|
|
201
|
+
// will ever need a tool, which is the branch this table exists to avoid.
|
|
202
|
+
const hint = format.host.installHint;
|
|
203
|
+
throw new Error(`gjsify ship: packing a ${format.id} needs ${format.host.requiredTools.join(' and ')}, and ` +
|
|
204
|
+
`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} not on PATH. ` +
|
|
205
|
+
`${hint === undefined ? 'Install it' : `Install it (${hint})`}, or drop this target — the other ` +
|
|
206
|
+
`formats need no tools at all. \`gjsify ship --stage\` also works without ${missing.join('/')}: ` +
|
|
207
|
+
'the payload is assembled by this CLI and only the container needs them.');
|
|
208
|
+
}
|
|
70
209
|
/** Parse `--target deb,rpm` into a sorted, deduplicated descriptor list. */
|
|
71
210
|
export function resolveFormats(raw) {
|
|
72
211
|
const names = raw
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { validateMimeTypes } from './mime.js';
|
|
13
13
|
import { basename } from 'node:path';
|
|
14
14
|
import { DEFAULT_GJS_FLOOR } from './depends.js';
|
|
15
|
+
import { resolveShipFlatpakSettings } from './flatpak-config.js';
|
|
15
16
|
import { assertRelease, normaliseVersion } from './version.js';
|
|
16
17
|
/** Freedesktop main category → deb `Section:` / rpm `Group:`. First match wins. */
|
|
17
18
|
const CATEGORY_SECTIONS = [
|
|
@@ -71,6 +72,11 @@ export function resolveShipSettings(input) {
|
|
|
71
72
|
warnings.push(`gjsify ship: no icon found — the installed app will show a placeholder in menus and app stores. ` +
|
|
72
73
|
'Point `gjsify.ship.icon` at an SVG (or a directory of sized PNGs).');
|
|
73
74
|
}
|
|
75
|
+
// Resolved here rather than in the packer, because the packer may run on a
|
|
76
|
+
// host with no `package.json` at all (ADR 0024 § A2) — and because the
|
|
77
|
+
// finish-args default depends on `kind`, which is a project fact.
|
|
78
|
+
const flatpakSettings = resolveShipFlatpakSettings({ ship, flatpak, kind });
|
|
79
|
+
warnings.push(...flatpakSettings.warnings);
|
|
74
80
|
const settings = {
|
|
75
81
|
projectDir: input.projectDir,
|
|
76
82
|
appId,
|
|
@@ -101,6 +107,7 @@ export function resolveShipSettings(input) {
|
|
|
101
107
|
outDir: input.cli.outDir ?? ship.outDir ?? 'ship',
|
|
102
108
|
arch: input.cli.arch ?? process.arch,
|
|
103
109
|
minGjsVersion: ship.minGjsVersion ?? DEFAULT_GJS_FLOOR,
|
|
110
|
+
flatpak: flatpakSettings.settings,
|
|
104
111
|
};
|
|
105
112
|
return { settings, metadata, warnings };
|
|
106
113
|
}
|
|
@@ -10,8 +10,14 @@ export declare const STAGE_MANIFEST_FILE = ".gjsify-ship-stage.json";
|
|
|
10
10
|
* Bump it whenever a field's MEANING changes, not when one is added that an
|
|
11
11
|
* older reader would simply ignore; a reader that ignores a field it does not
|
|
12
12
|
* know is exactly how a mode plan silently loses an entry.
|
|
13
|
+
*
|
|
14
|
+
* 2 added `settings.appId` and `settings.flatpak` for the Flatpak target. That
|
|
15
|
+
* is the second case and not the first: an older reader ignoring
|
|
16
|
+
* `settings.flatpak` would not skip a field, it would pack a Flatpak against
|
|
17
|
+
* whatever runtime IT defaults to — a different `org.gnome.Platform` version
|
|
18
|
+
* than the project asked for, at exit 0.
|
|
13
19
|
*/
|
|
14
|
-
export declare const STAGE_SCHEMA_VERSION =
|
|
20
|
+
export declare const STAGE_SCHEMA_VERSION = 2;
|
|
15
21
|
/**
|
|
16
22
|
* The OS whose layout `planStage` produces.
|
|
17
23
|
*
|
|
@@ -72,7 +78,10 @@ export interface StageManifestInput {
|
|
|
72
78
|
* `extraFiles` and `licenseFile` — seven absolute paths from the assembling
|
|
73
79
|
* host — into a file whose whole purpose is to be read somewhere else. The
|
|
74
80
|
* explicit list is also what makes adding a field a decision: see the comment
|
|
75
|
-
* on {@link PackSettings} for what belongs on each side.
|
|
81
|
+
* on {@link PackSettings} for what belongs on each side. `appId` and `flatpak`
|
|
82
|
+
* were that decision for the Flatpak target, and the `--from-stage` e2e is
|
|
83
|
+
* where an omission would have shown: it deletes the project, so a field the
|
|
84
|
+
* packer needs and the manifest lacks has nowhere to be read from.
|
|
76
85
|
*/
|
|
77
86
|
export declare function toPackSettings(settings: ShipSettings): PackSettings;
|
|
78
87
|
/** Absolute path of a stage's manifest. */
|
|
@@ -49,8 +49,14 @@ export const STAGE_MANIFEST_FILE = '.gjsify-ship-stage.json';
|
|
|
49
49
|
* Bump it whenever a field's MEANING changes, not when one is added that an
|
|
50
50
|
* older reader would simply ignore; a reader that ignores a field it does not
|
|
51
51
|
* know is exactly how a mode plan silently loses an entry.
|
|
52
|
+
*
|
|
53
|
+
* 2 added `settings.appId` and `settings.flatpak` for the Flatpak target. That
|
|
54
|
+
* is the second case and not the first: an older reader ignoring
|
|
55
|
+
* `settings.flatpak` would not skip a field, it would pack a Flatpak against
|
|
56
|
+
* whatever runtime IT defaults to — a different `org.gnome.Platform` version
|
|
57
|
+
* than the project asked for, at exit 0.
|
|
52
58
|
*/
|
|
53
|
-
export const STAGE_SCHEMA_VERSION =
|
|
59
|
+
export const STAGE_SCHEMA_VERSION = 2;
|
|
54
60
|
/**
|
|
55
61
|
* The OS whose layout `planStage` produces.
|
|
56
62
|
*
|
|
@@ -69,11 +75,15 @@ export const STAGE_LAYOUT_OS = 'linux';
|
|
|
69
75
|
* `extraFiles` and `licenseFile` — seven absolute paths from the assembling
|
|
70
76
|
* host — into a file whose whole purpose is to be read somewhere else. The
|
|
71
77
|
* explicit list is also what makes adding a field a decision: see the comment
|
|
72
|
-
* on {@link PackSettings} for what belongs on each side.
|
|
78
|
+
* on {@link PackSettings} for what belongs on each side. `appId` and `flatpak`
|
|
79
|
+
* were that decision for the Flatpak target, and the `--from-stage` e2e is
|
|
80
|
+
* where an omission would have shown: it deletes the project, so a field the
|
|
81
|
+
* packer needs and the manifest lacks has nowhere to be read from.
|
|
73
82
|
*/
|
|
74
83
|
export function toPackSettings(settings) {
|
|
75
84
|
return {
|
|
76
85
|
binaryName: settings.binaryName,
|
|
86
|
+
appId: settings.appId,
|
|
77
87
|
version: settings.version,
|
|
78
88
|
release: settings.release,
|
|
79
89
|
maintainer: settings.maintainer,
|
|
@@ -86,6 +96,7 @@ export function toPackSettings(settings) {
|
|
|
86
96
|
extraDepends: settings.extraDepends,
|
|
87
97
|
typelibPackages: settings.typelibPackages,
|
|
88
98
|
minGjsVersion: settings.minGjsVersion,
|
|
99
|
+
flatpak: settings.flatpak,
|
|
89
100
|
};
|
|
90
101
|
}
|
|
91
102
|
/** Absolute path of a stage's manifest. */
|
|
@@ -355,6 +366,7 @@ function readPackSettings(data, at) {
|
|
|
355
366
|
const typelibPackages = record(data.typelibPackages, field('typelibPackages'));
|
|
356
367
|
return {
|
|
357
368
|
binaryName: expectString(data.binaryName, field('binaryName')),
|
|
369
|
+
appId: expectString(data.appId, field('appId')),
|
|
358
370
|
version: expectString(data.version, field('version')),
|
|
359
371
|
release: expectString(data.release, field('release')),
|
|
360
372
|
maintainer: expectString(data.maintainer, field('maintainer')),
|
|
@@ -379,6 +391,31 @@ function readPackSettings(data, at) {
|
|
|
379
391
|
];
|
|
380
392
|
})),
|
|
381
393
|
minGjsVersion: expectString(data.minGjsVersion, field('minGjsVersion')),
|
|
394
|
+
flatpak: readFlatpakSettings(record(data.flatpak, field('flatpak')), field('flatpak')),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* The Flatpak half, validated the same way as everything else here.
|
|
399
|
+
*
|
|
400
|
+
* Every field is required and none is defaulted, deliberately: a `?? []` on
|
|
401
|
+
* `finishArgs` would turn a mutilated manifest into an app with no display
|
|
402
|
+
* socket — it installs, it starts, and it draws nothing. The defaults belong on
|
|
403
|
+
* the assembling host, where `gjsify.ship` and `kind` are in reach; by the time
|
|
404
|
+
* a manifest exists the answer has already been decided and writing it down
|
|
405
|
+
* twice is how the two hosts disagree.
|
|
406
|
+
*/
|
|
407
|
+
function readFlatpakSettings(data, at) {
|
|
408
|
+
const field = (name) => `${at}.${name}`;
|
|
409
|
+
const strings = (value, name) => expectArray(value, field(name)).map((entry, index) => expectString(entry, field(`${name}[${index}]`)));
|
|
410
|
+
return {
|
|
411
|
+
runtime: expectString(data.runtime, field('runtime')),
|
|
412
|
+
runtimeVersion: expectString(data.runtimeVersion, field('runtimeVersion')),
|
|
413
|
+
sdk: expectString(data.sdk, field('sdk')),
|
|
414
|
+
branch: expectString(data.branch, field('branch')),
|
|
415
|
+
sdkExtensions: strings(data.sdkExtensions, 'sdkExtensions'),
|
|
416
|
+
appendPath: strings(data.appendPath, 'appendPath'),
|
|
417
|
+
finishArgs: strings(data.finishArgs, 'finishArgs'),
|
|
418
|
+
cleanup: strings(data.cleanup, 'cleanup'),
|
|
382
419
|
};
|
|
383
420
|
}
|
|
384
421
|
function record(value, at) {
|
|
@@ -36,7 +36,71 @@ export interface ShipArtifact {
|
|
|
36
36
|
/** Size in bytes. */
|
|
37
37
|
size: number;
|
|
38
38
|
}
|
|
39
|
-
export type FormatId = 'deb' | 'rpm';
|
|
39
|
+
export type FormatId = 'deb' | 'rpm' | 'flatpak';
|
|
40
|
+
/**
|
|
41
|
+
* The formats whose ARTIFACT carries a distro dependency list.
|
|
42
|
+
*
|
|
43
|
+
* Not every `FormatId`, and this is where the difference is written down once
|
|
44
|
+
* instead of being rediscovered per call site. A `.deb` says
|
|
45
|
+
* `Depends: gir1.2-gtk-4.0`, an `.rpm` says `Requires: gtk4`, and both are
|
|
46
|
+
* package names in a distribution's namespace. A Flatpak has no such field: it
|
|
47
|
+
* declares ONE dependency, its runtime (`org.gnome.Platform//50`), in the
|
|
48
|
+
* manifest — and inside that runtime a typelib is either present or nothing on
|
|
49
|
+
* the system provides it, so there is no package to name and no gap a name
|
|
50
|
+
* could close.
|
|
51
|
+
*
|
|
52
|
+
* `Extract<…>` rather than a fresh literal union, so this cannot outlive
|
|
53
|
+
* `FormatId`: dropping a member there shrinks this and reds every table keyed on
|
|
54
|
+
* it. The other direction is covered by {@link FormatDescriptor.depends} being
|
|
55
|
+
* REQUIRED — a new format cannot be added without saying which side it is on.
|
|
56
|
+
*/
|
|
57
|
+
export type DistroFormatId = Extract<FormatId, 'deb' | 'rpm'>;
|
|
58
|
+
/** An operating system in the repo-wide `process.platform` spelling (root AGENTS.md § Runtime & platform model). */
|
|
59
|
+
export type HostOs = 'linux' | 'darwin' | 'win32';
|
|
60
|
+
/**
|
|
61
|
+
* Where a format can be FINISHED, and who can read the result back (ADR 0024 § A3).
|
|
62
|
+
*
|
|
63
|
+
* Three fields rather than one `hostOs`, because the three questions have
|
|
64
|
+
* different answers. `finishOn` is not the LAYOUT — a `<App>.app` tree is
|
|
65
|
+
* assembled anywhere while the `.dmg` around it needs macOS. `requiredTools` is
|
|
66
|
+
* not implied by `finishOn` — `.deb` and `.rpm` are written by this tree and
|
|
67
|
+
* exec nothing, which is the whole reason `rpm` could become an independent
|
|
68
|
+
* oracle and catch the dpkg-`$1` defect in the first artifact. And `oracle` is
|
|
69
|
+
* derivable from neither: a format built by the platform's own tool forfeits
|
|
70
|
+
* that independence unless a reader from a DIFFERENT implementation family
|
|
71
|
+
* exists, which is exactly what `selfReading` is the honest confession of.
|
|
72
|
+
*/
|
|
73
|
+
export interface HostRequirement {
|
|
74
|
+
/** OSes whose tooling can finish this format. `'any'` = pure JS, under GJS, offline. */
|
|
75
|
+
finishOn: 'any' | readonly HostOs[];
|
|
76
|
+
/** Commands the packer EXECS. Empty iff this tree writes the format itself. */
|
|
77
|
+
requiredTools: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* How to install {@link requiredTools}, in this format's own words.
|
|
80
|
+
*
|
|
81
|
+
* DATA, because the refusal that names it is one generic function. The
|
|
82
|
+
* first cut hardcoded `sudo dnf install flatpak flatpak-builder` inside
|
|
83
|
+
* `assertToolsInstalled`, which is correct for exactly one format and would
|
|
84
|
+
* have told the first `.dmg` or `.msi` user to install flatpak — from the
|
|
85
|
+
* same branchless dispatch this whole table exists to keep branchless.
|
|
86
|
+
* Required whenever `requiredTools` is non-empty; `flatpak.spec.ts` holds
|
|
87
|
+
* that, the same way it holds `selfReading`.
|
|
88
|
+
*/
|
|
89
|
+
installHint?: string;
|
|
90
|
+
oracle: {
|
|
91
|
+
/** Readers from a DIFFERENT implementation family than the packer. */
|
|
92
|
+
readWith: readonly string[];
|
|
93
|
+
/** Where each reader runs. A Linux-runnable reader is worth more — every CI leg has one. */
|
|
94
|
+
readOn: readonly HostOs[];
|
|
95
|
+
/**
|
|
96
|
+
* No independent discriminator yet. Legal to DECLARE while a format is
|
|
97
|
+
* being built; illegal to release — `flatpak.spec.ts` is what turns that
|
|
98
|
+
* sentence into a red test, so flipping it is a decision somebody makes
|
|
99
|
+
* rather than a field nobody reads.
|
|
100
|
+
*/
|
|
101
|
+
selfReading: boolean;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
40
104
|
/**
|
|
41
105
|
* Everything that is per-FORMAT, as data.
|
|
42
106
|
*
|
|
@@ -48,6 +112,13 @@ export interface FormatDescriptor {
|
|
|
48
112
|
id: FormatId;
|
|
49
113
|
/** Install prefix the format's contents hang under. */
|
|
50
114
|
prefix: string;
|
|
115
|
+
/** Where this format can be packed, what it execs, and who reads it back. */
|
|
116
|
+
host: HostRequirement;
|
|
117
|
+
/**
|
|
118
|
+
* The key this format's dependency list is spelled under, or `null` when its
|
|
119
|
+
* dependencies are not a package list at all — see {@link DistroFormatId}.
|
|
120
|
+
*/
|
|
121
|
+
depends: DistroFormatId | null;
|
|
51
122
|
/** Where the project's licence text goes in this format's overlay. */
|
|
52
123
|
licenseDest: (binaryName: string) => string;
|
|
53
124
|
/**
|
|
@@ -109,8 +180,12 @@ export interface ShipMimeType {
|
|
|
109
180
|
* `share/locale/<rel>`, and no packer reads the field. So translations
|
|
110
181
|
* cross in the payload, where they belong, and the `abs` path does not
|
|
111
182
|
* cross at all. This list being explicit is what caught that.
|
|
112
|
-
* - `
|
|
113
|
-
* renderers, all of which have run by the time a stage exists.
|
|
183
|
+
* - `name`, `kind`, `execArgs` — read by the planner and the metadata
|
|
184
|
+
* renderers, all of which have run by the time a stage exists. `appId` was
|
|
185
|
+
* on this line and MOVED: the Flatpak packer needs it at pack time twice
|
|
186
|
+
* over — it is the manifest's `id` and the ref `flatpak build-bundle`
|
|
187
|
+
* exports — and neither is derivable from the payload, because the desktop
|
|
188
|
+
* entry that carries the id is only staged for `kind: 'app'`.
|
|
114
189
|
* - `arch` — carried by the manifest's `target`, which is also the value a
|
|
115
190
|
* mismatched stage is refused on, so it lives in exactly one place.
|
|
116
191
|
* - `mimeTypes` — phase 1 renders the shared-mime-info document into the
|
|
@@ -121,6 +196,8 @@ export interface ShipMimeType {
|
|
|
121
196
|
export interface PackSettings {
|
|
122
197
|
/** Package name: the `bin/` entry, the deb `Package:`, the rpm `Name:`. */
|
|
123
198
|
binaryName: string;
|
|
199
|
+
/** Reverse-DNS application id — the Flatpak manifest's `id` and the exported ref. */
|
|
200
|
+
appId: string;
|
|
124
201
|
/** Upstream version, normalised for package managers. */
|
|
125
202
|
version: string;
|
|
126
203
|
/** Package revision within one upstream version (deb revision / rpm release). */
|
|
@@ -139,24 +216,50 @@ export interface PackSettings {
|
|
|
139
216
|
/** rpm `Group:`. */
|
|
140
217
|
group: string;
|
|
141
218
|
/** Extra runtime dependencies per format, appended to the derived set. */
|
|
142
|
-
extraDepends:
|
|
143
|
-
deb: string[];
|
|
144
|
-
rpm: string[];
|
|
145
|
-
};
|
|
219
|
+
extraDepends: Record<DistroFormatId, string[]>;
|
|
146
220
|
/** Project-supplied GI-namespace → package rows, filling gaps in the built-in table. */
|
|
147
|
-
typelibPackages: Record<string,
|
|
148
|
-
deb: string;
|
|
149
|
-
rpm: string;
|
|
150
|
-
}>;
|
|
221
|
+
typelibPackages: Record<string, Record<DistroFormatId, string>>;
|
|
151
222
|
/** Minimum GJS the emitted dependency asks for. */
|
|
152
223
|
minGjsVersion: string;
|
|
224
|
+
/** The Flatpak manifest's non-payload half, fully defaulted at stage time. */
|
|
225
|
+
flatpak: ShipFlatpakSettings;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* What the generated Flatpak manifest says about everything that is not the payload.
|
|
229
|
+
*
|
|
230
|
+
* Resolved — defaults applied — while the project is still in reach, because
|
|
231
|
+
* the defaults depend on `kind` and on `gjsify.flatpak`/`gjsify.ship.flatpak`,
|
|
232
|
+
* and the finishing host has neither.
|
|
233
|
+
*
|
|
234
|
+
* There is deliberately no `modules` / `extraModules` here. Under `ship` the
|
|
235
|
+
* module list IS the staged payload (ADR 0024 § 2: "a target that needs an
|
|
236
|
+
* extra file gets an overlay, never a branch in the staging code"), and an
|
|
237
|
+
* escape hatch that injects arbitrary build modules would put a second staging
|
|
238
|
+
* model back in the tree — which is the one thing § 8 gates the whole migration
|
|
239
|
+
* on. A project that genuinely needs to BUILD something inside the sandbox
|
|
240
|
+
* still has `gjsify flatpak init` + `gjsify flatpak build`, unchanged.
|
|
241
|
+
*/
|
|
242
|
+
export interface ShipFlatpakSettings {
|
|
243
|
+
/** Runtime id, e.g. `org.gnome.Platform`. */
|
|
244
|
+
runtime: string;
|
|
245
|
+
/** Runtime/SDK version, e.g. `50`. */
|
|
246
|
+
runtimeVersion: string;
|
|
247
|
+
/** SDK id, e.g. `org.gnome.Sdk`. */
|
|
248
|
+
sdk: string;
|
|
249
|
+
/** The branch the app is exported under — the last segment of `app/<id>/<arch>/<branch>`. */
|
|
250
|
+
branch: string;
|
|
251
|
+
sdkExtensions: string[];
|
|
252
|
+
/** Path components prepended to `PATH` inside the build sandbox. */
|
|
253
|
+
appendPath: string[];
|
|
254
|
+
/** Capabilities the finished app is granted. */
|
|
255
|
+
finishArgs: string[];
|
|
256
|
+
/** Cleanup globs applied to `/app` after the module has run. */
|
|
257
|
+
cleanup: string[];
|
|
153
258
|
}
|
|
154
259
|
/** The resolved, fully defaulted configuration for one `gjsify ship` run. */
|
|
155
260
|
export interface ShipSettings extends PackSettings {
|
|
156
261
|
/** Absolute path to the project being shipped. */
|
|
157
262
|
projectDir: string;
|
|
158
|
-
/** Reverse-DNS application id — desktop entry, metainfo and icon basename. */
|
|
159
|
-
appId: string;
|
|
160
263
|
/** Human-readable display name. */
|
|
161
264
|
name: string;
|
|
162
265
|
/** Absolute path to the licence file, when one was found. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -142,21 +142,21 @@
|
|
|
142
142
|
"cli"
|
|
143
143
|
],
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@gjsify/buffer": "^0.
|
|
146
|
-
"@gjsify/create-app": "^0.
|
|
147
|
-
"@gjsify/node-globals": "^0.
|
|
148
|
-
"@gjsify/node-polyfills": "^0.
|
|
149
|
-
"@gjsify/npm-registry": "^0.
|
|
150
|
-
"@gjsify/resolve-npm": "^0.
|
|
151
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
152
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
153
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
154
|
-
"@gjsify/semver": "^0.
|
|
155
|
-
"@gjsify/tar": "^0.
|
|
156
|
-
"@gjsify/tsc": "^0.
|
|
157
|
-
"@gjsify/utils": "^0.
|
|
158
|
-
"@gjsify/web-polyfills": "^0.
|
|
159
|
-
"@gjsify/workspace": "^0.
|
|
145
|
+
"@gjsify/buffer": "^0.44.0",
|
|
146
|
+
"@gjsify/create-app": "^0.44.0",
|
|
147
|
+
"@gjsify/node-globals": "^0.44.0",
|
|
148
|
+
"@gjsify/node-polyfills": "^0.44.0",
|
|
149
|
+
"@gjsify/npm-registry": "^0.44.0",
|
|
150
|
+
"@gjsify/resolve-npm": "^0.44.0",
|
|
151
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.44.0",
|
|
152
|
+
"@gjsify/vite-plugin-blueprint": "^0.44.0",
|
|
153
|
+
"@gjsify/rolldown-plugin-pnp": "^0.44.0",
|
|
154
|
+
"@gjsify/semver": "^0.44.0",
|
|
155
|
+
"@gjsify/tar": "^0.44.0",
|
|
156
|
+
"@gjsify/tsc": "^0.44.0",
|
|
157
|
+
"@gjsify/utils": "^0.44.0",
|
|
158
|
+
"@gjsify/web-polyfills": "^0.44.0",
|
|
159
|
+
"@gjsify/workspace": "^0.44.0",
|
|
160
160
|
"acorn": "^8.17.0",
|
|
161
161
|
"cosmiconfig": "^9.0.2",
|
|
162
162
|
"get-tsconfig": "^4.14.0",
|
|
@@ -165,13 +165,13 @@
|
|
|
165
165
|
"yargs": "^18.0.0"
|
|
166
166
|
},
|
|
167
167
|
"devDependencies": {
|
|
168
|
-
"@gjsify/unit": "^0.
|
|
168
|
+
"@gjsify/unit": "^0.44.0",
|
|
169
169
|
"@types/yargs": "^17.0.35",
|
|
170
170
|
"typescript": "^6.0.3"
|
|
171
171
|
},
|
|
172
172
|
"peerDependencies": {
|
|
173
|
-
"@gjsify/oxfmt-native": "^0.
|
|
174
|
-
"@gjsify/rolldown-native": "^0.
|
|
173
|
+
"@gjsify/oxfmt-native": "^0.44.0",
|
|
174
|
+
"@gjsify/rolldown-native": "^0.44.0"
|
|
175
175
|
},
|
|
176
176
|
"peerDependenciesMeta": {
|
|
177
177
|
"@gjsify/oxfmt-native": {
|