@gjsify/cli 0.42.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 +444 -415
- package/lib/actions/build.d.ts +6 -0
- package/lib/actions/build.js +66 -9
- 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.d.ts +2 -0
- package/lib/commands/ship.js +358 -104
- package/lib/config.js +25 -0
- package/lib/types/cli-build-options.d.ts +11 -0
- package/lib/types/config-data.d.ts +106 -2
- package/lib/utils/flatpak-runtime.d.ts +41 -0
- package/lib/utils/flatpak-runtime.js +54 -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 +13 -13
- package/lib/utils/ship/depends.js +25 -3
- package/lib/utils/ship/discover.js +72 -6
- 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 +152 -5
- 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 +20 -0
- package/lib/utils/ship/stage-manifest.d.ts +163 -0
- package/lib/utils/ship/stage-manifest.js +445 -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 +203 -27
- package/lib/utils/ship/types.js +5 -0
- package/package.json +19 -19
|
@@ -16,6 +16,12 @@ import { statSync } from 'node:fs';
|
|
|
16
16
|
* what `rpm -qi` then shows a user, and an artifact that looks broken is a
|
|
17
17
|
* support question. The mtime keeps the property that matters (pack the same
|
|
18
18
|
* build twice, get the same bytes) while saying something true.
|
|
19
|
+
*
|
|
20
|
+
* Only the ASSEMBLING host runs this. A stage records the answer it got
|
|
21
|
+
* (`.gjsify-ship-stage.json` → `mtime`) and the packing host reuses it: an
|
|
22
|
+
* artifact upload does not carry mtimes, so re-stat'ing the stage there would
|
|
23
|
+
* stamp every header with "whenever the download finished" and quietly destroy
|
|
24
|
+
* the reproducibility this function exists to protect.
|
|
19
25
|
*/
|
|
20
26
|
export function buildTimestamp(bundlePath, env = process.env) {
|
|
21
27
|
const raw = env.SOURCE_DATE_EPOCH;
|
|
@@ -28,6 +34,72 @@ export function buildTimestamp(bundlePath, env = process.env) {
|
|
|
28
34
|
}
|
|
29
35
|
return Math.floor(statSync(bundlePath).mtimeMs / 1000);
|
|
30
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Read {@link PayloadFacts} off a payload or off a plan.
|
|
39
|
+
*
|
|
40
|
+
* Takes anything with a `path`, so the assembling phase can ask the same
|
|
41
|
+
* question of the PLAN (before the tree is read back) that the packing phase
|
|
42
|
+
* asks of the payload. One function, so the two phases cannot disagree about
|
|
43
|
+
* whether a package installs a schema.
|
|
44
|
+
*/
|
|
45
|
+
export function readPayloadFacts(entries) {
|
|
46
|
+
const paths = entries.map((entry) => entry.path);
|
|
47
|
+
return {
|
|
48
|
+
hasDesktopEntry: paths.some((path) => path.startsWith('share/applications/') && path.endsWith('.desktop')),
|
|
49
|
+
hasIcons: paths.some((path) => path.startsWith('share/icons/hicolor/')),
|
|
50
|
+
hasSchemas: paths.some((path) => path.startsWith('share/glib-2.0/schemas/') && path.endsWith('.gschema.xml')),
|
|
51
|
+
hasMimeTypes: paths.some((path) => path.startsWith('share/mime/packages/') && path.endsWith('.xml')),
|
|
52
|
+
// Anywhere in the payload, not only `lib/<name>/gi/`: `gjsify.ship.extraFiles` can place
|
|
53
|
+
// one elsewhere, and a typelib the package carries is a typelib the package must not also
|
|
54
|
+
// declare a distro dependency for, wherever it sits.
|
|
55
|
+
bundledTypelibs: paths.filter((path) => path.endsWith('.typelib')),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The interpreters the payload's own executables need, read off their shebangs.
|
|
60
|
+
*
|
|
61
|
+
* An interpreter is a dependency like any other, and rpm expects it declared:
|
|
62
|
+
* `rpmbuild`'s file-based generator emits one `Requires` per executable
|
|
63
|
+
* shebang, with the `RPMSENSE_FIND_REQUIRES` sense that says "derived, not
|
|
64
|
+
* declared". Measured on Fedora 44 against a package whose only file is a
|
|
65
|
+
* `#!/bin/sh` script: `rpm -qp --requires` → `/usr/bin/sh 16384`.
|
|
66
|
+
*
|
|
67
|
+
* The LITERAL path, not a resolved one. `rpmbuild` prints `/usr/bin/sh` there
|
|
68
|
+
* because it resolved `/bin` through the symlink of the usrmerged host it ran
|
|
69
|
+
* on; this writer has no target host to resolve against (ADR 0024 § A1 — the
|
|
70
|
+
* packers are pure JavaScript and run anywhere), and `/bin/sh` is satisfied on
|
|
71
|
+
* both layouts: measured on Fedora 44, `rpm -q --whatprovides /bin/sh` and
|
|
72
|
+
* `/usr/bin/sh` both answer `bash`. It is also the spelling the scriptlet
|
|
73
|
+
* requirements already use.
|
|
74
|
+
*
|
|
75
|
+
* EXECUTABLE files only, which is the same rule `rpmbuild` applies. A GJS
|
|
76
|
+
* bundle staged 0644 carries `#!/usr/bin/env -S gjs -m` for the days it is run
|
|
77
|
+
* directly, but nothing in the installed package executes it as a program — the
|
|
78
|
+
* launcher `exec`s `gjs` with it as an argument — so declaring `/usr/bin/env`
|
|
79
|
+
* for it would be a dependency on a path this package never uses.
|
|
80
|
+
*/
|
|
81
|
+
export function readShebangInterpreters(payload) {
|
|
82
|
+
const found = new Set();
|
|
83
|
+
for (const entry of payload) {
|
|
84
|
+
if ((entry.mode & 0o111) === 0)
|
|
85
|
+
continue;
|
|
86
|
+
const interpreter = readShebang(entry.data);
|
|
87
|
+
if (interpreter !== null)
|
|
88
|
+
found.add(interpreter);
|
|
89
|
+
}
|
|
90
|
+
return [...found].sort();
|
|
91
|
+
}
|
|
92
|
+
/** The absolute interpreter path of a `#!` line, or `null` when there is none to read. */
|
|
93
|
+
function readShebang(data) {
|
|
94
|
+
if (data[0] !== 0x23 || data[1] !== 0x21)
|
|
95
|
+
return null; // `#!`
|
|
96
|
+
// A shebang is one LINE; reading further would let a long file's contents
|
|
97
|
+
// decide how much work this does. 256 bytes is above every real one and is
|
|
98
|
+
// what Linux itself truncates at (BINPRM_BUF_SIZE).
|
|
99
|
+
const line = new TextDecoder().decode(data.subarray(2, Math.min(data.byteLength, 256))).split('\n')[0] ?? '';
|
|
100
|
+
const interpreter = line.trim().split(/\s+/)[0] ?? '';
|
|
101
|
+
return interpreter.startsWith('/') ? interpreter : null;
|
|
102
|
+
}
|
|
31
103
|
/**
|
|
32
104
|
* Does the payload contain anything architecture-specific?
|
|
33
105
|
*
|
|
@@ -44,6 +116,23 @@ export function isArchIndependent(payload) {
|
|
|
44
116
|
// values this repository can actually produce are listed; an unknown one reads
|
|
45
117
|
// as "cannot tell" rather than as a mismatch, because refusing an artifact over
|
|
46
118
|
// a machine constant nobody here emits would be a guess wearing a gate's clothes.
|
|
119
|
+
// The machine values this project actually ships packages for — no more. A value
|
|
120
|
+
// missing here makes `readBinaryArch` return null, and null is SILENT, so an
|
|
121
|
+
// absent row costs nothing but a check that does not fire.
|
|
122
|
+
//
|
|
123
|
+
// `EM_MIPS` (0x08) is absent for that reason and no other. An earlier version of
|
|
124
|
+
// this comment claimed it was absent because one value covers `mips` and
|
|
125
|
+
// `mipsel` and the row would have to guess; that was wrong three times over, and
|
|
126
|
+
// is corrected here rather than deleted because it is the kind of reasoning that
|
|
127
|
+
// gets re-derived: (1) `mipsel` IS little-endian MIPS, so the discriminator is
|
|
128
|
+
// `EI_DATA`, which `readBinaryArch` reads four lines below; (2) it could not
|
|
129
|
+
// "refuse a correct pack" either way, because `formats.ts` has no `mips` row in
|
|
130
|
+
// `DEBIAN_ARCH`/`RPM_ARCH`, so `archName` throws before any label is written;
|
|
131
|
+
// and (3) the principle it invoked is already broken one row down — `0x16` maps
|
|
132
|
+
// to `s390x`, but `EM_S390` is emitted by 31-bit `s390` too, and its
|
|
133
|
+
// discriminator is `EI_CLASS` at offset 4, which this function does NOT read.
|
|
134
|
+
// That row is the ambiguous one. Unreachable today (nothing here builds s390),
|
|
135
|
+
// but it is the row to fix first if this table ever grows.
|
|
47
136
|
const ELF_MACHINE_TO_ARCH = {
|
|
48
137
|
0x03: 'ia32',
|
|
49
138
|
0x15: 'ppc64',
|
|
@@ -121,7 +210,8 @@ export function assertPayloadMatchesArch(payload, arch) {
|
|
|
121
210
|
'anything.\n' +
|
|
122
211
|
` A package labelled ${arch} installs on ${arch} and then fails to load. Build the payload ` +
|
|
123
212
|
`for ${arch}\n` +
|
|
124
|
-
' (its own prebuild), or
|
|
213
|
+
' (its own prebuild), or assemble the stage without `--arch` and label the payload you ' +
|
|
214
|
+
'actually have.');
|
|
125
215
|
}
|
|
126
216
|
}
|
|
127
217
|
/** ELF, Mach-O (both endiannesses, both widths, and a fat archive) or PE. */
|
package/lib/utils/ship/plan.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FormatDescriptor, ShipSettings, StagedFile } from './types.js';
|
|
1
|
+
import type { FormatDescriptor, FormatId, ShipSettings, StagedFile } from './types.js';
|
|
2
2
|
/** Everything the planner needs that it cannot derive from the settings alone. */
|
|
3
3
|
export interface StageInputs {
|
|
4
4
|
/** Paths inside `settings.bundleDir`, relative and POSIX-separated. */
|
|
@@ -24,8 +24,37 @@ export declare function planStage(settings: ShipSettings, inputs: StageInputs):
|
|
|
24
24
|
* The per-format additions. Only the licence differs today, and it differs in
|
|
25
25
|
* BOTH place and shape: Debian policy wants a machine-readable `copyright` in
|
|
26
26
|
* `share/doc/<pkg>/`, RPM wants the licence text under `share/licenses/<pkg>/`.
|
|
27
|
+
*
|
|
28
|
+
* A project with no licence file is REFUSED here rather than packaged without
|
|
29
|
+
* one. Debian Policy § 12.5 makes the copyright file mandatory and lintian
|
|
30
|
+
* raises `no-copyright-file` as an error, but nothing between this function and
|
|
31
|
+
* the installed package says so: an empty overlay produced a `.deb` whose
|
|
32
|
+
* control file `dpkg-deb --info` prints cleanly, that `dpkg -i` installs, and
|
|
33
|
+
* that is simply unlicensed. That is what gjsify's own package was, every
|
|
34
|
+
* release of it, until the first CI leg that read one with a real `dpkg` said
|
|
35
|
+
* so on its eighth assertion.
|
|
36
|
+
*
|
|
37
|
+
* It throws on the host that can act on it. This runs during `--stage` as well
|
|
38
|
+
* as during a one-shot `ship`, so the refusal lands where the project — and the
|
|
39
|
+
* missing `LICENSE` — actually is; {@link assertOverlayIsLicensed} repeats it at
|
|
40
|
+
* pack time for a stage that arrived from somewhere else.
|
|
27
41
|
*/
|
|
28
42
|
export declare function planOverlay(settings: ShipSettings, format: FormatDescriptor, inputs: StageInputs): StagedFile[];
|
|
43
|
+
/**
|
|
44
|
+
* The same refusal, at pack time.
|
|
45
|
+
*
|
|
46
|
+
* `planOverlay` cannot cover `--from-stage`: that path never plans anything, it
|
|
47
|
+
* rehydrates the closure the assembling host wrote. A manifest whose
|
|
48
|
+
* `overlay.deb` is `[]` passes every structural check in `readStageManifest`
|
|
49
|
+
* — the key is present, the value is an array of the right shape — and the
|
|
50
|
+
* silent unlicensed package comes back. The stage manifest's own header lists
|
|
51
|
+
* "no `overlay`" among the omissions that fail at exit 0 with an artifact that
|
|
52
|
+
* installs; this is where that stops being a comment.
|
|
53
|
+
*
|
|
54
|
+
* Cheap enough to run on both paths, so it does, and a stage written by this
|
|
55
|
+
* version can no longer reach it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function assertOverlayIsLicensed(formatId: FormatId, binaryName: string, overlay: readonly StagedFile[]): void;
|
|
29
58
|
/**
|
|
30
59
|
* The hicolor subdirectory for an icon: `scalable` for SVG, otherwise the
|
|
31
60
|
* pixel size read from a `<n>x<n>` path component or a trailing number in the
|
package/lib/utils/ship/plan.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// case (including the ones that must be REFUSED) is unit-testable. Same split
|
|
3
3
|
// as `utils/copy-targets.ts`, and for the same reason: a stager that discovers
|
|
4
4
|
// its own inputs can only be tested by building a real project.
|
|
5
|
+
import { renderMimePackage } from './mime.js';
|
|
5
6
|
import { basename, extname, posix } from 'node:path';
|
|
6
7
|
/**
|
|
7
8
|
* Build the prefix-relative payload.
|
|
@@ -65,6 +66,16 @@ export function planStage(settings, inputs) {
|
|
|
65
66
|
source: { kind: 'file', path: file },
|
|
66
67
|
});
|
|
67
68
|
}
|
|
69
|
+
// A shared-mime-info document, when the project DEFINES a type rather than only handling one.
|
|
70
|
+
// `share/mime/packages/` is shared between packages, so the app id is the filename for the same
|
|
71
|
+
// reason it is for the GSettings schema: a generic name collides with another package's file.
|
|
72
|
+
if (settings.mimeTypes.length > 0) {
|
|
73
|
+
files.push({
|
|
74
|
+
path: `share/mime/packages/${settings.appId}.xml`,
|
|
75
|
+
mode: 0o644,
|
|
76
|
+
source: { kind: 'text', text: renderMimePackage(settings.mimeTypes) },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
68
79
|
// Compiled gettext catalogues, layout preserved: `share/locale/<lang>/LC_MESSAGES/<domain>.mo`
|
|
69
80
|
// is where `bindtextdomain` looks, and nowhere else.
|
|
70
81
|
for (const locale of settings.localeFiles) {
|
|
@@ -83,15 +94,56 @@ export function planStage(settings, inputs) {
|
|
|
83
94
|
* The per-format additions. Only the licence differs today, and it differs in
|
|
84
95
|
* BOTH place and shape: Debian policy wants a machine-readable `copyright` in
|
|
85
96
|
* `share/doc/<pkg>/`, RPM wants the licence text under `share/licenses/<pkg>/`.
|
|
97
|
+
*
|
|
98
|
+
* A project with no licence file is REFUSED here rather than packaged without
|
|
99
|
+
* one. Debian Policy § 12.5 makes the copyright file mandatory and lintian
|
|
100
|
+
* raises `no-copyright-file` as an error, but nothing between this function and
|
|
101
|
+
* the installed package says so: an empty overlay produced a `.deb` whose
|
|
102
|
+
* control file `dpkg-deb --info` prints cleanly, that `dpkg -i` installs, and
|
|
103
|
+
* that is simply unlicensed. That is what gjsify's own package was, every
|
|
104
|
+
* release of it, until the first CI leg that read one with a real `dpkg` said
|
|
105
|
+
* so on its eighth assertion.
|
|
106
|
+
*
|
|
107
|
+
* It throws on the host that can act on it. This runs during `--stage` as well
|
|
108
|
+
* as during a one-shot `ship`, so the refusal lands where the project — and the
|
|
109
|
+
* missing `LICENSE` — actually is; {@link assertOverlayIsLicensed} repeats it at
|
|
110
|
+
* pack time for a stage that arrived from somewhere else.
|
|
86
111
|
*/
|
|
87
112
|
export function planOverlay(settings, format, inputs) {
|
|
88
|
-
if (inputs.licenseText === undefined)
|
|
89
|
-
|
|
113
|
+
if (inputs.licenseText === undefined) {
|
|
114
|
+
throw new Error(`gjsify ship: no licence file found, so the ${format.id} package would ship no ` +
|
|
115
|
+
`${format.licenseDest(settings.binaryName)}. Debian Policy § 12.5 requires it and lintian ` +
|
|
116
|
+
'errors without it, and neither dpkg nor rpm will tell you it is missing. Add a LICENSE file ' +
|
|
117
|
+
`to ${settings.projectDir} or to the root of its repository, or point ` +
|
|
118
|
+
'`gjsify.ship.licenseFile` at the one this package ships under.');
|
|
119
|
+
}
|
|
90
120
|
const text = format.licenseKind === 'debian-copyright'
|
|
91
121
|
? renderDebianCopyright(settings, inputs.licenseText)
|
|
92
122
|
: inputs.licenseText;
|
|
93
123
|
return [{ path: format.licenseDest(settings.binaryName), mode: 0o644, source: { kind: 'text', text } }];
|
|
94
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* The same refusal, at pack time.
|
|
127
|
+
*
|
|
128
|
+
* `planOverlay` cannot cover `--from-stage`: that path never plans anything, it
|
|
129
|
+
* rehydrates the closure the assembling host wrote. A manifest whose
|
|
130
|
+
* `overlay.deb` is `[]` passes every structural check in `readStageManifest`
|
|
131
|
+
* — the key is present, the value is an array of the right shape — and the
|
|
132
|
+
* silent unlicensed package comes back. The stage manifest's own header lists
|
|
133
|
+
* "no `overlay`" among the omissions that fail at exit 0 with an artifact that
|
|
134
|
+
* installs; this is where that stops being a comment.
|
|
135
|
+
*
|
|
136
|
+
* Cheap enough to run on both paths, so it does, and a stage written by this
|
|
137
|
+
* version can no longer reach it.
|
|
138
|
+
*/
|
|
139
|
+
export function assertOverlayIsLicensed(formatId, binaryName, overlay) {
|
|
140
|
+
if (overlay.length > 0)
|
|
141
|
+
return;
|
|
142
|
+
throw new Error(`gjsify ship: this stage carries no ${formatId} licence overlay, so the package would ship no ` +
|
|
143
|
+
`licence file for ${binaryName} — Debian Policy § 12.5 requires one and lintian errors without ` +
|
|
144
|
+
'it. The fix is on the assembling host, not this one: give that project a LICENSE (or set ' +
|
|
145
|
+
'`gjsify.ship.licenseFile`) and re-run the `--stage` phase.');
|
|
146
|
+
}
|
|
95
147
|
/**
|
|
96
148
|
* Debian machine-readable copyright, format 1.0. The licence text is indented
|
|
97
149
|
* by one space and its blank lines become ` .` — a bare blank line ends the
|
package/lib/utils/ship/rpm.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
1
|
+
import { type PayloadEntry } from './payload.js';
|
|
2
|
+
import type { PackSettings } from './types.js';
|
|
3
3
|
export interface RpmInputs {
|
|
4
|
-
settings:
|
|
4
|
+
settings: PackSettings;
|
|
5
5
|
payload: readonly PayloadEntry[];
|
|
6
6
|
prefix: string;
|
|
7
7
|
depends: readonly string[];
|
package/lib/utils/ship/rpm.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createHash } from 'node:crypto';
|
|
|
15
15
|
import { concatBytes } from './bytes.js';
|
|
16
16
|
import { createCpioArchive, S_IFDIR, S_IFREG } from './cpio.js';
|
|
17
17
|
import { parseDepend } from './depends.js';
|
|
18
|
+
import { readPayloadFacts, readShebangInterpreters } from './payload.js';
|
|
18
19
|
import { buildRpmHeader, buildRpmLead, padToEight, RpmType, RPM_TAG_HEADERIMMUTABLE, RPM_TAG_HEADERSIGNATURES, } from './rpm-header.js';
|
|
19
20
|
import { gzipDeterministic } from './gzip.js';
|
|
20
21
|
import { renderRpmScriptlets } from './scripts.js';
|
|
@@ -80,6 +81,9 @@ const SENSE = {
|
|
|
80
81
|
INTERP: 1 << 8,
|
|
81
82
|
SCRIPT_POST: 1 << 10,
|
|
82
83
|
SCRIPT_POSTUN: 1 << 12,
|
|
84
|
+
// "This one was derived from the payload, not declared by the packager" —
|
|
85
|
+
// the sense `rpmbuild`'s own dependency generator uses (measured: 16384).
|
|
86
|
+
FIND_REQUIRES: 1 << 14,
|
|
83
87
|
RPMLIB: 1 << 24,
|
|
84
88
|
};
|
|
85
89
|
/** SHA-256 in rpm's digest-algorithm numbering (PGP hash algorithm ids). */
|
|
@@ -205,8 +209,9 @@ function mainHeaderEntries(inputs, files, cpio, compressed) {
|
|
|
205
209
|
dirIndexes.push(index);
|
|
206
210
|
baseNames.push(file.path.slice(slash + 1));
|
|
207
211
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
212
|
+
// From the PAYLOAD, not the settings — see the same call in `deb.ts`.
|
|
213
|
+
const scripts = renderRpmScriptlets(readPayloadFacts(inputs.payload), inputs.prefix);
|
|
214
|
+
const requires = buildRequires(inputs.depends, scripts, readShebangInterpreters(inputs.payload));
|
|
210
215
|
const entries = [
|
|
211
216
|
{ tag: TAG.HEADERI18NTABLE, type: RpmType.STRING_ARRAY, value: ['C'] },
|
|
212
217
|
{ tag: TAG.NAME, type: RpmType.STRING, value: settings.binaryName },
|
|
@@ -325,8 +330,15 @@ function assertParallelFileArrays(entries, fileCount) {
|
|
|
325
330
|
* A scriptlet's interpreter is a dependency like any other and rpm expects it
|
|
326
331
|
* declared with the matching `SCRIPT_*` sense bit — without it, a system
|
|
327
332
|
* without `/bin/sh` installs the package and then fails inside `%post`.
|
|
333
|
+
*
|
|
334
|
+
* The payload's own executables need the same treatment, and until the first
|
|
335
|
+
* real `rpm -qp --requires` ran against this writer nothing here said so: every
|
|
336
|
+
* `.rpm` it produced installed a `#!/bin/sh` launcher and declared no shell.
|
|
337
|
+
* The two senses are not redundant and rpm emits both — a package can need a
|
|
338
|
+
* shell to CONFIGURE (`Requires(post)`) and to RUN (a plain requirement), and
|
|
339
|
+
* dropping either is a different broken system.
|
|
328
340
|
*/
|
|
329
|
-
function buildRequires(depends, scripts) {
|
|
341
|
+
function buildRequires(depends, scripts, interpreters) {
|
|
330
342
|
const names = [];
|
|
331
343
|
const flags = [];
|
|
332
344
|
const versions = [];
|
|
@@ -346,6 +358,11 @@ function buildRequires(depends, scripts) {
|
|
|
346
358
|
flags.push(SENSE.INTERP | SENSE.SCRIPT_POSTUN);
|
|
347
359
|
versions.push('');
|
|
348
360
|
}
|
|
361
|
+
for (const interpreter of interpreters) {
|
|
362
|
+
names.push(interpreter);
|
|
363
|
+
flags.push(SENSE.FIND_REQUIRES);
|
|
364
|
+
versions.push('');
|
|
365
|
+
}
|
|
349
366
|
for (const [name, version] of RPMLIB_REQUIRES) {
|
|
350
367
|
names.push(name);
|
|
351
368
|
flags.push(SENSE.RPMLIB | SENSE.LESS | SENSE.EQUAL);
|
|
@@ -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,8 +9,10 @@
|
|
|
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';
|
|
15
|
+
import { resolveShipFlatpakSettings } from './flatpak-config.js';
|
|
14
16
|
import { assertRelease, normaliseVersion } from './version.js';
|
|
15
17
|
/** Freedesktop main category → deb `Section:` / rpm `Group:`. First match wins. */
|
|
16
18
|
const CATEGORY_SECTIONS = [
|
|
@@ -32,6 +34,17 @@ export function resolveShipSettings(input) {
|
|
|
32
34
|
// `gjsify.flatpak` key-by-key is exactly the fallback ADR 0024 § 8 asks
|
|
33
35
|
// for. The non-metadata keys each block carries ride along unread.
|
|
34
36
|
const metadata = { ...flatpak, ...definedOnly(ship) };
|
|
37
|
+
// A type this package DEFINES is also a type it handles. Folding it into `provides.mimetypes`
|
|
38
|
+
// here means the desktop entry and the metainfo need no knowledge of `mimeTypes` at all: they
|
|
39
|
+
// already render `MimeType=` (with the `%f` field code) and `<mediatype>` from that one field.
|
|
40
|
+
// Doing it the other way — leaving the two lists independent — makes "defined but not handled"
|
|
41
|
+
// a state you can reach by omission, and that state installs cleanly and does nothing.
|
|
42
|
+
const mimeTypes = ship.mimeTypes ?? [];
|
|
43
|
+
if (mimeTypes.length > 0) {
|
|
44
|
+
validateMimeTypes(mimeTypes);
|
|
45
|
+
const handled = new Set([...(metadata.provides?.mimetypes ?? []), ...mimeTypes.map((m) => m.type)]);
|
|
46
|
+
metadata.provides = { ...metadata.provides, mimetypes: [...handled] };
|
|
47
|
+
}
|
|
35
48
|
const binaryName = ship.binaryName ?? deriveBinaryName(pkg.name);
|
|
36
49
|
const appId = ship.appId ?? flatpak.appId ?? reverseDnsOrThrow(pkg.name, binaryName);
|
|
37
50
|
const name = metadata.name ?? titleCase(binaryName);
|
|
@@ -59,6 +72,11 @@ export function resolveShipSettings(input) {
|
|
|
59
72
|
warnings.push(`gjsify ship: no icon found — the installed app will show a placeholder in menus and app stores. ` +
|
|
60
73
|
'Point `gjsify.ship.icon` at an SVG (or a directory of sized PNGs).');
|
|
61
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);
|
|
62
80
|
const settings = {
|
|
63
81
|
projectDir: input.projectDir,
|
|
64
82
|
appId,
|
|
@@ -75,6 +93,7 @@ export function resolveShipSettings(input) {
|
|
|
75
93
|
section: ship.section ?? sections.section,
|
|
76
94
|
group: ship.group ?? sections.group,
|
|
77
95
|
kind,
|
|
96
|
+
mimeTypes,
|
|
78
97
|
extraDepends: { deb: ship.depends?.deb ?? [], rpm: ship.depends?.rpm ?? [] },
|
|
79
98
|
typelibPackages: ship.typelibPackages ?? {},
|
|
80
99
|
bundlePath: discovered.bundlePath,
|
|
@@ -88,6 +107,7 @@ export function resolveShipSettings(input) {
|
|
|
88
107
|
outDir: input.cli.outDir ?? ship.outDir ?? 'ship',
|
|
89
108
|
arch: input.cli.arch ?? process.arch,
|
|
90
109
|
minGjsVersion: ship.minGjsVersion ?? DEFAULT_GJS_FLOOR,
|
|
110
|
+
flatpak: flatpakSettings.settings,
|
|
91
111
|
};
|
|
92
112
|
return { settings, metadata, warnings };
|
|
93
113
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
* 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.
|
|
19
|
+
*/
|
|
20
|
+
export declare const STAGE_SCHEMA_VERSION = 2;
|
|
21
|
+
/**
|
|
22
|
+
* The OS whose layout `planStage` produces.
|
|
23
|
+
*
|
|
24
|
+
* A constant rather than a measurement, because it is a property of the PLAN
|
|
25
|
+
* (`bin/`, `lib/<name>/`, `share/…` is the Linux/XDG layout), not of the host
|
|
26
|
+
* that ran it — assembling is cross-platform (ADR 0024 § A1). When
|
|
27
|
+
* `gjsify ship darwin --stage` lands this becomes the positional's value, and
|
|
28
|
+
* this constant is where the compiler will point.
|
|
29
|
+
*/
|
|
30
|
+
export declare const STAGE_LAYOUT_OS = "linux";
|
|
31
|
+
/** What this stage was assembled FOR, in the repo-wide `${process.platform}-${process.arch}` spelling. */
|
|
32
|
+
export interface StageTarget {
|
|
33
|
+
os: string;
|
|
34
|
+
arch: string;
|
|
35
|
+
}
|
|
36
|
+
/** One staged path, its planned mode, and the size it had when the stage was written. */
|
|
37
|
+
export interface StageFileRecord extends StagedMode {
|
|
38
|
+
bytes: number;
|
|
39
|
+
}
|
|
40
|
+
/** One pre-rendered overlay file. Text only — see {@link writeStageManifest}. */
|
|
41
|
+
export interface StageOverlayFile extends StagedMode {
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
export interface StageManifest {
|
|
45
|
+
schema: number;
|
|
46
|
+
/** The tool that assembled the stage, so a mixed-version pipeline is answerable after the fact. */
|
|
47
|
+
tool: {
|
|
48
|
+
name: string;
|
|
49
|
+
version: string;
|
|
50
|
+
};
|
|
51
|
+
target: StageTarget;
|
|
52
|
+
/** The formats phase 1 rendered an overlay closure for. */
|
|
53
|
+
formats: FormatId[];
|
|
54
|
+
/** The one build stamp every header and every rendered file shares. */
|
|
55
|
+
mtime: number;
|
|
56
|
+
/** GI namespaces the bundle imports, `Ns-Version` where the specifier pins one. */
|
|
57
|
+
namespaces: string[];
|
|
58
|
+
settings: PackSettings;
|
|
59
|
+
staged: StageFileRecord[];
|
|
60
|
+
overlay: Partial<Record<FormatId, StageOverlayFile[]>>;
|
|
61
|
+
}
|
|
62
|
+
export interface StageManifestInput {
|
|
63
|
+
/** Absolute path of the stage root, already written. */
|
|
64
|
+
stageDir: string;
|
|
65
|
+
settings: ShipSettings;
|
|
66
|
+
formats: readonly FormatDescriptor[];
|
|
67
|
+
mtime: number;
|
|
68
|
+
namespaces: readonly string[];
|
|
69
|
+
staged: readonly StagedFile[];
|
|
70
|
+
/** Per-format overlay, as `planOverlay` produced it. */
|
|
71
|
+
overlay: ReadonlyMap<FormatId, readonly StagedFile[]>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The half of {@link ShipSettings} that crosses the host boundary.
|
|
75
|
+
*
|
|
76
|
+
* Written out field by field rather than spread: a spread would carry
|
|
77
|
+
* `projectDir`, `bundlePath`, `bundleDir`, `iconFiles`, `schemaFiles`,
|
|
78
|
+
* `extraFiles` and `licenseFile` — seven absolute paths from the assembling
|
|
79
|
+
* host — into a file whose whole purpose is to be read somewhere else. The
|
|
80
|
+
* explicit list is also what makes adding a field a decision: see the comment
|
|
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.
|
|
85
|
+
*/
|
|
86
|
+
export declare function toPackSettings(settings: ShipSettings): PackSettings;
|
|
87
|
+
/** Absolute path of a stage's manifest. */
|
|
88
|
+
export declare function stageManifestPath(stageDir: string): string;
|
|
89
|
+
/** `linux-x64`. One spelling repo-wide (root AGENTS.md § Runtime & platform model). */
|
|
90
|
+
export declare function formatTarget(target: StageTarget): string;
|
|
91
|
+
/**
|
|
92
|
+
* Write the manifest at the stage root, AFTER the tree itself.
|
|
93
|
+
*
|
|
94
|
+
* `bytes` is stat'ed off the written tree rather than taken from the plan: the
|
|
95
|
+
* plan holds a source path or a string, and what the packing host will compare
|
|
96
|
+
* against is what actually landed on disk here.
|
|
97
|
+
*/
|
|
98
|
+
export declare function writeStageManifest(input: StageManifestInput): StageManifest;
|
|
99
|
+
/**
|
|
100
|
+
* Read and validate a stage's manifest.
|
|
101
|
+
*
|
|
102
|
+
* Every field is checked, because this file is the only thing standing between
|
|
103
|
+
* "a directory someone pointed us at" and a signed-looking `.deb`. A missing
|
|
104
|
+
* `staged` entry would reach `readStage` as a mode nobody planned; a `mode` that
|
|
105
|
+
* arrived as a string would reach `chmod` as `NaN`.
|
|
106
|
+
*/
|
|
107
|
+
export declare function readStageManifest(stageDir: string): StageManifest;
|
|
108
|
+
/** The overlay for one format, in the shape `writeStage` takes. */
|
|
109
|
+
export declare function overlayFiles(manifest: StageManifest, format: FormatId): StagedFile[];
|
|
110
|
+
/**
|
|
111
|
+
* Refuse a stage that is not the one this job was told to pack.
|
|
112
|
+
*
|
|
113
|
+
* WHAT THIS IS: a matrix-leg mix-up check. It catches a finish job that
|
|
114
|
+
* downloaded the wrong artifact — the leg that forgot to route — and nothing
|
|
115
|
+
* more. It is opt-in (no flag, no check) and it compares two strings the same
|
|
116
|
+
* pipeline wrote one job apart, so treat a green run as "this is the stage I
|
|
117
|
+
* asked for", never as "this stage is correctly labelled".
|
|
118
|
+
*
|
|
119
|
+
* WHAT THIS IS NOT: the guard its precedent
|
|
120
|
+
* (`packages/node-gi/scripts/verify-bundle-manifest.mjs --expect-host-target`)
|
|
121
|
+
* provides. That script compares a bundle to `process.platform`/`process.arch`
|
|
122
|
+
* because the runner BUILT it, so the machine is the right second opinion.
|
|
123
|
+
* Here the stage arrives from another host by design, so a host comparison
|
|
124
|
+
* would refuse a legal use — measured: an `arm64`-labelled stage packs on this
|
|
125
|
+
* x64 host at exit 0 with correct bytes, because both packers are pure JS.
|
|
126
|
+
*
|
|
127
|
+
* The class that precedent guards — a label and the bytes under it disagreeing
|
|
128
|
+
* — is NOT guarded here, and `target.arch` cannot guard it: it is defaulted
|
|
129
|
+
* from `process.arch` only when `--arch` is absent, so the moment an operator
|
|
130
|
+
* says which leg they are on, `target.arch` is that operator's claim and
|
|
131
|
+
* nothing compares it to a machine or to a byte. Measured BEFORE
|
|
132
|
+
* {@link assertPayloadMatchesArch} existed: a stage carrying an x86-64 `.so`,
|
|
133
|
+
* assembled `--stage --arch arm64` and packed `--expect-target linux-arm64`,
|
|
134
|
+
* exited 0 and produced an `aarch64` rpm whose only `.so` was `e_machine=0x3e`.
|
|
135
|
+
* The same command in this tree now exits 1 and writes nothing — kept in the
|
|
136
|
+
* past tense because it is the reason the guard below it exists, not a
|
|
137
|
+
* description of what this function does.
|
|
138
|
+
*
|
|
139
|
+
* That class is guarded one layer down instead, where the oracle is free and
|
|
140
|
+
* cannot be wrong about a machine it never saw: {@link assertPayloadMatchesArch}
|
|
141
|
+
* in `payload.ts` reads ELF `e_machine` / Mach-O `cputype` out of the payload
|
|
142
|
+
* and refuses a label the bytes contradict. Payload-versus-label goes red on
|
|
143
|
+
* the mislabelled stage AND stays green on the legitimate cross-host pack;
|
|
144
|
+
* host-versus-label can only do one of the two.
|
|
145
|
+
*/
|
|
146
|
+
export declare function assertExpectedTarget(manifest: StageManifest, expected: string | undefined): void;
|
|
147
|
+
/** Refuse a format the stage carries no overlay closure for. */
|
|
148
|
+
export declare function assertFormatsStaged(manifest: StageManifest, formats: readonly FormatDescriptor[]): void;
|
|
149
|
+
/**
|
|
150
|
+
* The build stamp the packing host uses: the stage's, always.
|
|
151
|
+
*
|
|
152
|
+
* A `SOURCE_DATE_EPOCH` set only in the finish job would silently produce a
|
|
153
|
+
* different artifact than the same stage packed on the assembling host, which
|
|
154
|
+
* is the one property the two-phase split has to keep. It is a warning rather
|
|
155
|
+
* than a refusal because the honest fix ("set it in both jobs or in neither")
|
|
156
|
+
* is the caller's, and refusing would break a runner that exports it globally.
|
|
157
|
+
*/
|
|
158
|
+
export declare function resolveStageMtime(manifest: StageManifest, env?: Record<string, string | undefined>): {
|
|
159
|
+
mtime: number;
|
|
160
|
+
warnings: string[];
|
|
161
|
+
};
|
|
162
|
+
/** One actionable line when the two phases ran different gjsify versions. */
|
|
163
|
+
export declare function warnOnToolSkew(manifest: StageManifest): string[];
|