@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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
3
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
4
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_GNOME_RUNTIME_VERSION = "50";
|
|
7
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
8
|
+
export declare const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = "24.08";
|
|
9
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
10
|
+
export declare const DEFAULT_GUI_FINISH_ARGS: string[];
|
|
11
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
12
|
+
export declare const DEFAULT_CLI_FINISH_ARGS: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The runtime keys, structurally — not `ConfigDataFlatpak`.
|
|
15
|
+
*
|
|
16
|
+
* Two config blocks answer this question now (`gjsify.flatpak` and
|
|
17
|
+
* `gjsify.ship.flatpak`, § the deprecation window in `utils/ship/flatpak-config.ts`),
|
|
18
|
+
* and a resolver typed to one of them would have to be duplicated for the other.
|
|
19
|
+
*/
|
|
20
|
+
export interface RuntimeSelection {
|
|
21
|
+
runtime?: string;
|
|
22
|
+
runtimeVersion?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ResolvedRuntime {
|
|
25
|
+
runtime: 'gnome' | 'freedesktop';
|
|
26
|
+
runtimeId: string;
|
|
27
|
+
sdk: string;
|
|
28
|
+
runtimeVersion: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
32
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveRuntime(config: RuntimeSelection | undefined, overrides: RuntimeSelection): ResolvedRuntime;
|
|
35
|
+
/**
|
|
36
|
+
* `append-path` for the build sandbox, derived from the declared SDK extensions.
|
|
37
|
+
*
|
|
38
|
+
* `/app/bin` is appended last so a build command finds the app's own binaries
|
|
39
|
+
* without knowing where they landed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deriveAppendPath(sdkExtensions: readonly string[]): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Which runtime a Flatpak is built against, and what the finished app may do.
|
|
2
|
+
//
|
|
3
|
+
// Lifted out of `commands/flatpak/` when `gjsify ship --target flatpak` became
|
|
4
|
+
// the second caller (ADR 0024 § 8). Same move, same reason, as
|
|
5
|
+
// `utils/app-metadata.ts`: these are facts about the APP, not about whichever
|
|
6
|
+
// command happens to render them, and the second copy is the one that drifts —
|
|
7
|
+
// a `DEFAULT_GNOME_RUNTIME_VERSION` bumped in one place and not the other
|
|
8
|
+
// produces a `ship` Flatpak and a `flatpak init` Flatpak targeting different
|
|
9
|
+
// GNOME releases from the same `package.json`.
|
|
10
|
+
/**
|
|
11
|
+
* Default GNOME-Platform runtime version. Bumped per release window.
|
|
12
|
+
* GNOME 50 = April 2026 stable; tracked in
|
|
13
|
+
* https://docs.flathub.org/docs/for-app-authors/requirements.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_GNOME_RUNTIME_VERSION = '50';
|
|
16
|
+
/** Default Freedesktop-Platform runtime version (LTS-ish). */
|
|
17
|
+
export const DEFAULT_FREEDESKTOP_RUNTIME_VERSION = '24.08';
|
|
18
|
+
/** Permissive GUI defaults for GTK4 + Adwaita apps. */
|
|
19
|
+
export const DEFAULT_GUI_FINISH_ARGS = ['--device=dri', '--share=ipc', '--socket=fallback-x11', '--socket=wayland'];
|
|
20
|
+
/** Lean defaults for headless CLI tools — no display, no GPU. */
|
|
21
|
+
export const DEFAULT_CLI_FINISH_ARGS = [];
|
|
22
|
+
/**
|
|
23
|
+
* Pick the runtime + sdk + version triple from config + CLI overrides.
|
|
24
|
+
* `--runtime` and `--runtime-version` flags win over config values.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveRuntime(config, overrides) {
|
|
27
|
+
const runtime = (overrides.runtime ?? config?.runtime ?? 'gnome');
|
|
28
|
+
if (runtime !== 'gnome' && runtime !== 'freedesktop') {
|
|
29
|
+
throw new Error(`gjsify flatpak: unknown runtime "${runtime}" (expected "gnome" or "freedesktop")`);
|
|
30
|
+
}
|
|
31
|
+
const runtimeVersion = overrides.runtimeVersion ??
|
|
32
|
+
config?.runtimeVersion ??
|
|
33
|
+
(runtime === 'gnome' ? DEFAULT_GNOME_RUNTIME_VERSION : DEFAULT_FREEDESKTOP_RUNTIME_VERSION);
|
|
34
|
+
if (runtime === 'gnome') {
|
|
35
|
+
return { runtime, runtimeId: 'org.gnome.Platform', sdk: 'org.gnome.Sdk', runtimeVersion };
|
|
36
|
+
}
|
|
37
|
+
return { runtime, runtimeId: 'org.freedesktop.Platform', sdk: 'org.freedesktop.Sdk', runtimeVersion };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `append-path` for the build sandbox, derived from the declared SDK extensions.
|
|
41
|
+
*
|
|
42
|
+
* `/app/bin` is appended last so a build command finds the app's own binaries
|
|
43
|
+
* without knowing where they landed.
|
|
44
|
+
*/
|
|
45
|
+
export function deriveAppendPath(sdkExtensions) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const ext of sdkExtensions) {
|
|
48
|
+
const m = /^org\.freedesktop\.Sdk\.Extension\.([A-Za-z0-9-]+)$/.exec(ext);
|
|
49
|
+
if (m)
|
|
50
|
+
out.push(`/usr/lib/sdk/${m[1]}/bin`);
|
|
51
|
+
}
|
|
52
|
+
out.push('/app/bin');
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DistroFormatId } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* The GJS the bundles target (root AGENTS.md § Constraints: GJS 1.86 /
|
|
4
4
|
* SpiderMonkey 140, Rolldown `firefox140`). A `--app gjs` bundle can carry
|
|
@@ -14,7 +14,7 @@ import type { FormatId } from './types.js';
|
|
|
14
14
|
* is how a project whose bundle really does run on older GJS lowers it.
|
|
15
15
|
*/
|
|
16
16
|
export declare const DEFAULT_GJS_FLOOR = "1.86";
|
|
17
|
-
export declare function warnAboutGjsFloor(format:
|
|
17
|
+
export declare function warnAboutGjsFloor(format: DistroFormatId, floor: string): string[];
|
|
18
18
|
export interface DependsInputs {
|
|
19
19
|
/** GI namespaces the bundle imports, in `Ns-Version` spelling. */
|
|
20
20
|
namespaces: readonly string[];
|
|
@@ -32,10 +32,7 @@ export interface DependsInputs {
|
|
|
32
32
|
/** User-supplied additions for this format. */
|
|
33
33
|
extra: readonly string[];
|
|
34
34
|
/** Project-supplied table rows, filling gaps in {@link TYPELIB_PACKAGES}. */
|
|
35
|
-
typelibPackages?: Record<string,
|
|
36
|
-
deb: string;
|
|
37
|
-
rpm: string;
|
|
38
|
-
}>;
|
|
35
|
+
typelibPackages?: Record<string, Record<DistroFormatId, string>>;
|
|
39
36
|
/**
|
|
40
37
|
* Paths of typelib files the payload carries itself. The namespaces they cover need no distro
|
|
41
38
|
* dependency — and are derived from these filenames, never declared separately.
|
|
@@ -49,7 +46,7 @@ export interface DependsInputs {
|
|
|
49
46
|
*
|
|
50
47
|
* @throws when a namespace has no entry in the table — see the module header.
|
|
51
48
|
*/
|
|
52
|
-
export declare function deriveDepends(format:
|
|
49
|
+
export declare function deriveDepends(format: DistroFormatId, inputs: DependsInputs): string[];
|
|
53
50
|
/** One parsed dependency. `relation`/`version` are absent for an unversioned one. */
|
|
54
51
|
export interface ParsedDepend {
|
|
55
52
|
name: string;
|
|
@@ -41,7 +41,7 @@ export const DEFAULT_GJS_FLOOR = '1.86';
|
|
|
41
41
|
* A record, not `format === 'deb' ? … : …`. The ternary reads as a two-way
|
|
42
42
|
* choice and is really "deb, or ELSE rpm's package name" — measured on a third
|
|
43
43
|
* format, `dmg` got `glib2`, at exit 0, in the `Depends:` of a package that has
|
|
44
|
-
* no such thing. Adding a `
|
|
44
|
+
* no such thing. Adding a `DistroFormatId` now fails to build here instead.
|
|
45
45
|
*/
|
|
46
46
|
const SCHEMA_COMPILER_PACKAGE = { deb: 'libglib2.0-bin', rpm: 'glib2' };
|
|
47
47
|
/** Debian's `gjs` versions per suite, as measured 2026-08-15 — see {@link warnAboutGjsFloor}. */
|
|
@@ -53,10 +53,10 @@ const DEBIAN_GJS = 'trixie ships 1.82.3 and forky 1.88.1; 1.84 and 1.86 were ski
|
|
|
53
53
|
/**
|
|
54
54
|
* Which formats this Debian-suite warning is ABOUT.
|
|
55
55
|
*
|
|
56
|
-
* A `Record<
|
|
56
|
+
* A `Record<DistroFormatId, …>` and not `format !== 'deb'`: the negative form answers
|
|
57
57
|
* for every format that will ever exist, and answers "stay quiet" — so a third
|
|
58
58
|
* format would inherit silence about a floor nobody has checked for its distro,
|
|
59
|
-
* with no compile error. As a record, adding a `
|
|
59
|
+
* with no compile error. As a record, adding a `DistroFormatId` fails to build until
|
|
60
60
|
* someone decides. `rpm` is false because Fedora ships a current GJS, which is a
|
|
61
61
|
* measured fact about Fedora, not a default.
|
|
62
62
|
*/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ConfigDataFlatpak, ConfigDataShip, ShipFlatpakOptions } from '../../types/config-data.js';
|
|
2
|
+
import type { ShipFlatpakSettings } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The release that stops reading `gjsify.flatpak` for the keys below.
|
|
5
|
+
*
|
|
6
|
+
* A major, not the next minor: the block is in published releases and in
|
|
7
|
+
* downstream `package.json`s that this repository does not control, and a
|
|
8
|
+
* window measured in weeks is a window nobody notices before it closes.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LEGACY_FLATPAK_KEYS_REMOVED_IN = "1.0.0";
|
|
11
|
+
/** The keys whose new home is `gjsify.ship.flatpak`, in the order the warning lists them. */
|
|
12
|
+
export declare const MIGRATED_FLATPAK_KEYS: readonly ["runtime", "runtimeVersion", "sdkExtensions", "appendPath", "finishArgs", "cleanup"];
|
|
13
|
+
export type MigratedFlatpakKey = (typeof MIGRATED_FLATPAK_KEYS)[number];
|
|
14
|
+
/** The six keys as a reader sees them, before any default is applied. */
|
|
15
|
+
export type MigratedFlatpakValues = {
|
|
16
|
+
[K in MigratedFlatpakKey]: ConfigDataFlatpak[K];
|
|
17
|
+
};
|
|
18
|
+
export interface PickedFlatpakBuildKeys {
|
|
19
|
+
values: MigratedFlatpakValues;
|
|
20
|
+
/** Keys that came from `gjsify.flatpak`, in {@link MIGRATED_FLATPAK_KEYS} order. */
|
|
21
|
+
fromLegacy: MigratedFlatpakKey[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `gjsify.ship.flatpak.<key>` when set, else `gjsify.flatpak.<key>` — per KEY,
|
|
25
|
+
* noting which side answered.
|
|
26
|
+
*
|
|
27
|
+
* Per-key and not per-block: a project migrating one key at a time must not
|
|
28
|
+
* lose the five it has not moved yet. That is also what makes the warning
|
|
29
|
+
* actionable — it names the keys still coming from the old block, so the reader
|
|
30
|
+
* knows what to edit rather than being told a block is deprecated.
|
|
31
|
+
*
|
|
32
|
+
* A LOOP over {@link MIGRATED_FLATPAK_KEYS}, not six hand-written picks. Written
|
|
33
|
+
* out, the list was the one unbound copy of this vocabulary: a key added to it
|
|
34
|
+
* with no `pick` call behind it compiled fine, went missing from the window, and
|
|
35
|
+
* the only test that mentioned the constant compared it to a literal copy of
|
|
36
|
+
* itself. Reading the keys off the constant is what makes "this list IS the
|
|
37
|
+
* window" true rather than asserted.
|
|
38
|
+
*
|
|
39
|
+
* NO default is applied here. The defaults differ per reader — `ship` derives
|
|
40
|
+
* `finishArgs` from `kind`, `flatpak init` also merges `--sdk-extension` flags —
|
|
41
|
+
* and a default applied here would be a third answer neither of them chose.
|
|
42
|
+
*/
|
|
43
|
+
export declare function pickFlatpakBuildKeys(preferred: ShipFlatpakOptions | undefined, legacy: ConfigDataFlatpak): PickedFlatpakBuildKeys;
|
|
44
|
+
/** The branch a `ship` Flatpak is exported under when the project names none. */
|
|
45
|
+
export declare const DEFAULT_SHIP_FLATPAK_BRANCH = "stable";
|
|
46
|
+
export interface ShipFlatpakInput {
|
|
47
|
+
ship: ConfigDataShip;
|
|
48
|
+
flatpak: ConfigDataFlatpak;
|
|
49
|
+
/** Decides the finish-args default: a GUI app needs a display, a CLI does not. */
|
|
50
|
+
kind: 'app' | 'cli';
|
|
51
|
+
}
|
|
52
|
+
export interface ResolvedShipFlatpak {
|
|
53
|
+
settings: ShipFlatpakSettings;
|
|
54
|
+
warnings: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the Flatpak half of `gjsify.ship`, reading the legacy block where the
|
|
58
|
+
* new one is silent, and applying `ship`'s own defaults.
|
|
59
|
+
*
|
|
60
|
+
* The per-key fallback itself is {@link pickFlatpakBuildKeys}, shared with
|
|
61
|
+
* `gjsify flatpak init`/`ci`; what is `ship`-specific and stays here is the
|
|
62
|
+
* defaulting — `branch`, the `kind`-dependent finish-args, the derived
|
|
63
|
+
* `appendPath` — plus the warning, which only `ship` prints because only `ship`
|
|
64
|
+
* has somewhere else to point at.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveShipFlatpakSettings(input: ShipFlatpakInput): ResolvedShipFlatpak;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// The deprecation window on the `gjsify.flatpak` build keys (ADR 0024 § 8).
|
|
2
|
+
//
|
|
3
|
+
// § 8 names the cost of the migration and does not let it be skipped:
|
|
4
|
+
// "`gjsify.flatpak` is a published config contract, so the keys move with a
|
|
5
|
+
// deprecation window in which both spellings resolve and the old one warns."
|
|
6
|
+
// This file is that window.
|
|
7
|
+
//
|
|
8
|
+
// WHAT MOVED and what did not — the table is `docs/ship-formats.md`, and the
|
|
9
|
+
// short form is: the six BUILD keys moved and warn from the old spelling; the
|
|
10
|
+
// `AppMetadata` half is a designed ALIAS and is NOT deprecated (both blocks
|
|
11
|
+
// describe the same app, and warning on it would print for every project that
|
|
12
|
+
// has a `gjsify.flatpak` block at all); and the TOOLCHAIN keys are untouched
|
|
13
|
+
// because `gjsify flatpak <sub>` has not moved, so deprecating them would warn
|
|
14
|
+
// on commands with nowhere else to read from.
|
|
15
|
+
//
|
|
16
|
+
// A WINDOW HAS TWO SIDES, and the first cut only built one. The six build keys
|
|
17
|
+
// are read by `gjsify flatpak init` and `flatpak ci` as well, and those
|
|
18
|
+
// commands have NOT moved — so a project that did what this file's warning told
|
|
19
|
+
// it to do, and moved them, silently lost them there: `flatpak init` fell back
|
|
20
|
+
// to its own defaults and wrote a manifest against a different
|
|
21
|
+
// `org.gnome.Platform` version with different finish-args, committed, at exit 0.
|
|
22
|
+
// That is why {@link pickFlatpakBuildKeys} is exported and BOTH command groups
|
|
23
|
+
// go through it. "Both spellings resolve" is now true of every reader of these
|
|
24
|
+
// keys, which is what makes the advice safe to follow.
|
|
25
|
+
//
|
|
26
|
+
// WHY A NAMED VERSION, IN ONE PLACE. The precedent is
|
|
27
|
+
// `utils/normalize-bundler-options.ts`, the `esbuild` → `bundler` shim, and it
|
|
28
|
+
// also carries the precedent's DEFECT: its warning text names 0.5.0, the tree is
|
|
29
|
+
// far past it, and its header now has to say "removing the shim means fixing
|
|
30
|
+
// that string too". So the version here is a constant the message reads, and the
|
|
31
|
+
// spec asserts the message names it.
|
|
32
|
+
import { DEFAULT_CLI_FINISH_ARGS, DEFAULT_GUI_FINISH_ARGS, deriveAppendPath, resolveRuntime, } from '../flatpak-runtime.js';
|
|
33
|
+
/**
|
|
34
|
+
* The release that stops reading `gjsify.flatpak` for the keys below.
|
|
35
|
+
*
|
|
36
|
+
* A major, not the next minor: the block is in published releases and in
|
|
37
|
+
* downstream `package.json`s that this repository does not control, and a
|
|
38
|
+
* window measured in weeks is a window nobody notices before it closes.
|
|
39
|
+
*/
|
|
40
|
+
export const LEGACY_FLATPAK_KEYS_REMOVED_IN = '1.0.0';
|
|
41
|
+
/** The keys whose new home is `gjsify.ship.flatpak`, in the order the warning lists them. */
|
|
42
|
+
export const MIGRATED_FLATPAK_KEYS = [
|
|
43
|
+
'runtime',
|
|
44
|
+
'runtimeVersion',
|
|
45
|
+
'sdkExtensions',
|
|
46
|
+
'appendPath',
|
|
47
|
+
'finishArgs',
|
|
48
|
+
'cleanup',
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* `gjsify.ship.flatpak.<key>` when set, else `gjsify.flatpak.<key>` — per KEY,
|
|
52
|
+
* noting which side answered.
|
|
53
|
+
*
|
|
54
|
+
* Per-key and not per-block: a project migrating one key at a time must not
|
|
55
|
+
* lose the five it has not moved yet. That is also what makes the warning
|
|
56
|
+
* actionable — it names the keys still coming from the old block, so the reader
|
|
57
|
+
* knows what to edit rather than being told a block is deprecated.
|
|
58
|
+
*
|
|
59
|
+
* A LOOP over {@link MIGRATED_FLATPAK_KEYS}, not six hand-written picks. Written
|
|
60
|
+
* out, the list was the one unbound copy of this vocabulary: a key added to it
|
|
61
|
+
* with no `pick` call behind it compiled fine, went missing from the window, and
|
|
62
|
+
* the only test that mentioned the constant compared it to a literal copy of
|
|
63
|
+
* itself. Reading the keys off the constant is what makes "this list IS the
|
|
64
|
+
* window" true rather than asserted.
|
|
65
|
+
*
|
|
66
|
+
* NO default is applied here. The defaults differ per reader — `ship` derives
|
|
67
|
+
* `finishArgs` from `kind`, `flatpak init` also merges `--sdk-extension` flags —
|
|
68
|
+
* and a default applied here would be a third answer neither of them chose.
|
|
69
|
+
*/
|
|
70
|
+
export function pickFlatpakBuildKeys(preferred, legacy) {
|
|
71
|
+
const ship = preferred ?? {};
|
|
72
|
+
// Assembled through an index signature because the value type varies per
|
|
73
|
+
// key; the cast is on the finished object, where every key is present.
|
|
74
|
+
const values = {};
|
|
75
|
+
const fromLegacy = [];
|
|
76
|
+
for (const key of MIGRATED_FLATPAK_KEYS) {
|
|
77
|
+
const own = ship[key];
|
|
78
|
+
if (own !== undefined) {
|
|
79
|
+
values[key] = own;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const inherited = legacy[key];
|
|
83
|
+
if (inherited !== undefined)
|
|
84
|
+
fromLegacy.push(key);
|
|
85
|
+
values[key] = inherited;
|
|
86
|
+
}
|
|
87
|
+
return { values: values, fromLegacy };
|
|
88
|
+
}
|
|
89
|
+
/** The branch a `ship` Flatpak is exported under when the project names none. */
|
|
90
|
+
export const DEFAULT_SHIP_FLATPAK_BRANCH = 'stable';
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the Flatpak half of `gjsify.ship`, reading the legacy block where the
|
|
93
|
+
* new one is silent, and applying `ship`'s own defaults.
|
|
94
|
+
*
|
|
95
|
+
* The per-key fallback itself is {@link pickFlatpakBuildKeys}, shared with
|
|
96
|
+
* `gjsify flatpak init`/`ci`; what is `ship`-specific and stays here is the
|
|
97
|
+
* defaulting — `branch`, the `kind`-dependent finish-args, the derived
|
|
98
|
+
* `appendPath` — plus the warning, which only `ship` prints because only `ship`
|
|
99
|
+
* has somewhere else to point at.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveShipFlatpakSettings(input) {
|
|
102
|
+
const shipFlatpak = input.ship.flatpak ?? {};
|
|
103
|
+
const { values, fromLegacy } = pickFlatpakBuildKeys(shipFlatpak, input.flatpak);
|
|
104
|
+
const resolved = resolveRuntime(values, {});
|
|
105
|
+
const extensions = values.sdkExtensions ?? [];
|
|
106
|
+
const { appendPath, finishArgs, cleanup } = values;
|
|
107
|
+
const settings = {
|
|
108
|
+
runtime: resolved.runtimeId,
|
|
109
|
+
runtimeVersion: resolved.runtimeVersion,
|
|
110
|
+
sdk: resolved.sdk,
|
|
111
|
+
branch: shipFlatpak.branch ?? DEFAULT_SHIP_FLATPAK_BRANCH,
|
|
112
|
+
sdkExtensions: [...extensions],
|
|
113
|
+
// Derived from the extensions when nothing was declared, and only then:
|
|
114
|
+
// an extension whose `/usr/lib/sdk/<x>/bin` is not on PATH inside the
|
|
115
|
+
// sandbox is an extension the build cannot use, which looks like the
|
|
116
|
+
// extension not being installed.
|
|
117
|
+
appendPath: appendPath ?? (extensions.length > 0 ? deriveAppendPath(extensions) : []),
|
|
118
|
+
finishArgs: finishArgs ?? (input.kind === 'cli' ? DEFAULT_CLI_FINISH_ARGS : DEFAULT_GUI_FINISH_ARGS),
|
|
119
|
+
cleanup: cleanup ?? [],
|
|
120
|
+
};
|
|
121
|
+
return { settings, warnings: fromLegacy.length === 0 ? [] : [deprecationWarning(fromLegacy)] };
|
|
122
|
+
}
|
|
123
|
+
/** One line, naming every key still read from the old block and where each goes. */
|
|
124
|
+
function deprecationWarning(keys) {
|
|
125
|
+
const list = keys.map((key) => `gjsify.flatpak.${key}`).join(', ');
|
|
126
|
+
const moved = keys.map((key) => `gjsify.ship.flatpak.${key}`).join(', ');
|
|
127
|
+
return (`DEPRECATION: ${list} ${keys.length > 1 ? 'are' : 'is'} read by \`gjsify ship\` for compatibility and ` +
|
|
128
|
+
`will be removed in ${LEGACY_FLATPAK_KEYS_REMOVED_IN}. Move ${keys.length > 1 ? 'them' : 'it'} to ` +
|
|
129
|
+
`${moved} — both spellings resolve until then, and \`gjsify.ship.flatpak\` wins where both are set. ` +
|
|
130
|
+
'The app METADATA in `gjsify.flatpak` (name, summary, developer, categories, licence) is NOT ' +
|
|
131
|
+
'deprecated: both blocks describe the same application and either may carry it (ADR 0024 § 8).');
|
|
132
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PackSettings } from './types.js';
|
|
2
|
+
export interface FlatpakManifestInput {
|
|
3
|
+
settings: PackSettings;
|
|
4
|
+
/** Absolute path of the staged payload — the tree `readStage` validated. */
|
|
5
|
+
stageDir: string;
|
|
6
|
+
/** Absolute path of this format's rendered overlay (the licence file). */
|
|
7
|
+
overlayDir: string;
|
|
8
|
+
/** The stage's own closure file, which is not payload and must not be copied into `/app`. */
|
|
9
|
+
stageManifestFile: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The generated Flatpak manifest, as a plain object.
|
|
13
|
+
*
|
|
14
|
+
* Pure, so every field is unit-testable without flatpak tooling — and the
|
|
15
|
+
* suite that needs that is the one running in CI, where there is none.
|
|
16
|
+
*
|
|
17
|
+
* The app id is what makes this legal Flatpak: every file flatpak EXPORTS out
|
|
18
|
+
* of `/app/share` has to be named after it, and `planStage` already enforces
|
|
19
|
+
* exactly that for its own reasons — the desktop entry, the metainfo, the icon,
|
|
20
|
+
* the GSettings schema and the shared-mime-info document are all
|
|
21
|
+
* `<appId>[.…].<ext>`, because `share/glib-2.0/schemas/` and
|
|
22
|
+
* `share/mime/packages/` are directories shared between packages. Two
|
|
23
|
+
* independent constraints, one naming rule.
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderShipFlatpakManifest(input: FlatpakManifestInput): Record<string, unknown>;
|
|
26
|
+
export interface FlatpakPackInput extends FlatpakManifestInput {
|
|
27
|
+
/** Working root for the manifest, the build dir, the repo and the builder's state. */
|
|
28
|
+
workDir: string;
|
|
29
|
+
/** Absolute path the finished `.flatpak` is written to. */
|
|
30
|
+
target: string;
|
|
31
|
+
/** Flatpak's spelling of the architecture, e.g. `x86_64`. */
|
|
32
|
+
archLabel: string;
|
|
33
|
+
verbose: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build the app and export a single-file bundle at `input.target`.
|
|
37
|
+
*
|
|
38
|
+
* The only packer in this tree that execs anything, and the descriptor says so
|
|
39
|
+
* (`FORMATS.flatpak.host.requiredTools`). ADR 0024 § A3 is explicit that this
|
|
40
|
+
* costs the independent oracle the hand-written deb/rpm writers bought: what
|
|
41
|
+
* pays it back here is that a `.flatpak` is an OSTree static delta, so
|
|
42
|
+
* `flatpak build-import-bundle` + `ostree ls -R` read the artifact back with
|
|
43
|
+
* code this tree did not write.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildFlatpakBundle(input: FlatpakPackInput): Promise<void>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// The Flatpak packer — ADR 0024 § 8, stage 6.
|
|
2
|
+
//
|
|
3
|
+
// This is a MOVE, not a second implementation. `gjsify flatpak` has emitted
|
|
4
|
+
// Flatpak manifests since long before `ship` existed; what it emitted was a
|
|
5
|
+
// `buildsystem: meson` module, which made every app carry meson glue whose only
|
|
6
|
+
// job was to call `gjsify build` and copy the result into a prefix — 158 lines
|
|
7
|
+
// of it in Learn6502, 66 of them `GI_TYPELIB_PATH`/`LD_LIBRARY_PATH` plumbing.
|
|
8
|
+
// `ship` already produces that prefix. So the module becomes:
|
|
9
|
+
//
|
|
10
|
+
// buildsystem: simple
|
|
11
|
+
// build-commands: mkdir -p /app; cp -a stage/. /app/; cp -a overlay/. /app/
|
|
12
|
+
//
|
|
13
|
+
// and meson leaves the sandbox. Nothing else about the payload changes — the
|
|
14
|
+
// launcher derives its own prefix at runtime (§ 3), which is exactly why one
|
|
15
|
+
// staged tree can be `/usr` in a `.deb` and `/app` here.
|
|
16
|
+
//
|
|
17
|
+
// FOUR THINGS ABOUT flatpak-builder 1.4.10 WERE MEASURED before this file was
|
|
18
|
+
// written, because each had a plausible wrong answer and flatpak-builder reports
|
|
19
|
+
// none of them: an absolute `dir` source `path` works, `skip` works and is what
|
|
20
|
+
// keeps the stage's own sidecar out of `/app`, `cp -a` preserves the mode, and
|
|
21
|
+
// `--show-manifest` is NOT a validator (it accepted an unknown source property
|
|
22
|
+
// and `buildsystem: "nonsense"` at exit 0, so it must never be used as a gate).
|
|
23
|
+
// The numbers, the commands and the oracle that replaces `--show-manifest`:
|
|
24
|
+
// `docs/ship-formats.md`.
|
|
25
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import { describeExit, spawnToCompletion } from '../spawn.js';
|
|
28
|
+
/**
|
|
29
|
+
* The generated Flatpak manifest, as a plain object.
|
|
30
|
+
*
|
|
31
|
+
* Pure, so every field is unit-testable without flatpak tooling — and the
|
|
32
|
+
* suite that needs that is the one running in CI, where there is none.
|
|
33
|
+
*
|
|
34
|
+
* The app id is what makes this legal Flatpak: every file flatpak EXPORTS out
|
|
35
|
+
* of `/app/share` has to be named after it, and `planStage` already enforces
|
|
36
|
+
* exactly that for its own reasons — the desktop entry, the metainfo, the icon,
|
|
37
|
+
* the GSettings schema and the shared-mime-info document are all
|
|
38
|
+
* `<appId>[.…].<ext>`, because `share/glib-2.0/schemas/` and
|
|
39
|
+
* `share/mime/packages/` are directories shared between packages. Two
|
|
40
|
+
* independent constraints, one naming rule.
|
|
41
|
+
*/
|
|
42
|
+
export function renderShipFlatpakManifest(input) {
|
|
43
|
+
const { settings, stageDir, overlayDir } = input;
|
|
44
|
+
const flatpak = settings.flatpak;
|
|
45
|
+
const manifest = {
|
|
46
|
+
id: settings.appId,
|
|
47
|
+
runtime: flatpak.runtime,
|
|
48
|
+
'runtime-version': flatpak.runtimeVersion,
|
|
49
|
+
sdk: flatpak.sdk,
|
|
50
|
+
branch: flatpak.branch,
|
|
51
|
+
// `bin/<binaryName>`, which is the one executable the stage plans. NOT
|
|
52
|
+
// the app id, which is what `gjsify flatpak init` defaults to: under
|
|
53
|
+
// `ship` the launcher's name is decided by `gjsify.ship.binaryName`, and
|
|
54
|
+
// a `command` naming a file that is not in `/app/bin` produces an app
|
|
55
|
+
// that installs and cannot be launched.
|
|
56
|
+
command: settings.binaryName,
|
|
57
|
+
'finish-args': flatpak.finishArgs,
|
|
58
|
+
};
|
|
59
|
+
if (flatpak.sdkExtensions.length > 0)
|
|
60
|
+
manifest['sdk-extensions'] = flatpak.sdkExtensions;
|
|
61
|
+
if (flatpak.appendPath.length > 0) {
|
|
62
|
+
manifest['build-options'] = { 'append-path': flatpak.appendPath.join(':') };
|
|
63
|
+
}
|
|
64
|
+
if (flatpak.cleanup.length > 0)
|
|
65
|
+
manifest.cleanup = flatpak.cleanup;
|
|
66
|
+
manifest.modules = [
|
|
67
|
+
{
|
|
68
|
+
name: settings.binaryName,
|
|
69
|
+
buildsystem: 'simple',
|
|
70
|
+
// `mkdir -p /app` first: flatpak-builder creates the app directory,
|
|
71
|
+
// but a module whose only command is a `cp` into a path that does
|
|
72
|
+
// not exist yet is one refactor away from failing, and the mkdir is
|
|
73
|
+
// free. `stage/.` and not `stage` — `cp -a stage /app/` would
|
|
74
|
+
// create `/app/stage`, which installs cleanly and puts the whole
|
|
75
|
+
// payload one directory too deep.
|
|
76
|
+
'build-commands': ['mkdir -p /app', 'cp -a stage/. /app/', 'cp -a overlay/. /app/'],
|
|
77
|
+
sources: [
|
|
78
|
+
{ type: 'dir', path: stageDir, dest: 'stage', skip: [input.stageManifestFile] },
|
|
79
|
+
{ type: 'dir', path: overlayDir, dest: 'overlay' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
return manifest;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build the app and export a single-file bundle at `input.target`.
|
|
87
|
+
*
|
|
88
|
+
* The only packer in this tree that execs anything, and the descriptor says so
|
|
89
|
+
* (`FORMATS.flatpak.host.requiredTools`). ADR 0024 § A3 is explicit that this
|
|
90
|
+
* costs the independent oracle the hand-written deb/rpm writers bought: what
|
|
91
|
+
* pays it back here is that a `.flatpak` is an OSTree static delta, so
|
|
92
|
+
* `flatpak build-import-bundle` + `ostree ls -R` read the artifact back with
|
|
93
|
+
* code this tree did not write.
|
|
94
|
+
*/
|
|
95
|
+
export async function buildFlatpakBundle(input) {
|
|
96
|
+
const { settings, workDir, archLabel } = input;
|
|
97
|
+
const manifestPath = join(workDir, `${settings.appId}.json`);
|
|
98
|
+
const buildDir = join(workDir, 'build');
|
|
99
|
+
const repoDir = join(workDir, 'repo');
|
|
100
|
+
// Kept between runs: it is flatpak-builder's own ostree cache, and dropping
|
|
101
|
+
// it turns every re-pack into a full rebuild of every module.
|
|
102
|
+
const stateDir = join(workDir, 'state');
|
|
103
|
+
mkdirSync(workDir, { recursive: true });
|
|
104
|
+
// The repo is wiped, the state dir is not. A repo that survives holds the
|
|
105
|
+
// PREVIOUS commit on the same ref, and `build-bundle` would happily export
|
|
106
|
+
// it if this run's export failed for any reason that did not fail the
|
|
107
|
+
// build — a stale artifact wearing the new version's filename.
|
|
108
|
+
rmSync(repoDir, { recursive: true, force: true, maxRetries: 5 });
|
|
109
|
+
writeFileSync(manifestPath, `${JSON.stringify(renderShipFlatpakManifest(input), null, 2)}\n`);
|
|
110
|
+
await run('flatpak-builder', [
|
|
111
|
+
'--force-clean',
|
|
112
|
+
// Needed wherever FUSE is unavailable — containers, most CI runners.
|
|
113
|
+
// It only disables a read-only-bind optimisation, and a build that
|
|
114
|
+
// cannot start is worse than one that copies a few more bytes.
|
|
115
|
+
'--disable-rofiles-fuse',
|
|
116
|
+
'--delete-build-dirs',
|
|
117
|
+
`--state-dir=${stateDir}`,
|
|
118
|
+
`--arch=${archLabel}`,
|
|
119
|
+
`--repo=${repoDir}`,
|
|
120
|
+
buildDir,
|
|
121
|
+
manifestPath,
|
|
122
|
+
], input, 'flatpak-builder not found. Install it (Fedora: `sudo dnf install flatpak-builder`).');
|
|
123
|
+
// The branch is passed explicitly even though the manifest declares it: the
|
|
124
|
+
// two have to name the same ref, and `build-bundle`'s default is `master`
|
|
125
|
+
// while a manifest that sets `branch` exports somewhere else. Leaving it
|
|
126
|
+
// implicit is how this exports nothing and says "ref not found".
|
|
127
|
+
await run('flatpak', ['build-bundle', `--arch=${archLabel}`, repoDir, input.target, settings.appId, settings.flatpak.branch], input, 'flatpak not found. Install it via your distro: see https://flathub.org/setup.');
|
|
128
|
+
}
|
|
129
|
+
async function run(cmd, args, input, notFoundHint) {
|
|
130
|
+
if (input.verbose)
|
|
131
|
+
console.log(`[gjsify ship] ${cmd} ${args.join(' ')}`);
|
|
132
|
+
// `completion: 'return'` because `ship` packs the remaining formats and
|
|
133
|
+
// prints the artifact list afterwards, so it cannot end in `process.exit()`
|
|
134
|
+
// the way `gjsify flatpak build` does. Under GJS that selects the blocking
|
|
135
|
+
// path, which captures the child's output and re-emits it at the end
|
|
136
|
+
// instead of streaming it — the documented cost of a spawn a caller has to
|
|
137
|
+
// return from (see utils/spawn.ts), and the reason `--verbose` prints the
|
|
138
|
+
// invocation before the silence starts.
|
|
139
|
+
const result = await spawnToCompletion(cmd, args, {
|
|
140
|
+
completion: 'return',
|
|
141
|
+
cwd: input.workDir,
|
|
142
|
+
stdio: 'inherit',
|
|
143
|
+
notFound: () => new Error(`gjsify ship: ${notFoundHint}`),
|
|
144
|
+
});
|
|
145
|
+
if (result.code !== 0) {
|
|
146
|
+
throw new Error(`gjsify ship: ${cmd} failed${describeExit(result)}.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
import type { FormatDescriptor, FormatId } from './types.js';
|
|
2
2
|
export declare const FORMATS: Record<FormatId, FormatDescriptor>;
|
|
3
3
|
export declare const FORMAT_IDS: FormatId[];
|
|
4
|
+
/**
|
|
5
|
+
* What `gjsify ship` builds when nothing said otherwise — every format that
|
|
6
|
+
* needs no tool and no particular host.
|
|
7
|
+
*
|
|
8
|
+
* A SECOND derivation from the same table, and it has to be one: `FORMAT_IDS`
|
|
9
|
+
* used to be both "every format that exists" and "every format to build by
|
|
10
|
+
* default", which was the same list only while every format was `finishOn:
|
|
11
|
+
* 'any'`. Adding Flatpak to `FORMAT_IDS` alone would have made a bare
|
|
12
|
+
* `gjsify ship` demand `flatpak-builder` of every project that ever packaged a
|
|
13
|
+
* `.deb` — and on a host without it, of every `release-cut.yml` run, which
|
|
14
|
+
* packs `@gjsify/cli` itself on a bare ubuntu runner. A host-bound format is
|
|
15
|
+
* opt-in through `--target` or `gjsify.ship.targets`; `--help` still lists all
|
|
16
|
+
* of them.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_FORMAT_IDS: FormatId[];
|
|
19
|
+
/**
|
|
20
|
+
* Refuse a format this host cannot finish, BEFORE anything is built or written.
|
|
21
|
+
*
|
|
22
|
+
* `host` is injected so both branches are unit-testable from any machine — the
|
|
23
|
+
* same reason `resolvePrebuildDirName` and `checkTypeSkew`'s readers are pure.
|
|
24
|
+
* The message names the two-phase split rather than just saying no: the answer
|
|
25
|
+
* to "I am on macOS and want a Flatpak" is `--stage` here and `--from-stage` on
|
|
26
|
+
* a Linux runner, and a refusal that does not say so reads as "unsupported".
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertHostCanFinish(format: FormatDescriptor, host?: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Refuse a format whose tools are not installed, with the tool named.
|
|
31
|
+
*
|
|
32
|
+
* Separate from {@link assertHostCanFinish} because the two failures have
|
|
33
|
+
* different fixes — one needs a different machine, the other needs a package —
|
|
34
|
+
* and separate from the packer's own `notFound` handler because it fires BEFORE
|
|
35
|
+
* a build: `gjsify ship` runs the project's `build` script first, and finding
|
|
36
|
+
* out afterwards that `flatpak-builder` is absent costs the whole build.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertToolsInstalled(format: FormatDescriptor, present?: (cmd: string) => boolean): void;
|
|
4
39
|
/** Parse `--target deb,rpm` into a sorted, deduplicated descriptor list. */
|
|
5
40
|
export declare function resolveFormats(raw: readonly string[]): FormatDescriptor[];
|