@gjsify/cli 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.gjs.mjs +192 -165
- package/lib/commands/gettext.d.ts +2 -1
- package/lib/commands/gettext.js +105 -47
- package/lib/commands/onboard.d.ts +7 -0
- package/lib/commands/onboard.js +349 -96
- package/lib/commands/ship.d.ts +3 -0
- package/lib/commands/ship.js +833 -54
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/types/config-data.d.ts +63 -1
- package/lib/utils/msgfmt-merge.d.ts +35 -0
- package/lib/utils/msgfmt-merge.js +102 -0
- package/lib/utils/npm-otp.d.ts +27 -2
- package/lib/utils/npm-otp.js +82 -7
- package/lib/utils/onboard-discovery.d.ts +68 -0
- package/lib/utils/onboard-discovery.js +178 -0
- package/lib/utils/onboard-probe.d.ts +118 -1
- package/lib/utils/onboard-probe.js +153 -4
- package/lib/utils/prompt-output.d.ts +12 -0
- package/lib/utils/prompt-output.js +54 -0
- package/lib/utils/prompt.js +22 -2
- package/lib/utils/publishable-packages.d.ts +1 -1
- package/lib/utils/publishable-packages.js +1 -0
- package/lib/utils/resolve-npm-package.d.ts +56 -0
- package/lib/utils/resolve-npm-package.js +69 -0
- package/lib/utils/ship/app-runtime.d.ts +214 -0
- package/lib/utils/ship/app-runtime.js +429 -0
- package/lib/utils/ship/depends.d.ts +50 -0
- package/lib/utils/ship/depends.js +117 -3
- package/lib/utils/ship/discover.js +18 -1
- package/lib/utils/ship/dmg.d.ts +71 -0
- package/lib/utils/ship/dmg.js +181 -0
- package/lib/utils/ship/flatpak.js +1 -1
- package/lib/utils/ship/formats.d.ts +85 -7
- package/lib/utils/ship/formats.js +734 -15
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +362 -17
- package/lib/utils/ship/layout.d.ts +268 -0
- package/lib/utils/ship/layout.js +351 -0
- package/lib/utils/ship/localize-metadata.d.ts +22 -0
- package/lib/utils/ship/localize-metadata.js +126 -0
- package/lib/utils/ship/mime.js +1 -9
- package/lib/utils/ship/msi.d.ts +126 -0
- package/lib/utils/ship/msi.js +417 -0
- package/lib/utils/ship/node-runtime.d.ts +71 -0
- package/lib/utils/ship/node-runtime.js +122 -0
- package/lib/utils/ship/payload.d.ts +156 -5
- package/lib/utils/ship/payload.js +580 -14
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +20 -7
- package/lib/utils/ship/plist.d.ts +58 -0
- package/lib/utils/ship/plist.js +198 -0
- package/lib/utils/ship/rpm.js +9 -3
- package/lib/utils/ship/schemas.d.ts +61 -0
- package/lib/utils/ship/schemas.js +118 -0
- package/lib/utils/ship/scripts.js +7 -3
- package/lib/utils/ship/settings.d.ts +19 -1
- package/lib/utils/ship/settings.js +12 -1
- package/lib/utils/ship/share-dirs.d.ts +23 -0
- package/lib/utils/ship/share-dirs.js +46 -0
- package/lib/utils/ship/signing.d.ts +195 -0
- package/lib/utils/ship/signing.js +317 -0
- package/lib/utils/ship/stage-manifest.d.ts +40 -12
- package/lib/utils/ship/stage-manifest.js +99 -13
- package/lib/utils/ship/stage-writer.d.ts +48 -0
- package/lib/utils/ship/stage-writer.js +71 -1
- package/lib/utils/ship/types.d.ts +158 -13
- package/lib/utils/ship/xml.d.ts +7 -0
- package/lib/utils/ship/xml.js +25 -0
- package/lib/utils/ship/zip.d.ts +69 -0
- package/lib/utils/ship/zip.js +245 -0
- package/lib/utils/trust-registry.js +5 -0
- package/package.json +19 -19
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
// The runtime a self-contained `<App>.app` CARRIES — found, then staged with its
|
|
2
|
+
// tree intact (#1354 M2b, ADR 0024 stage 4).
|
|
3
|
+
//
|
|
4
|
+
// WHAT M2a LEFT. `gjsify ship darwin` produced a real bundle — `Info.plist`,
|
|
5
|
+
// `PkgInfo`, a compiled schema cache — whose launcher still `exec`ed the bare
|
|
6
|
+
// name `node` off `PATH` and whose GI namespaces still expected a GTK on the
|
|
7
|
+
// machine. macOS ships neither. So the artifact was an application in shape and
|
|
8
|
+
// not in behaviour, and `formats.ts`'s `macos-app` row says so in as many words:
|
|
9
|
+
// "whatever the bundle does not carry, it finds on the machine or it does not
|
|
10
|
+
// run — which is what makes M2b (staging a runtime) the milestone that matters".
|
|
11
|
+
// This module is that staging.
|
|
12
|
+
//
|
|
13
|
+
// FOUR PIECES, THREE OWNERS — `@gjsify/node-gi` supplies two of them — and none
|
|
14
|
+
// is a dependency of anything here:
|
|
15
|
+
//
|
|
16
|
+
// * the interpreter — `@gjsify/node-runtime-<target>`, resolved by
|
|
17
|
+
// {@link resolveNodeRuntime} in `node-runtime.ts`. This module is the caller
|
|
18
|
+
// that module was written for, and #1354 M3 gave it a second layout to serve.
|
|
19
|
+
// * the relocated GTK closure — `@gjsify/gtk-runtime-<target>`'s `gtk/`.
|
|
20
|
+
// * the node-gi addon built against that closure —
|
|
21
|
+
// `@gjsify/node-gi`'s `prebuilds/<target>/node_gi.node`, whose `@rpath` is
|
|
22
|
+
// `@loader_path/gtk/lib`.
|
|
23
|
+
// * node-gi's own JAVASCRIPT, which is a fourth piece and not an afterthought:
|
|
24
|
+
// `@gjsify/node-gi/*` is EXTERNAL in every `--app node` bundle by design
|
|
25
|
+
// (`rolldown-plugin-gjsify/src/app/node.ts`: the reverse-bridge modules
|
|
26
|
+
// "must resolve at runtime against the consumer's node_modules"), so a
|
|
27
|
+
// `gi://Gtk` import compiles to `require('@gjsify/node-gi/gi')` in the
|
|
28
|
+
// shipped file. Measured on the `.app` fixture's own bundle. A `.app` has no
|
|
29
|
+
// consumer node_modules, so the package is staged into one the bundle's
|
|
30
|
+
// directory owns.
|
|
31
|
+
//
|
|
32
|
+
// THE LAYOUT IS NOT OURS TO CHOOSE. `resolveGtkRuntimeBundle()` in
|
|
33
|
+
// `packages/node-gi/node-gi/gtk-runtime.js` walks four candidates and probes each
|
|
34
|
+
// for `<dir>/lib` + `<dir>/girepository-1.0`; the addon's `@rpath` reaches its
|
|
35
|
+
// dylibs as `@loader_path/gtk/lib`; `loaders.cache` names each gdk-pixbuf loader
|
|
36
|
+
// `@loader_path/../../..`-relative; `GST_PLUGIN_SCANNER` is an executable under
|
|
37
|
+
// `libexec/gstreamer-1.0`. Every one of those relations is RELATIVE, so the whole
|
|
38
|
+
// tree survives a copy — as long as it is copied as a tree. Hence
|
|
39
|
+
// {@link appRuntimePaths}: the staged shape is node-gi's own sibling layout,
|
|
40
|
+
// `prebuilds/<target>/{node_gi.node, gtk/}`, the same one
|
|
41
|
+
// `.github/workflows/node-gi.yml`'s macOS legs assemble by hand before they run
|
|
42
|
+
// the conformance.
|
|
43
|
+
//
|
|
44
|
+
// AND THAT SPLIT IS WHY THE TWO ENVIRONMENT VARIABLES ARE LOAD-BEARING rather
|
|
45
|
+
// than belt-and-braces. Once node-gi's JS sits at
|
|
46
|
+
// `Contents/Resources/lib/node_modules/@gjsify/node-gi`, its `packageRoot` is
|
|
47
|
+
// THAT directory — so `prebuildAddonPath()` looks for
|
|
48
|
+
// `…/node_modules/@gjsify/node-gi/prebuilds/<target>/node_gi.node` and
|
|
49
|
+
// `resolveGtkRuntimeBundle()`'s candidate 2 for `…/prebuilds/<target>/gtk`, and
|
|
50
|
+
// neither is where a `.app` may keep loadable code: `Contents/Frameworks` is, and
|
|
51
|
+
// it is what `codesign` now reaches (ADR 0024 § A4) — M6 landed, and
|
|
52
|
+
// `utils/ship/signing.ts` re-signs every Mach-O in the payload by magic number,
|
|
53
|
+
// which is most of this closure. Putting it under `Resources` to save two
|
|
54
|
+
// `export` lines would trade a correct bundle for a shorter launcher.
|
|
55
|
+
//
|
|
56
|
+
// WHY NOT `gjsify.ship.bundledTypelibs`. Because it FLATTENS. `plan.ts` stages
|
|
57
|
+
// each such file as `posix.join(libDir, 'gi', basename(file))`, and
|
|
58
|
+
// `discoverTypelibs` walks the directory recursively — so three inputs at three
|
|
59
|
+
// depths collapse into one directory. Measured on this tree, through the built
|
|
60
|
+
// planner:
|
|
61
|
+
//
|
|
62
|
+
// gi/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.so → lib/demo/gi/libpixbufloader-svg.so
|
|
63
|
+
// gi/loaders.cache → lib/demo/gi/loaders.cache
|
|
64
|
+
// gi/Gtk-4.0.typelib → lib/demo/gi/Gtk-4.0.typelib
|
|
65
|
+
//
|
|
66
|
+
// That destroys the loader paths `loaders.cache` records, and it leaves a
|
|
67
|
+
// directory with no `lib/` and no `girepository-1.0/` — so
|
|
68
|
+
// `resolveGtkRuntimeBundle()`'s existence probe rejects it and node-gi falls back
|
|
69
|
+
// to a GTK the machine does not have. `bundledTypelibs` stays what it is (a FLAT
|
|
70
|
+
// pair of a typelib and its backer, which is all it ever claimed to be); a
|
|
71
|
+
// relocated closure needs its own stager, and this is it.
|
|
72
|
+
//
|
|
73
|
+
// `null`-not-throw, like `node-runtime.ts`, and for the same reason: whether a
|
|
74
|
+
// missing piece is fatal depends on what the caller is building. `assemble` in
|
|
75
|
+
// `commands/ship.ts` stages what was found, names what was not, and keeps
|
|
76
|
+
// `Layout.runtimeGap` for exactly the case where the interpreter is still absent.
|
|
77
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
78
|
+
import { dirname, join, posix, sep } from 'node:path';
|
|
79
|
+
import { listFilesRecursive } from './discover.js';
|
|
80
|
+
import { nodeRuntimeBinaryName } from './node-runtime.js';
|
|
81
|
+
import { isExecutableAsset } from './plan.js';
|
|
82
|
+
import { resolveInstalledPackage } from '../resolve-npm-package.js';
|
|
83
|
+
/**
|
|
84
|
+
* The targets a relocated GTK closure is published for.
|
|
85
|
+
*
|
|
86
|
+
* The same three `@gjsify/gtk-runtime-*` packages node-gi resolves, and
|
|
87
|
+
* deliberately NOT derived from {@link NODE_RUNTIME_TARGETS}: the two lists agree
|
|
88
|
+
* today by coincidence of which platforms lack a dependable system stack, and
|
|
89
|
+
* deriving one from the other would make a future Linux GTK bundle silently imply
|
|
90
|
+
* a bundled Linux interpreter (which `node-runtime.ts` refuses on purpose).
|
|
91
|
+
*/
|
|
92
|
+
export const GTK_RUNTIME_TARGETS = ['darwin-arm64', 'darwin-x64', 'win32-x64'];
|
|
93
|
+
/** Is this `<platform>-<arch>` one of the published GTK-closure targets? */
|
|
94
|
+
export function isGtkRuntimeTarget(target) {
|
|
95
|
+
return GTK_RUNTIME_TARGETS.includes(target);
|
|
96
|
+
}
|
|
97
|
+
/** The npm name carrying the relocated closure for a target. One spelling, derived. */
|
|
98
|
+
export function gtkRuntimePackageName(target) {
|
|
99
|
+
return `@gjsify/gtk-runtime-${target}`;
|
|
100
|
+
}
|
|
101
|
+
/** The addon's filename, node-gi's `ADDON_FILENAME`. Identical in every prebuild. */
|
|
102
|
+
export const NODE_GI_ADDON_FILENAME = 'node_gi.node';
|
|
103
|
+
/** The package whose `prebuilds/<target>/` holds the addon and, in CI, the closure beside it. */
|
|
104
|
+
export const NODE_GI_PACKAGE = '@gjsify/node-gi';
|
|
105
|
+
/**
|
|
106
|
+
* Locate the relocated GTK closure for `target`, or `null`.
|
|
107
|
+
*
|
|
108
|
+
* The search order MIRRORS `resolveGtkRuntimeBundle()` rather than inventing a
|
|
109
|
+
* second one, because the two have to agree: this function decides what gets
|
|
110
|
+
* copied into the bundle, and that function decides what the bundle finds at
|
|
111
|
+
* runtime. Four candidates, first complete hit wins:
|
|
112
|
+
*
|
|
113
|
+
* 1. `GJSIFY_GTK_RUNTIME` — an explicit bundle directory, for a maintainer
|
|
114
|
+
* holding an unpublished or patched closure. The same variable the launcher
|
|
115
|
+
* later EXPORTS, and the same precedence it has there.
|
|
116
|
+
* 2. `@gjsify/node-gi`'s own `prebuilds/<target>/gtk`. This is the layout the
|
|
117
|
+
* macOS CI legs stage by hand (`node-gi.yml`: download the bundle artifact
|
|
118
|
+
* into `prebuilds/darwin-<arch>/gtk`, the addon beside it), so a job that has
|
|
119
|
+
* prepared node-gi for a conformance run has, by that act alone, prepared it
|
|
120
|
+
* for a ship run.
|
|
121
|
+
* 3. the sibling monorepo directory `../gtk-runtime-<target>/gtk`, which is what
|
|
122
|
+
* an in-repo `npm run build:bundle` writes.
|
|
123
|
+
* 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`.
|
|
124
|
+
*
|
|
125
|
+
* A candidate counts only when it holds BOTH the loadable-code directory and the
|
|
126
|
+
* typelib directory — node-gi's own probe, restated here rather than weakened,
|
|
127
|
+
* because a directory that passes here and fails there is a `.app` that ships
|
|
128
|
+
* 200 MB and still cannot open a window.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveGtkRuntime(target, options = {}) {
|
|
131
|
+
const env = options.env ?? process.env;
|
|
132
|
+
// `bin` on win32, `lib` everywhere else — the NAME differs, the ROLE does not
|
|
133
|
+
// (`gtk-runtime.js`'s `nativeSubdir`). Read off the TARGET and never
|
|
134
|
+
// `process.platform`: assembling a win32 tree from Linux is a supported path.
|
|
135
|
+
const nativeSubdir = target.startsWith('win32-') ? 'bin' : 'lib';
|
|
136
|
+
const candidates = [];
|
|
137
|
+
const override = env['GJSIFY_GTK_RUNTIME'];
|
|
138
|
+
if (override)
|
|
139
|
+
candidates.push({ source: 'GJSIFY_GTK_RUNTIME', dir: override });
|
|
140
|
+
const nodeGiRoot = resolveNodeGiRoot(options);
|
|
141
|
+
if (nodeGiRoot !== null) {
|
|
142
|
+
candidates.push({
|
|
143
|
+
source: `${NODE_GI_PACKAGE}/prebuilds/${target}`,
|
|
144
|
+
dir: join(nodeGiRoot, 'prebuilds', target, 'gtk'),
|
|
145
|
+
});
|
|
146
|
+
candidates.push({
|
|
147
|
+
source: `${NODE_GI_PACKAGE} sibling`,
|
|
148
|
+
dir: join(nodeGiRoot, '..', `gtk-runtime-${target}`, 'gtk'),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const packageName = gtkRuntimePackageName(target);
|
|
152
|
+
const entry = resolveStagedPackage(packageName, options);
|
|
153
|
+
if (entry !== null)
|
|
154
|
+
candidates.push({ source: packageName, dir: join(dirname(entry), 'gtk') });
|
|
155
|
+
for (const candidate of candidates) {
|
|
156
|
+
const libDir = join(candidate.dir, nativeSubdir);
|
|
157
|
+
const typelibDir = join(candidate.dir, 'girepository-1.0');
|
|
158
|
+
if (!existsSync(libDir) || !existsSync(typelibDir))
|
|
159
|
+
continue;
|
|
160
|
+
return { source: candidate.source, dir: candidate.dir, libDir, typelibDir };
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Locate the node-gi addon built for `target`, or `null`.
|
|
166
|
+
*
|
|
167
|
+
* ONE source, and no override. The addon and the closure ship as a matched pair —
|
|
168
|
+
* the addon's `@rpath` is `@loader_path/gtk/lib`, so it names the very directory
|
|
169
|
+
* the closure occupies — and `@gjsify/node-gi` publishes `prebuilds/` for all five
|
|
170
|
+
* of its platforms in ONE package, so resolving the package by name is already
|
|
171
|
+
* enough to reach a foreign target's binary from a Linux host. A second
|
|
172
|
+
* environment knob here would be a way to pair an addon with a closure it was not
|
|
173
|
+
* built against, which is #910's failure shape wearing a convenience.
|
|
174
|
+
*/
|
|
175
|
+
export function resolveNodeGiAddon(target, options = {}) {
|
|
176
|
+
const root = resolveNodeGiRoot(options);
|
|
177
|
+
if (root === null)
|
|
178
|
+
return null;
|
|
179
|
+
const addonPath = join(root, 'prebuilds', target, NODE_GI_ADDON_FILENAME);
|
|
180
|
+
// A package resolved but not POPULATED is `null`, not a path — `prebuilds/` is
|
|
181
|
+
// gitignored, so an in-repo checkout is exactly what that looks like, and a
|
|
182
|
+
// path to a file that is not there fails later, in a copy, with the target's
|
|
183
|
+
// name nowhere in the message. Same rule as `node-runtime.ts`'s `complete()`.
|
|
184
|
+
return existsSync(addonPath) ? { source: NODE_GI_PACKAGE, addonPath } : null;
|
|
185
|
+
}
|
|
186
|
+
/** `@gjsify/node-gi`'s package root, or `null` when it is not resolvable from here. */
|
|
187
|
+
function resolveNodeGiRoot(options) {
|
|
188
|
+
const entry = resolveStagedPackage(NODE_GI_PACKAGE, options);
|
|
189
|
+
return entry === null ? null : dirname(entry);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resolve a package whose BYTES this module stages into a redistributed artifact.
|
|
193
|
+
*
|
|
194
|
+
* `resolveInstalledPackage`, never `resolveNpmPackage`: under Bun the latter
|
|
195
|
+
* answers from the runtime's global install cache for a project that merely has a
|
|
196
|
+
* `package.json` and no `node_modules`, which would put files the author never
|
|
197
|
+
* declared inside a `.app`. See that function for the measurement.
|
|
198
|
+
*/
|
|
199
|
+
function resolveStagedPackage(specifier, options) {
|
|
200
|
+
const opts = { bundleUrl: import.meta.url };
|
|
201
|
+
if (options.cwd !== undefined)
|
|
202
|
+
opts.cwd = options.cwd;
|
|
203
|
+
if (options.resolve !== undefined)
|
|
204
|
+
opts.resolve = options.resolve;
|
|
205
|
+
return resolveInstalledPackage(specifier, opts);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The files a SHIPPED `.app` needs from `@gjsify/node-gi`, or `null`.
|
|
209
|
+
*
|
|
210
|
+
* NOT the published tarball. `files` in node-gi's manifest also lists `src/`,
|
|
211
|
+
* `binding.gyp` and `scripts/install.mjs` — the inputs to `node-gyp`, which a
|
|
212
|
+
* `.app` never runs — and the `.d.ts` declarations, which nothing inside a bundle
|
|
213
|
+
* compiles against. And it lists `prebuilds/`, which this module stages into
|
|
214
|
+
* `Contents/Frameworks` instead, where a `.app` keeps loadable code.
|
|
215
|
+
*
|
|
216
|
+
* So the rule is what `require('@gjsify/node-gi/<subpath>')` can REACH:
|
|
217
|
+
* `package.json` (the `exports` map is what resolves every subpath), every `.js`
|
|
218
|
+
* at the package root (every `exports` target is one), and `overrides/`, which
|
|
219
|
+
* `gi.js` loads at runtime. `tests/e2e/ship-macos` holds that rule against the
|
|
220
|
+
* REAL package's `exports` map, so a node-gi release that adds an entry outside
|
|
221
|
+
* this set fails there rather than in a `.app` a stranger downloaded.
|
|
222
|
+
*/
|
|
223
|
+
export function resolveNodeGiPackage(options = {}) {
|
|
224
|
+
const root = resolveNodeGiRoot(options);
|
|
225
|
+
if (root === null)
|
|
226
|
+
return null;
|
|
227
|
+
const files = ['package.json'];
|
|
228
|
+
for (const entry of readdirSync(root, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
229
|
+
if (entry.isFile() && entry.name.endsWith('.js'))
|
|
230
|
+
files.push(entry.name);
|
|
231
|
+
}
|
|
232
|
+
const overrides = join(root, 'overrides');
|
|
233
|
+
if (existsSync(overrides)) {
|
|
234
|
+
for (const rel of listFilesRecursive(overrides))
|
|
235
|
+
files.push(posix.join('overrides', rel));
|
|
236
|
+
}
|
|
237
|
+
// One `.js` is the floor: a package whose root holds only `package.json` is a
|
|
238
|
+
// directory that resolved and a runtime that is not there, and staging it would
|
|
239
|
+
// produce a `.app` failing at `require` with node-gi's name in the message and
|
|
240
|
+
// nothing to act on.
|
|
241
|
+
return files.length > 1 ? { source: NODE_GI_PACKAGE, root, files: files.sort() } : null;
|
|
242
|
+
}
|
|
243
|
+
export function appRuntimePaths(layout, identity, target) {
|
|
244
|
+
const dirs = layout.dirs(identity);
|
|
245
|
+
const nodeGiRoot = posix.join(dirs.native, 'node-gi');
|
|
246
|
+
const prebuildDir = posix.join(nodeGiRoot, 'prebuilds', target);
|
|
247
|
+
return {
|
|
248
|
+
nodeGiRoot,
|
|
249
|
+
nodeGiPackageDir: posix.join(dirs.bundle, 'node_modules', NODE_GI_PACKAGE),
|
|
250
|
+
prebuildDir,
|
|
251
|
+
gtkDir: posix.join(prebuildDir, 'gtk'),
|
|
252
|
+
addonPath: posix.join(prebuildDir, NODE_GI_ADDON_FILENAME),
|
|
253
|
+
// `Contents/MacOS`, because that is where a `.app` keeps executables and
|
|
254
|
+
// because `$here` — which the launcher already computes — is then the whole
|
|
255
|
+
// path expression. `Contents/Frameworks` would work for dyld and would make
|
|
256
|
+
// the launcher walk back up for the one file it execs. On Windows
|
|
257
|
+
// `dirs.launcher` is the program directory itself, so the interpreter lands
|
|
258
|
+
// beside the `.cmd` for the same reason.
|
|
259
|
+
//
|
|
260
|
+
// THE LEAF COMES FROM `node-runtime.ts`, which is the module that already
|
|
261
|
+
// decided it for the SOURCE file — `node.exe` on win32, `node` elsewhere,
|
|
262
|
+
// read off the TARGET and never `process.platform` (assembling a Windows
|
|
263
|
+
// program directory on Linux is the supported path). Restating the rule here
|
|
264
|
+
// would let the two drift into a stage that copies `node.exe` to `node` and a
|
|
265
|
+
// launcher that execs neither.
|
|
266
|
+
interpreterPath: posix.join(dirs.launcher, nodeRuntimeBinaryName(target)),
|
|
267
|
+
// rpm's `share/licenses/<name>/LICENSE` shape, which the `macos-app` row
|
|
268
|
+
// already uses for the app's own licence — one layout for a reader to learn,
|
|
269
|
+
// and `node` is a name no `binaryName` can collide with silently: `placeStage`
|
|
270
|
+
// refuses the collision by name.
|
|
271
|
+
interpreterLicensePath: posix.join(dirs.data, 'licenses', 'node', 'LICENSE'),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Stage the interpreter, the GTK closure and the addon into one bundle.
|
|
276
|
+
*
|
|
277
|
+
* PARTIAL BY DESIGN, and reported. An all-or-nothing rule would drop a 200 MB
|
|
278
|
+
* closure that IS present because an interpreter that is not shares the milestone
|
|
279
|
+
* with it, and would do it at exit 0 — while a bundle carrying the closure and no
|
|
280
|
+
* interpreter is a real, useful intermediate (it runs wherever a Node is on
|
|
281
|
+
* `PATH`, which is every developer's machine and no stranger's). So each piece is
|
|
282
|
+
* staged when found and named when not, and `commands/ship.ts` keeps
|
|
283
|
+
* `Layout.runtimeGap` for precisely the case where the interpreter is still
|
|
284
|
+
* missing.
|
|
285
|
+
*
|
|
286
|
+
* TREE-PRESERVING for the closure and FLAT for the other two, which is the whole
|
|
287
|
+
* distinction this module exists to draw: `listFilesRecursive` walks the bundle
|
|
288
|
+
* and every relative path is carried through unchanged, so
|
|
289
|
+
* `lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.so` is still four levels
|
|
290
|
+
* under `gtk/` on the other side of the copy.
|
|
291
|
+
*/
|
|
292
|
+
export function stageAppRuntime(input) {
|
|
293
|
+
const paths = appRuntimePaths(input.layout, input.identity, input.target);
|
|
294
|
+
const files = [];
|
|
295
|
+
const launcher = {};
|
|
296
|
+
const found = [];
|
|
297
|
+
const missing = [];
|
|
298
|
+
if (input.interpreter !== null) {
|
|
299
|
+
files.push({
|
|
300
|
+
path: paths.interpreterPath,
|
|
301
|
+
mode: 0o755,
|
|
302
|
+
source: { kind: 'file', path: input.interpreter.nodePath },
|
|
303
|
+
});
|
|
304
|
+
// The licence travels with the binary or neither does — `ResolvedNodeRuntime`
|
|
305
|
+
// makes that a property of the RESULT for this reason, and this is the caller
|
|
306
|
+
// that would otherwise have been free to forget it.
|
|
307
|
+
files.push({
|
|
308
|
+
path: paths.interpreterLicensePath,
|
|
309
|
+
mode: 0o644,
|
|
310
|
+
source: { kind: 'file', path: input.interpreter.licensePath },
|
|
311
|
+
});
|
|
312
|
+
launcher.interpreter = paths.interpreterPath;
|
|
313
|
+
found.push(`interpreter from ${input.interpreter.source}`);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
missing.push(`no bundled interpreter — install \`@gjsify/node-runtime-${input.target}\` (or point ` +
|
|
317
|
+
'`GJSIFY_NODE_RUNTIME` at a `bin/` directory holding `node` and its `LICENSE`)');
|
|
318
|
+
}
|
|
319
|
+
const gtk = resolveGtkRuntime(input.target, discoveryOptions(input));
|
|
320
|
+
if (gtk !== null) {
|
|
321
|
+
for (const rel of listFilesRecursive(gtk.dir)) {
|
|
322
|
+
const abs = join(gtk.dir, rel.split('/').join(sep));
|
|
323
|
+
files.push({
|
|
324
|
+
path: posix.join(paths.gtkDir, rel),
|
|
325
|
+
mode: stagedMode(rel, abs),
|
|
326
|
+
source: { kind: 'file', path: abs },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
launcher.gtkRuntimeDir = paths.gtkDir;
|
|
330
|
+
found.push(`GTK closure from ${gtk.source}`);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
// The native subdirectory is `bin` on win32 and `lib` everywhere else,
|
|
334
|
+
// exactly as `resolveGtkRuntimeBundle()` probes for it. Naming `lib/`
|
|
335
|
+
// unconditionally sent a Windows reader to build a directory this very
|
|
336
|
+
// resolver then refuses -- a hint that is wrong is worse than none,
|
|
337
|
+
// because it costs the reader the time to follow it first.
|
|
338
|
+
const nativeSubdir = input.target.startsWith('win32-') ? 'bin' : 'lib';
|
|
339
|
+
missing.push(`no GTK closure — install \`${gtkRuntimePackageName(input.target)}\` (or point ` +
|
|
340
|
+
`\`GJSIFY_GTK_RUNTIME\` at a bundle directory holding \`${nativeSubdir}/\` and \`girepository-1.0/\`)`);
|
|
341
|
+
}
|
|
342
|
+
// node-gi's JavaScript, which the bundle `require`s by name. Staged whenever
|
|
343
|
+
// the package is resolvable and reported separately from the addon: a bundle
|
|
344
|
+
// that carries the JS and no addon fails with node-gi's own diagnosis
|
|
345
|
+
// (`load-diagnostics.js`), while one that carries the addon and no JS fails at
|
|
346
|
+
// `require` before anything of node-gi's has run.
|
|
347
|
+
const js = resolveNodeGiPackage(discoveryOptions(input));
|
|
348
|
+
if (js !== null) {
|
|
349
|
+
for (const rel of js.files) {
|
|
350
|
+
files.push({
|
|
351
|
+
path: posix.join(paths.nodeGiPackageDir, rel),
|
|
352
|
+
mode: 0o644,
|
|
353
|
+
source: { kind: 'file', path: join(js.root, rel.split('/').join(sep)) },
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
found.push(`node-gi runtime (${js.files.length} file(s)) from ${js.source}`);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
missing.push(`no node-gi JavaScript — install \`${NODE_GI_PACKAGE}\`; a \`--app node\` bundle keeps ` +
|
|
360
|
+
'`@gjsify/node-gi/*` external, so the staged bundle would `require` a package the artifact ' +
|
|
361
|
+
'does not carry');
|
|
362
|
+
}
|
|
363
|
+
const addon = resolveNodeGiAddon(input.target, discoveryOptions(input));
|
|
364
|
+
if (addon !== null) {
|
|
365
|
+
files.push({ path: paths.addonPath, mode: 0o755, source: { kind: 'file', path: addon.addonPath } });
|
|
366
|
+
launcher.nodeGiAddon = paths.addonPath;
|
|
367
|
+
found.push(`node-gi addon from ${addon.source}`);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
missing.push(`no node-gi addon — install \`${NODE_GI_PACKAGE}\`, whose \`prebuilds/${input.target}/` +
|
|
371
|
+
`${NODE_GI_ADDON_FILENAME}\` is the binary built against that closure`);
|
|
372
|
+
}
|
|
373
|
+
return { files, launcher, found, missing };
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* The discovery options every piece is looked up with.
|
|
377
|
+
*
|
|
378
|
+
* ONE function, so a field added to {@link StageAppRuntimeInput} cannot reach two
|
|
379
|
+
* of the three lookups and miss the third. It missed all three once already:
|
|
380
|
+
* `resolve` was declared on the input, spelled correctly at the call site in the
|
|
381
|
+
* spec, and silently dropped here — `packages/infra/cli/tsconfig.json` excludes
|
|
382
|
+
* `src/**` + `.spec.ts`, so no excess-property check ever ran on it and the test
|
|
383
|
+
* that was supposed to red the global-cache guard passed with the guard removed.
|
|
384
|
+
*/
|
|
385
|
+
function discoveryOptions(input) {
|
|
386
|
+
const options = {};
|
|
387
|
+
if (input.cwd !== undefined)
|
|
388
|
+
options.cwd = input.cwd;
|
|
389
|
+
if (input.env !== undefined)
|
|
390
|
+
options.env = input.env;
|
|
391
|
+
if (input.resolve !== undefined)
|
|
392
|
+
options.resolve = input.resolve;
|
|
393
|
+
return options;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The mode one staged closure file gets.
|
|
397
|
+
*
|
|
398
|
+
* THREE tests, and each covers a case the others do not:
|
|
399
|
+
*
|
|
400
|
+
* * the source's own execute bit — the truth when the closure came off a real
|
|
401
|
+
* filesystem, and the ONLY thing that knows `libexec/gstreamer-1.0/gst-plugin-scanner`
|
|
402
|
+
* is a program (it has no extension, and GStreamer forks it out-of-process so a
|
|
403
|
+
* plugin that crashes on load cannot take the app down with it);
|
|
404
|
+
* * the shared-library spelling, shared with `plan.ts` rather than restated —
|
|
405
|
+
* load-bearing because `actions/upload-artifact` stores NO POSIX mode, so a
|
|
406
|
+
* closure that arrived through a CI artifact has 0644 on every file and the
|
|
407
|
+
* source-mode test above is blind on exactly the path this milestone's own CI
|
|
408
|
+
* leg takes;
|
|
409
|
+
* * `libexec/` and `bin/`, for the same reason applied to the extensionless
|
|
410
|
+
* programs the artifact round trip flattened.
|
|
411
|
+
*
|
|
412
|
+
* A dylib does not need the execute bit to be `dlopen`'d, so this is not about
|
|
413
|
+
* making the closure loadable — it is about the two files that are EXEC'd.
|
|
414
|
+
*/
|
|
415
|
+
function stagedMode(rel, abs) {
|
|
416
|
+
if (isExecutableAsset(rel))
|
|
417
|
+
return 0o755;
|
|
418
|
+
if (rel.startsWith('libexec/') || rel.startsWith('bin/'))
|
|
419
|
+
return 0o755;
|
|
420
|
+
try {
|
|
421
|
+
if ((statSync(abs).mode & 0o111) !== 0)
|
|
422
|
+
return 0o755;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
// Unreadable here means unreadable in the copy too, and `writeStage` is
|
|
426
|
+
// where that gets reported against the path it happened on.
|
|
427
|
+
}
|
|
428
|
+
return 0o644;
|
|
429
|
+
}
|
|
@@ -14,7 +14,32 @@ import type { DistroFormatId } 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
|
+
/**
|
|
18
|
+
* The Node the `--app node` bundles target, as a MAJOR.
|
|
19
|
+
*
|
|
20
|
+
* Node 24 is the LTS line `@gjsify/node-runtime-*` bundles for macOS and
|
|
21
|
+
* Windows, and this is the Linux half of the same decision: where an
|
|
22
|
+
* interpreter is bundled, the floor is what was bundled; where it is depended
|
|
23
|
+
* on, the floor is what the bundle needs.
|
|
24
|
+
*
|
|
25
|
+
* It excludes EVERY current DEB stable/LTS — Debian 13 trixie 20, Ubuntu 24.04
|
|
26
|
+
* LTS 18, Ubuntu 26.04 LTS 22; only Debian 14 forky has 24. `warnAboutNodeFloor`
|
|
27
|
+
* is what makes that visible at ship time, and `gjsify.ship.minNodeVersion` is
|
|
28
|
+
* how a project whose bundle really does run on an older Node lowers it. The
|
|
29
|
+
* same honest-floor-plus-warning answer as {@link DEFAULT_GJS_FLOOR}, one axis
|
|
30
|
+
* over — and it will fire far more often.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_NODE_FLOOR = "24";
|
|
17
33
|
export declare function warnAboutGjsFloor(format: DistroFormatId, floor: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Warn when the Node floor cannot be satisfied by a released Debian or Ubuntu.
|
|
36
|
+
* Returns the warning lines; empty when the floor is fine.
|
|
37
|
+
*
|
|
38
|
+
* Only reached when the payload actually needs an interpreter — a `--app gjs`
|
|
39
|
+
* package declares no Node dependency, so warning about its floor would be
|
|
40
|
+
* noise attached to a line that is never emitted.
|
|
41
|
+
*/
|
|
42
|
+
export declare function warnAboutNodeFloor(format: DistroFormatId, floor: string): string[];
|
|
18
43
|
export interface DependsInputs {
|
|
19
44
|
/** GI namespaces the bundle imports, in `Ns-Version` spelling. */
|
|
20
45
|
namespaces: readonly string[];
|
|
@@ -29,6 +54,29 @@ export interface DependsInputs {
|
|
|
29
54
|
hasIcons: boolean;
|
|
30
55
|
/** Whether the payload installs GSettings schemas. */
|
|
31
56
|
hasSchemas: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The interpreter the launcher execs — `settings.app`, the SAME field
|
|
59
|
+
* `renderLauncher` branches on.
|
|
60
|
+
*
|
|
61
|
+
* Not a payload fact, and the attempt to make it one is worth keeping: the
|
|
62
|
+
* first cut derived it from a staged FILENAME (`*.node.mjs`), which is wrong
|
|
63
|
+
* in both directions. `discoverPayload` stages the whole directory beside
|
|
64
|
+
* the bundle and `dist/<name>.gjs.js` next to `dist/<name>.node.mjs` is the
|
|
65
|
+
* documented normal layout, so a pure `--app gjs` project that also builds a
|
|
66
|
+
* Node bundle declared a Node dependency it never execs — and with a `>= 24`
|
|
67
|
+
* floor that `.deb` is REFUSED by apt on trixie, Ubuntu 24.04 and Ubuntu
|
|
68
|
+
* 26.04. A working package made uninstallable is not "one extra package
|
|
69
|
+
* installed".
|
|
70
|
+
*
|
|
71
|
+
* The honest source is the one thing that decides both the launcher and this
|
|
72
|
+
* list. `assertLauncherMatchesInterpreter` re-reads the staged launcher and
|
|
73
|
+
* refuses a package where the two disagree, which is the check a payload
|
|
74
|
+
* heuristic could never be.
|
|
75
|
+
*
|
|
76
|
+
* macOS and Windows never reach here: there the interpreter is CARRIED, from
|
|
77
|
+
* `@gjsify/node-runtime-<target>`.
|
|
78
|
+
*/
|
|
79
|
+
interpreter: 'gjs' | 'node';
|
|
32
80
|
/** User-supplied additions for this format. */
|
|
33
81
|
extra: readonly string[];
|
|
34
82
|
/** Project-supplied table rows, filling gaps in {@link TYPELIB_PACKAGES}. */
|
|
@@ -40,6 +88,8 @@ export interface DependsInputs {
|
|
|
40
88
|
bundledTypelibs?: readonly string[];
|
|
41
89
|
/** Minimum GJS. Default {@link DEFAULT_GJS_FLOOR}. */
|
|
42
90
|
minGjsVersion?: string;
|
|
91
|
+
/** Minimum Node major, when one is depended on. Default {@link DEFAULT_NODE_FLOOR}. */
|
|
92
|
+
minNodeVersion?: string;
|
|
43
93
|
}
|
|
44
94
|
/**
|
|
45
95
|
* The `Depends:` / `Requires:` list for one format.
|
|
@@ -35,6 +35,70 @@
|
|
|
35
35
|
* is how a project whose bundle really does run on older GJS lowers it.
|
|
36
36
|
*/
|
|
37
37
|
export const DEFAULT_GJS_FLOOR = '1.86';
|
|
38
|
+
/**
|
|
39
|
+
* The Node the `--app node` bundles target, as a MAJOR.
|
|
40
|
+
*
|
|
41
|
+
* Node 24 is the LTS line `@gjsify/node-runtime-*` bundles for macOS and
|
|
42
|
+
* Windows, and this is the Linux half of the same decision: where an
|
|
43
|
+
* interpreter is bundled, the floor is what was bundled; where it is depended
|
|
44
|
+
* on, the floor is what the bundle needs.
|
|
45
|
+
*
|
|
46
|
+
* It excludes EVERY current DEB stable/LTS — Debian 13 trixie 20, Ubuntu 24.04
|
|
47
|
+
* LTS 18, Ubuntu 26.04 LTS 22; only Debian 14 forky has 24. `warnAboutNodeFloor`
|
|
48
|
+
* is what makes that visible at ship time, and `gjsify.ship.minNodeVersion` is
|
|
49
|
+
* how a project whose bundle really does run on an older Node lowers it. The
|
|
50
|
+
* same honest-floor-plus-warning answer as {@link DEFAULT_GJS_FLOOR}, one axis
|
|
51
|
+
* over — and it will fire far more often.
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_NODE_FLOOR = '24';
|
|
54
|
+
/**
|
|
55
|
+
* The package providing a Node interpreter, per format.
|
|
56
|
+
*
|
|
57
|
+
* ⚠️ THE TWO STRINGS ARE NOT THE SAME NAME, and getting rpm's wrong is silent.
|
|
58
|
+
* `Requires: nodejs >= 24` is a NO-OP on Fedora. Measured with `dnf repoquery`
|
|
59
|
+
* on Fedora 44:
|
|
60
|
+
*
|
|
61
|
+
* --whatprovides 'nodejs >= 24' → nodejs22-1:22.23.1-2.fc44
|
|
62
|
+
* --whatprovides 'nodejs(engine) >= 24' → nodejs24-1:24.18.0-1.fc44
|
|
63
|
+
*
|
|
64
|
+
* F44 has no real `nodejs` package at all — the name is a virtual Provide of the
|
|
65
|
+
* stream packages, which carry **Epoch 1**. A bare `>= 24` desugars to `0:24`,
|
|
66
|
+
* and `1:22.23.1` beats it on epoch alone, so the floor admits the very version
|
|
67
|
+
* it was written to exclude. `nodejs(engine)` carries no epoch and compares as
|
|
68
|
+
* written. (Streams 20/22/24 are parallel-installable, which is why the name has
|
|
69
|
+
* to be a Provide in the first place.) The spelling appears nowhere in the
|
|
70
|
+
* Fedora packaging guideline's own source — it comes from real spec files.
|
|
71
|
+
*
|
|
72
|
+
* No `/usr/bin/node` file dependency beside it — right answer, and the FIRST
|
|
73
|
+
* reason written here for it was wrong, so both are recorded. It is not that
|
|
74
|
+
* `nodejs24` pulls the symlink in: `/usr/bin/node` is provided by `nodejs20-bin`,
|
|
75
|
+
* `nodejs22-bin` AND `nodejs24-bin`, all three of which
|
|
76
|
+
* `Provides`/`Conflicts: alternative-for(nodejs-bin)` and are therefore mutually
|
|
77
|
+
* exclusive. `nodejs24` itself installs `/usr/bin/node-24`. So the file
|
|
78
|
+
* dependency is satisfied by ANY stream's `-bin`, which makes it worthless as a
|
|
79
|
+
* version constraint — that is why it is not emitted.
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ AND THAT LEAVES A REAL HOLE, which no dependency this writer can emit
|
|
82
|
+
* closes. Measured on Fedora 44:
|
|
83
|
+
*
|
|
84
|
+
* dnf install --assumeno nodejs22-bin 'nodejs(engine) >= 24'
|
|
85
|
+
* → installs nodejs22-bin, nodejs22, nodejs24, nodejs24-libs
|
|
86
|
+
* → and NOT nodejs24-bin
|
|
87
|
+
*
|
|
88
|
+
* Every requirement is met and `node` on `PATH` is still 22. rpm's alternatives
|
|
89
|
+
* scheme has no vocabulary for "the stream that owns the symlink must be at
|
|
90
|
+
* least 24", so an app that must have 24 on `PATH` has to check at runtime; a
|
|
91
|
+
* package cannot ask for it. Recorded here because the gap is invisible from the
|
|
92
|
+
* emitted `Requires:` line, which looks correct and is.
|
|
93
|
+
*
|
|
94
|
+
* (`nodejs24` carries no bare `nodejs` Provide at all — only `nodejs22` does on
|
|
95
|
+
* F44, which is the other half of why the epoch trap above bites.)
|
|
96
|
+
*
|
|
97
|
+
* A `Record`, for the reason {@link SCHEMA_COMPILER_PACKAGE} records: a ternary
|
|
98
|
+
* reads as a two-way choice and is really "deb, or ELSE rpm's name", and a third
|
|
99
|
+
* format silently inheriting rpm's spelling has happened here before, at exit 0.
|
|
100
|
+
*/
|
|
101
|
+
const NODE_PACKAGE = { deb: 'nodejs', rpm: 'nodejs(engine)' };
|
|
38
102
|
/**
|
|
39
103
|
* The package providing `glib-compile-schemas`, per format.
|
|
40
104
|
*
|
|
@@ -78,6 +142,49 @@ export function warnAboutGjsFloor(format, floor) {
|
|
|
78
142
|
'to make apt happy, because the result is an install that succeeds and an app that does not start.',
|
|
79
143
|
];
|
|
80
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Node majors per DEB suite, as measured 2026-08-28 — see {@link warnAboutNodeFloor}.
|
|
147
|
+
*/
|
|
148
|
+
const DEBIAN_NODE = 'trixie ships 20, forky 24; Ubuntu 24.04 LTS ships 18 and 26.04 LTS 22';
|
|
149
|
+
/**
|
|
150
|
+
* Which formats the Node-floor warning is ABOUT.
|
|
151
|
+
*
|
|
152
|
+
* `rpm` is false, and it is a measured fact about Fedora rather than a default:
|
|
153
|
+
* F43/44/45 default to Node 22 but ship `nodejs24` in the base repository,
|
|
154
|
+
* parallel-installable, so `nodejs(engine) >= 24` resolves without an extra
|
|
155
|
+
* repository. Debian and Ubuntu have no such stream packages — one `nodejs` per
|
|
156
|
+
* suite, and it is older than 24 in every current stable/LTS.
|
|
157
|
+
*
|
|
158
|
+
* A `Record` and not `format !== 'deb'`, for the reason
|
|
159
|
+
* {@link GJS_FLOOR_IS_DEBIAN_NEWS} spells out: the negative form answers "stay
|
|
160
|
+
* quiet" for every format that will ever exist, including ones nobody has
|
|
161
|
+
* checked a distribution for.
|
|
162
|
+
*/
|
|
163
|
+
const NODE_FLOOR_IS_DEBIAN_NEWS = { deb: true, rpm: false };
|
|
164
|
+
/**
|
|
165
|
+
* Warn when the Node floor cannot be satisfied by a released Debian or Ubuntu.
|
|
166
|
+
* Returns the warning lines; empty when the floor is fine.
|
|
167
|
+
*
|
|
168
|
+
* Only reached when the payload actually needs an interpreter — a `--app gjs`
|
|
169
|
+
* package declares no Node dependency, so warning about its floor would be
|
|
170
|
+
* noise attached to a line that is never emitted.
|
|
171
|
+
*/
|
|
172
|
+
export function warnAboutNodeFloor(format, floor) {
|
|
173
|
+
if (!NODE_FLOOR_IS_DEBIAN_NEWS[format])
|
|
174
|
+
return [];
|
|
175
|
+
// Ubuntu 26.04 LTS's 22 is the newest a current DEB stable/LTS carries;
|
|
176
|
+
// trixie's 20 is older still. A floor of 22 or below is satisfiable
|
|
177
|
+
// somewhere released and stays quiet.
|
|
178
|
+
if (compareVersions(floor, '22') <= 0)
|
|
179
|
+
return [];
|
|
180
|
+
return [
|
|
181
|
+
`gjsify ship: \`Depends: nodejs (>= ${floor})\` is not satisfiable on any current DEB stable/LTS — ` +
|
|
182
|
+
`${DEBIAN_NODE}. The package installs on forky/sid and on distributions with a newer Node. Set ` +
|
|
183
|
+
'`gjsify.ship.minNodeVersion` if this bundle genuinely runs on an older Node; do NOT lower it ' +
|
|
184
|
+
'to make apt happy, because the result is an install that succeeds and an app that dies on the ' +
|
|
185
|
+
'first syntax or API the older interpreter does not have.',
|
|
186
|
+
];
|
|
187
|
+
}
|
|
81
188
|
/** Numeric dotted-version compare. Enough for `1.82.3` vs `1.86`. */
|
|
82
189
|
function compareVersions(a, b) {
|
|
83
190
|
const left = a.split('.').map(Number);
|
|
@@ -155,7 +262,14 @@ const BUNDLED_TYPELIB = /^Gjsify[A-Z]/;
|
|
|
155
262
|
* @throws when a namespace has no entry in the table — see the module header.
|
|
156
263
|
*/
|
|
157
264
|
export function deriveDepends(format, inputs) {
|
|
158
|
-
|
|
265
|
+
// The interpreter the launcher execs, and nothing else: a package declares ONE.
|
|
266
|
+
// Linux is the only place this is a dependency at all — macOS and Windows have
|
|
267
|
+
// no system Node, so an artifact for those CARRIES one from
|
|
268
|
+
// `@gjsify/node-runtime-<target>`, and that is the whole reason those three
|
|
269
|
+
// packages exist and this one line is their opposite.
|
|
270
|
+
const out = inputs.interpreter === 'node'
|
|
271
|
+
? [`${NODE_PACKAGE[format]} >= ${inputs.minNodeVersion ?? DEFAULT_NODE_FLOOR}`]
|
|
272
|
+
: [`gjs >= ${inputs.minGjsVersion ?? DEFAULT_GJS_FLOOR}`];
|
|
159
273
|
const unmapped = [];
|
|
160
274
|
// Namespaces the package SHIPS ITSELF, read off the staged filenames rather than from a
|
|
161
275
|
// separate declaration: `Gwebgl-0.1.typelib` → `Gwebgl-0.1` and `Gwebgl`. Deriving it is the
|
|
@@ -206,8 +320,8 @@ export function deriveDepends(format, inputs) {
|
|
|
206
320
|
if (inputs.hasSchemas)
|
|
207
321
|
out.push(SCHEMA_COMPILER_PACKAGE[format]);
|
|
208
322
|
out.push(...inputs.extra);
|
|
209
|
-
// Set-dedupe keeps first-insertion order, so
|
|
210
|
-
// list is stable across runs.
|
|
323
|
+
// Set-dedupe keeps first-insertion order, so the interpreter stays first and
|
|
324
|
+
// the list is stable across runs.
|
|
211
325
|
return [...new Set(out)];
|
|
212
326
|
}
|
|
213
327
|
/**
|