@gjsify/cli 0.42.0 → 0.43.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 +238 -210
- package/lib/actions/build.d.ts +6 -0
- package/lib/actions/build.js +64 -8
- package/lib/commands/ship.d.ts +2 -0
- package/lib/commands/ship.js +285 -93
- package/lib/types/config-data.d.ts +32 -0
- package/lib/utils/gjs-bundle-guard.d.ts +57 -3
- package/lib/utils/gjs-bundle-guard.js +122 -13
- package/lib/utils/jsx-config.d.ts +45 -0
- package/lib/utils/jsx-config.js +98 -0
- package/lib/utils/onboard-probe.d.ts +23 -0
- package/lib/utils/onboard-probe.js +65 -0
- package/lib/utils/ship/deb.d.ts +3 -3
- package/lib/utils/ship/deb.js +6 -2
- package/lib/utils/ship/depends.d.ts +9 -6
- package/lib/utils/ship/depends.js +25 -3
- package/lib/utils/ship/discover.js +72 -6
- package/lib/utils/ship/formats.js +9 -1
- package/lib/utils/ship/mime.d.ts +19 -0
- package/lib/utils/ship/mime.js +89 -0
- package/lib/utils/ship/payload.d.ts +76 -0
- package/lib/utils/ship/payload.js +91 -1
- package/lib/utils/ship/plan.d.ts +30 -1
- package/lib/utils/ship/plan.js +54 -2
- package/lib/utils/ship/rpm.d.ts +3 -3
- package/lib/utils/ship/rpm.js +20 -3
- package/lib/utils/ship/scripts.d.ts +14 -4
- package/lib/utils/ship/scripts.js +26 -8
- package/lib/utils/ship/settings.js +13 -0
- package/lib/utils/ship/stage-manifest.d.ts +154 -0
- package/lib/utils/ship/stage-manifest.js +408 -0
- package/lib/utils/ship/stage-writer.d.ts +30 -3
- package/lib/utils/ship/stage-writer.js +76 -9
- package/lib/utils/ship/types.d.ts +91 -18
- package/lib/utils/ship/types.js +5 -0
- package/package.json +19 -19
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PayloadFacts } from './payload.js';
|
|
2
2
|
/**
|
|
3
3
|
* The cache refreshes this payload makes necessary, each guarded by the tool's
|
|
4
4
|
* presence and `|| true`.
|
|
@@ -6,8 +6,18 @@ import type { ShipSettings } from './types.js';
|
|
|
6
6
|
* The caches belong to the distribution, not to us: on removal the helper may
|
|
7
7
|
* already be gone, and a maintainer script that fails during a purge leaves a
|
|
8
8
|
* package the user cannot uninstall.
|
|
9
|
+
*
|
|
10
|
+
* Keyed on the PAYLOAD, not on the settings. Each line refreshes exactly one
|
|
11
|
+
* directory, so the honest precondition is "did this package install anything
|
|
12
|
+
* into that directory" — which the payload answers and a project's file lists
|
|
13
|
+
* only approximate. They came apart in one measured case: a `kind: 'cli'`
|
|
14
|
+
* project with a `data/icons/` folder staged no icon at all (icons are an
|
|
15
|
+
* `'app'` thing in `planStage`) while `settings.iconFiles` was non-empty, so
|
|
16
|
+
* the postinst refreshed an icon cache for files that were never installed.
|
|
17
|
+
* The second reason is ADR 0024 § A2: those lists are absolute BUILD-host
|
|
18
|
+
* paths, and this code now also runs on a host that has only the staged tree.
|
|
9
19
|
*/
|
|
10
|
-
export declare function cacheRefreshCommands(
|
|
20
|
+
export declare function cacheRefreshCommands(facts: PayloadFacts, prefix: string): string[];
|
|
11
21
|
/**
|
|
12
22
|
* dpkg maintainer scripts, keyed by their `control.tar` filename.
|
|
13
23
|
*
|
|
@@ -15,7 +25,7 @@ export declare function cacheRefreshCommands(settings: ShipSettings, prefix: str
|
|
|
15
25
|
* and several abort paths, and on an upgrade the incoming package's `postinst`
|
|
16
26
|
* refreshes the caches anyway.
|
|
17
27
|
*/
|
|
18
|
-
export declare function renderDebScripts(
|
|
28
|
+
export declare function renderDebScripts(facts: PayloadFacts, prefix: string): Record<string, string>;
|
|
19
29
|
/**
|
|
20
30
|
* rpm scriptlets, for the `POSTIN` / `POSTUN` header tags.
|
|
21
31
|
*
|
|
@@ -25,7 +35,7 @@ export declare function renderDebScripts(settings: ShipSettings, prefix: string)
|
|
|
25
35
|
* work rather than a wrong result, where guarding on `[ $1 -eq 0 ]` would be
|
|
26
36
|
* one more place to get the convention backwards.
|
|
27
37
|
*/
|
|
28
|
-
export declare function renderRpmScriptlets(
|
|
38
|
+
export declare function renderRpmScriptlets(facts: PayloadFacts, prefix: string): {
|
|
29
39
|
post?: string;
|
|
30
40
|
postun?: string;
|
|
31
41
|
};
|
|
@@ -18,22 +18,40 @@
|
|
|
18
18
|
* The caches belong to the distribution, not to us: on removal the helper may
|
|
19
19
|
* already be gone, and a maintainer script that fails during a purge leaves a
|
|
20
20
|
* package the user cannot uninstall.
|
|
21
|
+
*
|
|
22
|
+
* Keyed on the PAYLOAD, not on the settings. Each line refreshes exactly one
|
|
23
|
+
* directory, so the honest precondition is "did this package install anything
|
|
24
|
+
* into that directory" — which the payload answers and a project's file lists
|
|
25
|
+
* only approximate. They came apart in one measured case: a `kind: 'cli'`
|
|
26
|
+
* project with a `data/icons/` folder staged no icon at all (icons are an
|
|
27
|
+
* `'app'` thing in `planStage`) while `settings.iconFiles` was non-empty, so
|
|
28
|
+
* the postinst refreshed an icon cache for files that were never installed.
|
|
29
|
+
* The second reason is ADR 0024 § A2: those lists are absolute BUILD-host
|
|
30
|
+
* paths, and this code now also runs on a host that has only the staged tree.
|
|
21
31
|
*/
|
|
22
|
-
export function cacheRefreshCommands(
|
|
32
|
+
export function cacheRefreshCommands(facts, prefix) {
|
|
23
33
|
const refreshes = [];
|
|
24
|
-
if (
|
|
34
|
+
if (facts.hasDesktopEntry) {
|
|
25
35
|
refreshes.push({
|
|
26
36
|
probe: 'update-desktop-database',
|
|
27
37
|
run: `update-desktop-database -q ${prefix}/share/applications`,
|
|
28
38
|
});
|
|
29
39
|
}
|
|
30
|
-
if (
|
|
40
|
+
if (facts.hasIcons) {
|
|
31
41
|
refreshes.push({
|
|
32
42
|
probe: 'gtk-update-icon-cache',
|
|
33
43
|
run: `gtk-update-icon-cache -q -t -f ${prefix}/share/icons/hicolor`,
|
|
34
44
|
});
|
|
35
45
|
}
|
|
36
|
-
if (
|
|
46
|
+
if (facts.hasMimeTypes) {
|
|
47
|
+
// Without this the document is installed and the type still does not exist: detection runs
|
|
48
|
+
// off the compiled cache in `share/mime/`, not off the packages directory.
|
|
49
|
+
refreshes.push({
|
|
50
|
+
probe: 'update-mime-database',
|
|
51
|
+
run: `update-mime-database ${prefix}/share/mime`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (facts.hasSchemas) {
|
|
37
55
|
refreshes.push({
|
|
38
56
|
probe: 'glib-compile-schemas',
|
|
39
57
|
run: `glib-compile-schemas ${prefix}/share/glib-2.0/schemas`,
|
|
@@ -48,8 +66,8 @@ export function cacheRefreshCommands(settings, prefix) {
|
|
|
48
66
|
* and several abort paths, and on an upgrade the incoming package's `postinst`
|
|
49
67
|
* refreshes the caches anyway.
|
|
50
68
|
*/
|
|
51
|
-
export function renderDebScripts(
|
|
52
|
-
const commands = cacheRefreshCommands(
|
|
69
|
+
export function renderDebScripts(facts, prefix) {
|
|
70
|
+
const commands = cacheRefreshCommands(facts, prefix);
|
|
53
71
|
if (commands.length === 0)
|
|
54
72
|
return {};
|
|
55
73
|
const body = commands.map((line) => ` ${line}`).join('\n');
|
|
@@ -67,8 +85,8 @@ export function renderDebScripts(settings, prefix) {
|
|
|
67
85
|
* work rather than a wrong result, where guarding on `[ $1 -eq 0 ]` would be
|
|
68
86
|
* one more place to get the convention backwards.
|
|
69
87
|
*/
|
|
70
|
-
export function renderRpmScriptlets(
|
|
71
|
-
const commands = cacheRefreshCommands(
|
|
88
|
+
export function renderRpmScriptlets(facts, prefix) {
|
|
89
|
+
const commands = cacheRefreshCommands(facts, prefix);
|
|
72
90
|
if (commands.length === 0)
|
|
73
91
|
return {};
|
|
74
92
|
const body = `${commands.join('\n')}\n`;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// Precedence is uniform: CLI flag > `gjsify.ship` > `gjsify.flatpak` (metadata
|
|
10
10
|
// only) > derived from package.json. Never a yargs `default:`, which is
|
|
11
11
|
// indistinguishable from a value the user typed and would clobber the config.
|
|
12
|
+
import { validateMimeTypes } from './mime.js';
|
|
12
13
|
import { basename } from 'node:path';
|
|
13
14
|
import { DEFAULT_GJS_FLOOR } from './depends.js';
|
|
14
15
|
import { assertRelease, normaliseVersion } from './version.js';
|
|
@@ -32,6 +33,17 @@ export function resolveShipSettings(input) {
|
|
|
32
33
|
// `gjsify.flatpak` key-by-key is exactly the fallback ADR 0024 § 8 asks
|
|
33
34
|
// for. The non-metadata keys each block carries ride along unread.
|
|
34
35
|
const metadata = { ...flatpak, ...definedOnly(ship) };
|
|
36
|
+
// A type this package DEFINES is also a type it handles. Folding it into `provides.mimetypes`
|
|
37
|
+
// here means the desktop entry and the metainfo need no knowledge of `mimeTypes` at all: they
|
|
38
|
+
// already render `MimeType=` (with the `%f` field code) and `<mediatype>` from that one field.
|
|
39
|
+
// Doing it the other way — leaving the two lists independent — makes "defined but not handled"
|
|
40
|
+
// a state you can reach by omission, and that state installs cleanly and does nothing.
|
|
41
|
+
const mimeTypes = ship.mimeTypes ?? [];
|
|
42
|
+
if (mimeTypes.length > 0) {
|
|
43
|
+
validateMimeTypes(mimeTypes);
|
|
44
|
+
const handled = new Set([...(metadata.provides?.mimetypes ?? []), ...mimeTypes.map((m) => m.type)]);
|
|
45
|
+
metadata.provides = { ...metadata.provides, mimetypes: [...handled] };
|
|
46
|
+
}
|
|
35
47
|
const binaryName = ship.binaryName ?? deriveBinaryName(pkg.name);
|
|
36
48
|
const appId = ship.appId ?? flatpak.appId ?? reverseDnsOrThrow(pkg.name, binaryName);
|
|
37
49
|
const name = metadata.name ?? titleCase(binaryName);
|
|
@@ -75,6 +87,7 @@ export function resolveShipSettings(input) {
|
|
|
75
87
|
section: ship.section ?? sections.section,
|
|
76
88
|
group: ship.group ?? sections.group,
|
|
77
89
|
kind,
|
|
90
|
+
mimeTypes,
|
|
78
91
|
extraDepends: { deb: ship.depends?.deb ?? [], rpm: ship.depends?.rpm ?? [] },
|
|
79
92
|
typelibPackages: ship.typelibPackages ?? {},
|
|
80
93
|
bundlePath: discovered.bundlePath,
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { FormatDescriptor, FormatId, PackSettings, ShipSettings, StagedFile, StagedMode } from './types.js';
|
|
2
|
+
/** The one file a stage carries that is not payload. Lives at the stage ROOT. */
|
|
3
|
+
export declare const STAGE_MANIFEST_FILE = ".gjsify-ship-stage.json";
|
|
4
|
+
/**
|
|
5
|
+
* The schema this CLI writes and the only one it reads.
|
|
6
|
+
*
|
|
7
|
+
* A stage is a short-lived intermediate between two jobs of ONE run, not an
|
|
8
|
+
* archive, so there is no migration path in either direction and both
|
|
9
|
+
* mismatches are refused rather than guessed at — see {@link readStageManifest}.
|
|
10
|
+
* Bump it whenever a field's MEANING changes, not when one is added that an
|
|
11
|
+
* older reader would simply ignore; a reader that ignores a field it does not
|
|
12
|
+
* know is exactly how a mode plan silently loses an entry.
|
|
13
|
+
*/
|
|
14
|
+
export declare const STAGE_SCHEMA_VERSION = 1;
|
|
15
|
+
/**
|
|
16
|
+
* The OS whose layout `planStage` produces.
|
|
17
|
+
*
|
|
18
|
+
* A constant rather than a measurement, because it is a property of the PLAN
|
|
19
|
+
* (`bin/`, `lib/<name>/`, `share/…` is the Linux/XDG layout), not of the host
|
|
20
|
+
* that ran it — assembling is cross-platform (ADR 0024 § A1). When
|
|
21
|
+
* `gjsify ship darwin --stage` lands this becomes the positional's value, and
|
|
22
|
+
* this constant is where the compiler will point.
|
|
23
|
+
*/
|
|
24
|
+
export declare const STAGE_LAYOUT_OS = "linux";
|
|
25
|
+
/** What this stage was assembled FOR, in the repo-wide `${process.platform}-${process.arch}` spelling. */
|
|
26
|
+
export interface StageTarget {
|
|
27
|
+
os: string;
|
|
28
|
+
arch: string;
|
|
29
|
+
}
|
|
30
|
+
/** One staged path, its planned mode, and the size it had when the stage was written. */
|
|
31
|
+
export interface StageFileRecord extends StagedMode {
|
|
32
|
+
bytes: number;
|
|
33
|
+
}
|
|
34
|
+
/** One pre-rendered overlay file. Text only — see {@link writeStageManifest}. */
|
|
35
|
+
export interface StageOverlayFile extends StagedMode {
|
|
36
|
+
text: string;
|
|
37
|
+
}
|
|
38
|
+
export interface StageManifest {
|
|
39
|
+
schema: number;
|
|
40
|
+
/** The tool that assembled the stage, so a mixed-version pipeline is answerable after the fact. */
|
|
41
|
+
tool: {
|
|
42
|
+
name: string;
|
|
43
|
+
version: string;
|
|
44
|
+
};
|
|
45
|
+
target: StageTarget;
|
|
46
|
+
/** The formats phase 1 rendered an overlay closure for. */
|
|
47
|
+
formats: FormatId[];
|
|
48
|
+
/** The one build stamp every header and every rendered file shares. */
|
|
49
|
+
mtime: number;
|
|
50
|
+
/** GI namespaces the bundle imports, `Ns-Version` where the specifier pins one. */
|
|
51
|
+
namespaces: string[];
|
|
52
|
+
settings: PackSettings;
|
|
53
|
+
staged: StageFileRecord[];
|
|
54
|
+
overlay: Partial<Record<FormatId, StageOverlayFile[]>>;
|
|
55
|
+
}
|
|
56
|
+
export interface StageManifestInput {
|
|
57
|
+
/** Absolute path of the stage root, already written. */
|
|
58
|
+
stageDir: string;
|
|
59
|
+
settings: ShipSettings;
|
|
60
|
+
formats: readonly FormatDescriptor[];
|
|
61
|
+
mtime: number;
|
|
62
|
+
namespaces: readonly string[];
|
|
63
|
+
staged: readonly StagedFile[];
|
|
64
|
+
/** Per-format overlay, as `planOverlay` produced it. */
|
|
65
|
+
overlay: ReadonlyMap<FormatId, readonly StagedFile[]>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The half of {@link ShipSettings} that crosses the host boundary.
|
|
69
|
+
*
|
|
70
|
+
* Written out field by field rather than spread: a spread would carry
|
|
71
|
+
* `projectDir`, `bundlePath`, `bundleDir`, `iconFiles`, `schemaFiles`,
|
|
72
|
+
* `extraFiles` and `licenseFile` — seven absolute paths from the assembling
|
|
73
|
+
* host — into a file whose whole purpose is to be read somewhere else. The
|
|
74
|
+
* explicit list is also what makes adding a field a decision: see the comment
|
|
75
|
+
* on {@link PackSettings} for what belongs on each side.
|
|
76
|
+
*/
|
|
77
|
+
export declare function toPackSettings(settings: ShipSettings): PackSettings;
|
|
78
|
+
/** Absolute path of a stage's manifest. */
|
|
79
|
+
export declare function stageManifestPath(stageDir: string): string;
|
|
80
|
+
/** `linux-x64`. One spelling repo-wide (root AGENTS.md § Runtime & platform model). */
|
|
81
|
+
export declare function formatTarget(target: StageTarget): string;
|
|
82
|
+
/**
|
|
83
|
+
* Write the manifest at the stage root, AFTER the tree itself.
|
|
84
|
+
*
|
|
85
|
+
* `bytes` is stat'ed off the written tree rather than taken from the plan: the
|
|
86
|
+
* plan holds a source path or a string, and what the packing host will compare
|
|
87
|
+
* against is what actually landed on disk here.
|
|
88
|
+
*/
|
|
89
|
+
export declare function writeStageManifest(input: StageManifestInput): StageManifest;
|
|
90
|
+
/**
|
|
91
|
+
* Read and validate a stage's manifest.
|
|
92
|
+
*
|
|
93
|
+
* Every field is checked, because this file is the only thing standing between
|
|
94
|
+
* "a directory someone pointed us at" and a signed-looking `.deb`. A missing
|
|
95
|
+
* `staged` entry would reach `readStage` as a mode nobody planned; a `mode` that
|
|
96
|
+
* arrived as a string would reach `chmod` as `NaN`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function readStageManifest(stageDir: string): StageManifest;
|
|
99
|
+
/** The overlay for one format, in the shape `writeStage` takes. */
|
|
100
|
+
export declare function overlayFiles(manifest: StageManifest, format: FormatId): StagedFile[];
|
|
101
|
+
/**
|
|
102
|
+
* Refuse a stage that is not the one this job was told to pack.
|
|
103
|
+
*
|
|
104
|
+
* WHAT THIS IS: a matrix-leg mix-up check. It catches a finish job that
|
|
105
|
+
* downloaded the wrong artifact — the leg that forgot to route — and nothing
|
|
106
|
+
* more. It is opt-in (no flag, no check) and it compares two strings the same
|
|
107
|
+
* pipeline wrote one job apart, so treat a green run as "this is the stage I
|
|
108
|
+
* asked for", never as "this stage is correctly labelled".
|
|
109
|
+
*
|
|
110
|
+
* WHAT THIS IS NOT: the guard its precedent
|
|
111
|
+
* (`packages/node-gi/scripts/verify-bundle-manifest.mjs --expect-host-target`)
|
|
112
|
+
* provides. That script compares a bundle to `process.platform`/`process.arch`
|
|
113
|
+
* because the runner BUILT it, so the machine is the right second opinion.
|
|
114
|
+
* Here the stage arrives from another host by design, so a host comparison
|
|
115
|
+
* would refuse a legal use — measured: an `arm64`-labelled stage packs on this
|
|
116
|
+
* x64 host at exit 0 with correct bytes, because both packers are pure JS.
|
|
117
|
+
*
|
|
118
|
+
* The class that precedent guards — a label and the bytes under it disagreeing
|
|
119
|
+
* — is NOT guarded here, and `target.arch` cannot guard it: it is defaulted
|
|
120
|
+
* from `process.arch` only when `--arch` is absent, so the moment an operator
|
|
121
|
+
* says which leg they are on, `target.arch` is that operator's claim and
|
|
122
|
+
* nothing compares it to a machine or to a byte. Measured BEFORE
|
|
123
|
+
* {@link assertPayloadMatchesArch} existed: a stage carrying an x86-64 `.so`,
|
|
124
|
+
* assembled `--stage --arch arm64` and packed `--expect-target linux-arm64`,
|
|
125
|
+
* exited 0 and produced an `aarch64` rpm whose only `.so` was `e_machine=0x3e`.
|
|
126
|
+
* The same command in this tree now exits 1 and writes nothing — kept in the
|
|
127
|
+
* past tense because it is the reason the guard below it exists, not a
|
|
128
|
+
* description of what this function does.
|
|
129
|
+
*
|
|
130
|
+
* That class is guarded one layer down instead, where the oracle is free and
|
|
131
|
+
* cannot be wrong about a machine it never saw: {@link assertPayloadMatchesArch}
|
|
132
|
+
* in `payload.ts` reads ELF `e_machine` / Mach-O `cputype` out of the payload
|
|
133
|
+
* and refuses a label the bytes contradict. Payload-versus-label goes red on
|
|
134
|
+
* the mislabelled stage AND stays green on the legitimate cross-host pack;
|
|
135
|
+
* host-versus-label can only do one of the two.
|
|
136
|
+
*/
|
|
137
|
+
export declare function assertExpectedTarget(manifest: StageManifest, expected: string | undefined): void;
|
|
138
|
+
/** Refuse a format the stage carries no overlay closure for. */
|
|
139
|
+
export declare function assertFormatsStaged(manifest: StageManifest, formats: readonly FormatDescriptor[]): void;
|
|
140
|
+
/**
|
|
141
|
+
* The build stamp the packing host uses: the stage's, always.
|
|
142
|
+
*
|
|
143
|
+
* A `SOURCE_DATE_EPOCH` set only in the finish job would silently produce a
|
|
144
|
+
* different artifact than the same stage packed on the assembling host, which
|
|
145
|
+
* is the one property the two-phase split has to keep. It is a warning rather
|
|
146
|
+
* than a refusal because the honest fix ("set it in both jobs or in neither")
|
|
147
|
+
* is the caller's, and refusing would break a runner that exports it globally.
|
|
148
|
+
*/
|
|
149
|
+
export declare function resolveStageMtime(manifest: StageManifest, env?: Record<string, string | undefined>): {
|
|
150
|
+
mtime: number;
|
|
151
|
+
warnings: string[];
|
|
152
|
+
};
|
|
153
|
+
/** One actionable line when the two phases ran different gjsify versions. */
|
|
154
|
+
export declare function warnOnToolSkew(manifest: StageManifest): string[];
|