@gjsify/cli 0.44.0 → 0.46.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 +202 -167
- 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 +850 -58
- package/lib/commands/trust.d.ts +6 -0
- package/lib/commands/trust.js +1 -1
- package/lib/commands/upgrade.d.ts +23 -0
- package/lib/commands/upgrade.js +95 -12
- package/lib/templates/install.mjs.tmpl +2 -2
- package/lib/types/config-data.d.ts +127 -1
- package/lib/utils/gi-search-path.js +11 -3
- 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 +103 -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 +108 -7
- package/lib/utils/ship/formats.js +778 -15
- package/lib/utils/ship/launcher.d.ts +13 -3
- package/lib/utils/ship/launcher.js +419 -17
- package/lib/utils/ship/layout.d.ts +330 -0
- package/lib/utils/ship/layout.js +504 -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 +434 -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 +599 -14
- package/lib/utils/ship/pe-launcher.d.ts +29 -0
- package/lib/utils/ship/pe-launcher.js +663 -0
- package/lib/utils/ship/plan.d.ts +21 -0
- package/lib/utils/ship/plan.js +55 -7
- package/lib/utils/ship/plist.d.ts +98 -0
- package/lib/utils/ship/plist.js +256 -0
- package/lib/utils/ship/rpm.js +16 -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 +91 -2
- package/lib/utils/ship/settings.js +93 -1
- package/lib/utils/ship/share-dirs.d.ts +53 -0
- package/lib/utils/ship/share-dirs.js +76 -0
- package/lib/utils/ship/signing.d.ts +311 -0
- package/lib/utils/ship/signing.js +506 -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 +91 -2
- package/lib/utils/ship/types.d.ts +191 -14
- 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/system-gi.d.ts +20 -5
- package/lib/utils/system-gi.js +36 -13
- package/lib/utils/trust-registry.js +5 -0
- package/package.json +19 -19
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { Layout, LayoutIdentity } from './layout.js';
|
|
2
|
+
import { type ResolveInstalledPackageOptions } from '../resolve-npm-package.js';
|
|
3
|
+
import type { StagedFile } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The targets a relocated GTK closure is published for.
|
|
6
|
+
*
|
|
7
|
+
* The same three `@gjsify/gtk-runtime-*` packages node-gi resolves, and
|
|
8
|
+
* deliberately NOT derived from {@link NODE_RUNTIME_TARGETS}: the two lists agree
|
|
9
|
+
* today by coincidence of which platforms lack a dependable system stack, and
|
|
10
|
+
* deriving one from the other would make a future Linux GTK bundle silently imply
|
|
11
|
+
* a bundled Linux interpreter (which `node-runtime.ts` refuses on purpose).
|
|
12
|
+
*/
|
|
13
|
+
export declare const GTK_RUNTIME_TARGETS: readonly ["darwin-arm64", "darwin-x64", "win32-x64"];
|
|
14
|
+
export type GtkRuntimeTarget = (typeof GTK_RUNTIME_TARGETS)[number];
|
|
15
|
+
/** Is this `<platform>-<arch>` one of the published GTK-closure targets? */
|
|
16
|
+
export declare function isGtkRuntimeTarget(target: string): target is GtkRuntimeTarget;
|
|
17
|
+
/** The npm name carrying the relocated closure for a target. One spelling, derived. */
|
|
18
|
+
export declare function gtkRuntimePackageName(target: GtkRuntimeTarget): string;
|
|
19
|
+
/** The addon's filename, node-gi's `ADDON_FILENAME`. Identical in every prebuild. */
|
|
20
|
+
export declare const NODE_GI_ADDON_FILENAME = "node_gi.node";
|
|
21
|
+
/** The package whose `prebuilds/<target>/` holds the addon and, in CI, the closure beside it. */
|
|
22
|
+
export declare const NODE_GI_PACKAGE = "@gjsify/node-gi";
|
|
23
|
+
/** Where the relocated GTK closure is, once found. */
|
|
24
|
+
export interface ResolvedGtkRuntime {
|
|
25
|
+
/** What produced the hit — an env var name, or the npm name it was resolved from. */
|
|
26
|
+
source: string;
|
|
27
|
+
/** The bundle directory: the one holding `lib/`, `girepository-1.0/`, `share/`. */
|
|
28
|
+
dir: string;
|
|
29
|
+
/** `<dir>/lib` on darwin, `<dir>/bin` on win32 — the loadable-code directory. */
|
|
30
|
+
libDir: string;
|
|
31
|
+
/** `<dir>/girepository-1.0`. */
|
|
32
|
+
typelibDir: string;
|
|
33
|
+
}
|
|
34
|
+
/** Where the node-gi addon is, once found. */
|
|
35
|
+
export interface ResolvedNodeGiAddon {
|
|
36
|
+
/** The npm name it was resolved from. */
|
|
37
|
+
source: string;
|
|
38
|
+
/** Absolute path to `node_gi.node`. */
|
|
39
|
+
addonPath: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ResolveRuntimeOptions {
|
|
42
|
+
/** Project directory to resolve from — the consumer's, not gjsify's. */
|
|
43
|
+
cwd?: string;
|
|
44
|
+
/** Environment to read the overrides from. Injected so it is testable. */
|
|
45
|
+
env?: Record<string, string | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* TEST SEAM, passed through to {@link resolveInstalledPackage} — resolve a
|
|
48
|
+
* specifier from one anchor. A fake answering the SAME path for both anchors
|
|
49
|
+
* is Bun's global-cache behaviour, which is otherwise unreachable from Node.
|
|
50
|
+
*/
|
|
51
|
+
resolve?: ResolveInstalledPackageOptions['resolve'];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Locate the relocated GTK closure for `target`, or `null`.
|
|
55
|
+
*
|
|
56
|
+
* The search order MIRRORS `resolveGtkRuntimeBundle()` rather than inventing a
|
|
57
|
+
* second one, because the two have to agree: this function decides what gets
|
|
58
|
+
* copied into the bundle, and that function decides what the bundle finds at
|
|
59
|
+
* runtime. Four candidates, first complete hit wins:
|
|
60
|
+
*
|
|
61
|
+
* 1. `GJSIFY_GTK_RUNTIME` — an explicit bundle directory, for a maintainer
|
|
62
|
+
* holding an unpublished or patched closure. The same variable the launcher
|
|
63
|
+
* later EXPORTS, and the same precedence it has there.
|
|
64
|
+
* 2. `@gjsify/node-gi`'s own `prebuilds/<target>/gtk`. This is the layout the
|
|
65
|
+
* macOS CI legs stage by hand (`node-gi.yml`: download the bundle artifact
|
|
66
|
+
* into `prebuilds/darwin-<arch>/gtk`, the addon beside it), so a job that has
|
|
67
|
+
* prepared node-gi for a conformance run has, by that act alone, prepared it
|
|
68
|
+
* for a ship run.
|
|
69
|
+
* 3. the sibling monorepo directory `../gtk-runtime-<target>/gtk`, which is what
|
|
70
|
+
* an in-repo `npm run build:bundle` writes.
|
|
71
|
+
* 4. the published `@gjsify/gtk-runtime-<target>` package's `gtk/`.
|
|
72
|
+
*
|
|
73
|
+
* A candidate counts only when it holds BOTH the loadable-code directory and the
|
|
74
|
+
* typelib directory — node-gi's own probe, restated here rather than weakened,
|
|
75
|
+
* because a directory that passes here and fails there is a `.app` that ships
|
|
76
|
+
* 200 MB and still cannot open a window.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveGtkRuntime(target: GtkRuntimeTarget, options?: ResolveRuntimeOptions): ResolvedGtkRuntime | null;
|
|
79
|
+
/**
|
|
80
|
+
* Locate the node-gi addon built for `target`, or `null`.
|
|
81
|
+
*
|
|
82
|
+
* ONE source, and no override. The addon and the closure ship as a matched pair —
|
|
83
|
+
* the addon's `@rpath` is `@loader_path/gtk/lib`, so it names the very directory
|
|
84
|
+
* the closure occupies — and `@gjsify/node-gi` publishes `prebuilds/` for all five
|
|
85
|
+
* of its platforms in ONE package, so resolving the package by name is already
|
|
86
|
+
* enough to reach a foreign target's binary from a Linux host. A second
|
|
87
|
+
* environment knob here would be a way to pair an addon with a closure it was not
|
|
88
|
+
* built against, which is #910's failure shape wearing a convenience.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveNodeGiAddon(target: GtkRuntimeTarget, options?: ResolveRuntimeOptions): ResolvedNodeGiAddon | null;
|
|
91
|
+
/** node-gi's JavaScript, as package-relative paths. */
|
|
92
|
+
export interface ResolvedNodeGiPackage {
|
|
93
|
+
source: string;
|
|
94
|
+
/** The package root the files are relative to. */
|
|
95
|
+
root: string;
|
|
96
|
+
/** POSIX-separated, package-relative — `package.json`, the root `.js`, `overrides/**`. */
|
|
97
|
+
files: string[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The files a SHIPPED `.app` needs from `@gjsify/node-gi`, or `null`.
|
|
101
|
+
*
|
|
102
|
+
* NOT the published tarball. `files` in node-gi's manifest also lists `src/`,
|
|
103
|
+
* `binding.gyp` and `scripts/install.mjs` — the inputs to `node-gyp`, which a
|
|
104
|
+
* `.app` never runs — and the `.d.ts` declarations, which nothing inside a bundle
|
|
105
|
+
* compiles against. And it lists `prebuilds/`, which this module stages into
|
|
106
|
+
* `Contents/Frameworks` instead, where a `.app` keeps loadable code.
|
|
107
|
+
*
|
|
108
|
+
* So the rule is what `require('@gjsify/node-gi/<subpath>')` can REACH:
|
|
109
|
+
* `package.json` (the `exports` map is what resolves every subpath), every `.js`
|
|
110
|
+
* at the package root (every `exports` target is one), and `overrides/`, which
|
|
111
|
+
* `gi.js` loads at runtime. `tests/e2e/ship-macos` holds that rule against the
|
|
112
|
+
* REAL package's `exports` map, so a node-gi release that adds an entry outside
|
|
113
|
+
* this set fails there rather than in a `.app` a stranger downloaded.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveNodeGiPackage(options?: ResolveRuntimeOptions): ResolvedNodeGiPackage | null;
|
|
116
|
+
/**
|
|
117
|
+
* Where each piece of the runtime lands inside the bundle — STAGE-relative.
|
|
118
|
+
*
|
|
119
|
+
* PURE, and derived from `Layout.dirs` rather than from a second set of string
|
|
120
|
+
* literals, so the paths the launcher exports and the paths the stager writes
|
|
121
|
+
* cannot drift: they are computed here once and both callers read this result.
|
|
122
|
+
*
|
|
123
|
+
* `Contents/Frameworks/node-gi/prebuilds/<target>/` is node-gi's OWN sibling
|
|
124
|
+
* layout reproduced verbatim inside `dirs.native`. It is not a naming preference:
|
|
125
|
+
* `resolveGtkRuntimeBundle()` probes `<dir>/lib` and `<dir>/girepository-1.0`,
|
|
126
|
+
* the addon's `@rpath` is `@loader_path/gtk/lib`, and `loaders.cache` addresses
|
|
127
|
+
* each pixbuf loader relative to the bundle toplevel. Reproducing the shape is
|
|
128
|
+
* what makes every one of those relations still true after the copy.
|
|
129
|
+
*/
|
|
130
|
+
export interface AppRuntimePaths {
|
|
131
|
+
/** `<App>.app/Contents/Frameworks/node-gi` — the package root the layout imitates. */
|
|
132
|
+
nodeGiRoot: string;
|
|
133
|
+
/**
|
|
134
|
+
* `<App>.app/Contents/Resources/lib/node_modules/@gjsify/node-gi` — where
|
|
135
|
+
* node-gi's JAVASCRIPT goes.
|
|
136
|
+
*
|
|
137
|
+
* Not a preference: `require('@gjsify/node-gi/gi')` from the staged bundle at
|
|
138
|
+
* `Contents/Resources/lib/<bundle>.mjs` walks `node_modules` upward from that
|
|
139
|
+
* file's own directory, and this is the FIRST directory that walk visits. Any
|
|
140
|
+
* other location works only by accident of what sits above the bundle.
|
|
141
|
+
*/
|
|
142
|
+
nodeGiPackageDir: string;
|
|
143
|
+
/** `<nodeGiRoot>/prebuilds/<target>` — the addon and the closure, siblings. */
|
|
144
|
+
prebuildDir: string;
|
|
145
|
+
/** `<prebuildDir>/gtk` — what `GJSIFY_GTK_RUNTIME` names. */
|
|
146
|
+
gtkDir: string;
|
|
147
|
+
/** `<prebuildDir>/node_gi.node` — what `NODE_GI_NATIVE` pins. */
|
|
148
|
+
addonPath: string;
|
|
149
|
+
/**
|
|
150
|
+
* `<App>.app/Contents/MacOS/node`, or `<program dir>/node.exe` — beside the
|
|
151
|
+
* launcher that execs it, under the name the target's own release uses.
|
|
152
|
+
*/
|
|
153
|
+
interpreterPath: string;
|
|
154
|
+
/** Node's own LICENSE, under `Contents/Resources` (macOS) or `share/` (Windows). */
|
|
155
|
+
interpreterLicensePath: string;
|
|
156
|
+
}
|
|
157
|
+
export declare function appRuntimePaths(layout: Layout, identity: LayoutIdentity, target: GtkRuntimeTarget): AppRuntimePaths;
|
|
158
|
+
/** What the launcher must export and exec, once the runtime is staged. Absent member = not staged. */
|
|
159
|
+
export interface LauncherRuntime {
|
|
160
|
+
/** Stage-relative path of the interpreter the bundle carries. */
|
|
161
|
+
interpreter?: string;
|
|
162
|
+
/** Stage-relative path of the GTK closure — `GJSIFY_GTK_RUNTIME`. */
|
|
163
|
+
gtkRuntimeDir?: string;
|
|
164
|
+
/** Stage-relative path of the node-gi addon — `NODE_GI_NATIVE`. */
|
|
165
|
+
nodeGiAddon?: string;
|
|
166
|
+
}
|
|
167
|
+
/** The staged runtime, and an account of what was and was not found. */
|
|
168
|
+
export interface StagedAppRuntime {
|
|
169
|
+
/** Stage-relative files, already placed — they never passed through the prefix-relative plan. */
|
|
170
|
+
files: StagedFile[];
|
|
171
|
+
/** What the launcher can now name. */
|
|
172
|
+
launcher: LauncherRuntime;
|
|
173
|
+
/** One line per piece that WAS found, naming where it came from. */
|
|
174
|
+
found: string[];
|
|
175
|
+
/** One line per piece that was not, naming the package to install. */
|
|
176
|
+
missing: string[];
|
|
177
|
+
}
|
|
178
|
+
export interface StageAppRuntimeInput {
|
|
179
|
+
layout: Layout;
|
|
180
|
+
identity: LayoutIdentity;
|
|
181
|
+
/** `${layout.os}-${settings.arch}` — the target the payload was built for. */
|
|
182
|
+
target: GtkRuntimeTarget;
|
|
183
|
+
/** Project directory the three packages are resolved from. */
|
|
184
|
+
cwd: string;
|
|
185
|
+
/** Injected so the overrides are testable. */
|
|
186
|
+
env?: Record<string, string | undefined>;
|
|
187
|
+
/** Passed to every discovery below — see {@link ResolveRuntimeOptions.resolve}. */
|
|
188
|
+
resolve?: ResolveRuntimeOptions['resolve'];
|
|
189
|
+
/** The interpreter, already resolved — see `node-runtime.ts`. `null` when absent. */
|
|
190
|
+
interpreter: {
|
|
191
|
+
source: string;
|
|
192
|
+
nodePath: string;
|
|
193
|
+
licensePath: string;
|
|
194
|
+
} | null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Stage the interpreter, the GTK closure and the addon into one bundle.
|
|
198
|
+
*
|
|
199
|
+
* PARTIAL BY DESIGN, and reported. An all-or-nothing rule would drop a 200 MB
|
|
200
|
+
* closure that IS present because an interpreter that is not shares the milestone
|
|
201
|
+
* with it, and would do it at exit 0 — while a bundle carrying the closure and no
|
|
202
|
+
* interpreter is a real, useful intermediate (it runs wherever a Node is on
|
|
203
|
+
* `PATH`, which is every developer's machine and no stranger's). So each piece is
|
|
204
|
+
* staged when found and named when not, and `commands/ship.ts` keeps
|
|
205
|
+
* `Layout.runtimeGap` for precisely the case where the interpreter is still
|
|
206
|
+
* missing.
|
|
207
|
+
*
|
|
208
|
+
* TREE-PRESERVING for the closure and FLAT for the other two, which is the whole
|
|
209
|
+
* distinction this module exists to draw: `listFilesRecursive` walks the bundle
|
|
210
|
+
* and every relative path is carried through unchanged, so
|
|
211
|
+
* `lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg.so` is still four levels
|
|
212
|
+
* under `gtk/` on the other side of the copy.
|
|
213
|
+
*/
|
|
214
|
+
export declare function stageAppRuntime(input: StageAppRuntimeInput): StagedAppRuntime;
|
|
@@ -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
|
+
}
|