@gjsify/node-gi 0.36.0 → 0.38.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/gi.js +143 -247
- package/gtk-runtime.d.ts +9 -1
- package/gtk-runtime.js +61 -7
- package/index.d.ts +3 -0
- package/index.js +93 -617
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/scripts/install.mjs +68 -97
- package/src/addon.cc +1 -0
- package/src/common.h +1 -0
- package/src/repo.cc +26 -0
- package/system-gi.js +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "GObject-Introspection runtime for Node.js — load gi:// namespaces (GLib/GObject/Gio/…) with GJS-compatible semantics. Vendored + rewritten from node-gtk under MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/scripts/install.mjs
CHANGED
|
@@ -1,72 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// @gjsify/node-gi — the `install` lifecycle script. Use the SHIPPED PREBUILD when
|
|
4
|
-
// this package carries one for the host
|
|
5
|
-
// when it does not. The source build is the FALLBACK, not the default.
|
|
4
|
+
// this package carries one for the host; the node-gyp source build is the FALLBACK.
|
|
6
5
|
//
|
|
7
|
-
// THE FAILURE THIS REPLACES.
|
|
6
|
+
// THE FAILURE THIS REPLACES. `package.json` used to declare
|
|
8
7
|
// `"install": "node-gyp rebuild"`, which npm/yarn/pnpm run on EVERY install — a
|
|
9
|
-
// shipped prebuild could not prevent it.
|
|
10
|
-
//
|
|
11
|
-
// `
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// So on any machine without the GTK/GObject-Introspection development toolchain
|
|
18
|
-
// — i.e. every end user — `npm install @gjsify/node-gi` FAILED, prebuild or not,
|
|
19
|
-
// and it failed by uninstalling the package rather than by degrading. The
|
|
20
|
-
// prebuild was reachable only through `gjsify install`, which deliberately runs
|
|
21
|
-
// no lifecycle scripts; that asymmetry is the whole reason
|
|
22
|
-
// `gjsify showcase --runtime node` worked on Windows while a plain npm
|
|
23
|
-
// dependency on the same version could not be installed.
|
|
8
|
+
// shipped prebuild could not prevent it. So on any machine without the
|
|
9
|
+
// GTK/GObject-Introspection development toolchain (i.e. every end user)
|
|
10
|
+
// `npm install @gjsify/node-gi` FAILED even when the tarball carried the right
|
|
11
|
+
// prebuild, and it failed by npm rolling the install back rather than by degrading.
|
|
12
|
+
// The prebuild was reachable only through `gjsify install`, which deliberately runs
|
|
13
|
+
// no lifecycle scripts — which is why `gjsify showcase --runtime node` worked on
|
|
14
|
+
// Windows while a plain npm dependency on the same version could not be installed.
|
|
24
15
|
//
|
|
25
16
|
// WHY EXISTENCE, NOT LOADABILITY, IS THE GATE. The obvious-looking alternative
|
|
26
|
-
// (`node-gyp-build`) gates on
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// `
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// honest question at install time; whether it LOADS is what the release
|
|
36
|
-
// workflow's per-target load test answers (release.yml runs
|
|
37
|
-
// `NODE_GI_NATIVE=prebuild node --test test/smoke.test.mjs` on every leg), where
|
|
38
|
-
// there is a real GTK to load against.
|
|
17
|
+
// (`node-gyp-build`) gates on a bin that `require()`s the addon and falls back to a
|
|
18
|
+
// source build if the load throws. On exactly the platforms whose whole point is the
|
|
19
|
+
// prebuild that is a FALSE NEGATIVE: the win32/darwin addon's DLL/dylib closure is
|
|
20
|
+
// only findable after `gtk-runtime.js` has wired the batteries-included GTK bundle
|
|
21
|
+
// into `PATH` / `DYLD_FALLBACK_LIBRARY_PATH`, which `index.js` does at import time
|
|
22
|
+
// and a bare install-time `require()` of the `.node` never does. A load-based gate
|
|
23
|
+
// would therefore call a perfectly good prebuild broken and rebuild. Existence of the
|
|
24
|
+
// file the loader actually probes is the honest question at install time; whether it
|
|
25
|
+
// LOADS is what release.yml's per-target load test answers, against a real GTK.
|
|
39
26
|
//
|
|
40
|
-
// WHY NOT DEPEND ON `node-gyp-build` (verified, not assumed):
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// surface is otherwise just `node-addon-api`, to make a decision this
|
|
49
|
-
// package can make from its own already-shared probe.
|
|
50
|
-
// (It would, contrary to first assumption, MATCH the `node_gi.node` filename:
|
|
51
|
-
// node-gyp-build 4.8.4's `matchTags` treats a missing runtime tag as
|
|
52
|
-
// permissive — checked against the installed copy. The filename was not the
|
|
53
|
-
// blocker; the gate and the message are.)
|
|
27
|
+
// WHY NOT DEPEND ON `node-gyp-build` (verified, not assumed): that false-negative
|
|
28
|
+
// load gate; its probe order is `build/` FIRST then the prebuild, the OPPOSITE of
|
|
29
|
+
// node-gi's deliberate consumer default (`native-paths.js` `nativeCandidates`), so
|
|
30
|
+
// the two would disagree about which binary matters; it propagates a bare node-gyp
|
|
31
|
+
// exit code instead of one actionable diagnosis; and it would add a runtime
|
|
32
|
+
// dependency to a package whose dep surface is otherwise just `node-addon-api`. (It
|
|
33
|
+
// WOULD match the `node_gi.node` filename — node-gyp-build 4.8.4's `matchTags` treats
|
|
34
|
+
// a missing runtime tag as permissive. The filename was never the blocker.)
|
|
54
35
|
//
|
|
55
|
-
// THE SOURCE BUILD MUST KEEP WORKING, and does. `prebuilds/` is gitignored
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// `
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// is also forceable: `NODE_GI_BUILD_FROM_SOURCE=1` (or npm's own
|
|
63
|
-
// `--build-from-source`, which sets `npm_config_build_from_source=true`) skips
|
|
64
|
-
// the prebuild check entirely. `npm run build:native` / `build:prebuild` /
|
|
65
|
-
// `rebuild` still invoke `node-gyp` directly and are untouched by this script.
|
|
36
|
+
// THE SOURCE BUILD MUST KEEP WORKING, and does. `prebuilds/` is gitignored, so a
|
|
37
|
+
// fresh checkout has none and this script builds exactly as before — which is what
|
|
38
|
+
// node-gi.yml's `npm install --foreground-scripts` steps and release.yml's
|
|
39
|
+
// `node-gi-prebuild-*` legs rely on to produce `build/Release/node_gi.node` for
|
|
40
|
+
// `scripts/stage-prebuild.mjs`. Depending on a directory being ABSENT is too implicit
|
|
41
|
+
// to be the only guarantee, so the build is also forceable:
|
|
42
|
+
// `NODE_GI_BUILD_FROM_SOURCE=1`, or npm's own `--build-from-source`.
|
|
66
43
|
//
|
|
67
|
-
// FAIL-SAFE DIRECTION: every uncertain branch builds. Only a positively
|
|
68
|
-
//
|
|
69
|
-
//
|
|
44
|
+
// FAIL-SAFE DIRECTION: every uncertain branch builds. Only a positively confirmed
|
|
45
|
+
// prebuild file for this exact host skips the build, so a bug here degrades to the
|
|
46
|
+
// previous behaviour rather than to a package with no addon.
|
|
70
47
|
import { existsSync, readFileSync } from 'node:fs';
|
|
71
48
|
import { spawnSync } from 'node:child_process';
|
|
72
49
|
import { createRequire } from 'node:module';
|
|
@@ -75,15 +52,13 @@ import { hostTarget, packageRoot, prebuildAddonPath } from '../native-paths.js';
|
|
|
75
52
|
|
|
76
53
|
const target = hostTarget();
|
|
77
54
|
|
|
78
|
-
/** `1`/`true` on an env var, tolerant of the shapes people actually type. */
|
|
79
55
|
const isEnabled = (value) => value === '1' || value === 'true';
|
|
80
56
|
|
|
81
57
|
/**
|
|
82
|
-
* Did the user explicitly ask for a source build? `
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* it.
|
|
58
|
+
* Did the user explicitly ask for a source build? `npm_config_build_from_source` is
|
|
59
|
+
* what npm exports for `--build-from-source` (the flag every node-gyp-build package
|
|
60
|
+
* honours), so CI or a maintainer can force the build without knowing node-gi's own
|
|
61
|
+
* env var name.
|
|
87
62
|
*/
|
|
88
63
|
const buildFromSourceRequested = () =>
|
|
89
64
|
isEnabled(process.env.NODE_GI_BUILD_FROM_SOURCE) || process.env.npm_config_build_from_source === 'true';
|
|
@@ -102,15 +77,14 @@ function declaredPlatforms() {
|
|
|
102
77
|
}
|
|
103
78
|
|
|
104
79
|
/**
|
|
105
|
-
* Run `node-gyp rebuild` in the package root, inheriting stdio so the real
|
|
106
|
-
*
|
|
107
|
-
*
|
|
80
|
+
* Run `node-gyp rebuild` in the package root, inheriting stdio so the real compiler
|
|
81
|
+
* output stays visible — it is the actual diagnosis; the framed message below only
|
|
82
|
+
* tells the reader what to DO about it.
|
|
108
83
|
*
|
|
109
|
-
* node-gyp is resolved the way node-gyp-build's own bin does: prefer node-gyp's
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* @returns {{ status: number, error?: Error }}
|
|
84
|
+
* node-gyp is resolved the way node-gyp-build's own bin does: prefer node-gyp's JS
|
|
85
|
+
* entry if it happens to be resolvable, else the `node-gyp` shim that npm, yarn and
|
|
86
|
+
* pnpm all put on `PATH` for lifecycle scripts (`node-gyp.cmd` on Windows, hence
|
|
87
|
+
* `shell` there).
|
|
114
88
|
*/
|
|
115
89
|
function runNodeGyp() {
|
|
116
90
|
const win32 = process.platform === 'win32';
|
|
@@ -118,11 +92,11 @@ function runNodeGyp() {
|
|
|
118
92
|
let args = ['rebuild'];
|
|
119
93
|
let shell = win32;
|
|
120
94
|
try {
|
|
121
|
-
// Only succeeds when node-gyp is genuinely resolvable from here; npm's
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// `import.meta.resolve` — the latter is only synchronous from Node 20.6,
|
|
125
|
-
//
|
|
95
|
+
// Only succeeds when node-gyp is genuinely resolvable from here; npm's bundled
|
|
96
|
+
// copy lives in npm's own tree and is not, which is why the PATH shim above
|
|
97
|
+
// stays the normal path. `createRequire().resolve` rather than
|
|
98
|
+
// `import.meta.resolve` — the latter is only synchronous from Node 20.6, and
|
|
99
|
+
// `engines.node` is `>=20`.
|
|
126
100
|
const entry = createRequire(import.meta.url).resolve('node-gyp/bin/node-gyp.js');
|
|
127
101
|
command = process.execPath;
|
|
128
102
|
args = [entry, 'rebuild'];
|
|
@@ -135,12 +109,11 @@ function runNodeGyp() {
|
|
|
135
109
|
}
|
|
136
110
|
|
|
137
111
|
/**
|
|
138
|
-
* Are we running inside a source CHECKOUT rather than an installed tarball?
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* packaging regression the consumer should report instead of debugging.
|
|
112
|
+
* Are we running inside a source CHECKOUT rather than an installed tarball? Keyed on
|
|
113
|
+
* two paths `files` deliberately does not publish (`test/` and the staging tool),
|
|
114
|
+
* because the distinction changes the advice: in a checkout `prebuilds/` is gitignored
|
|
115
|
+
* so its absence is expected and the source build IS the path; in an installed copy a
|
|
116
|
+
* missing prebuild for a PROMISED target is a packaging regression to report.
|
|
144
117
|
*/
|
|
145
118
|
function isRepoCheckout() {
|
|
146
119
|
return existsSync(join(packageRoot, 'test')) || existsSync(join(packageRoot, 'scripts', 'stage-prebuild.mjs'));
|
|
@@ -149,12 +122,11 @@ function isRepoCheckout() {
|
|
|
149
122
|
/**
|
|
150
123
|
* ONE actionable message for "no usable prebuild AND the source build did not
|
|
151
124
|
* succeed" — the case that used to surface as nothing but a node-gyp stack. It
|
|
152
|
-
* distinguishes the three
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* 0.26.0 shipped `prebuilds/linux-*` only while promising four targets — and
|
|
125
|
+
* distinguishes the three reasons the prebuild was missing, because the reader's next
|
|
126
|
+
* action differs each time: a checkout is SUPPOSED to build; an undeclared platform
|
|
127
|
+
* means "build it yourself or use a supported target"; a DECLARED platform with no
|
|
128
|
+
* artifact in an installed copy is a packaging regression to report, not to work
|
|
129
|
+
* around (0.26.0 shipped `prebuilds/linux-*` only while promising four targets, and
|
|
158
130
|
* nothing told the consumer).
|
|
159
131
|
*/
|
|
160
132
|
function reportFailure(status, spawnError) {
|
|
@@ -204,8 +176,8 @@ function reportFailure(status, spawnError) {
|
|
|
204
176
|
|
|
205
177
|
function main() {
|
|
206
178
|
// Deliberately first: an explicit "do not build" wins over everything, so a
|
|
207
|
-
// consumer on an unsupported platform can complete an install and supply an
|
|
208
|
-
//
|
|
179
|
+
// consumer on an unsupported platform can complete an install and supply an addon
|
|
180
|
+
// via NODE_GI_NATIVE afterwards.
|
|
209
181
|
if (isEnabled(process.env.NODE_GI_SKIP_NATIVE_BUILD)) {
|
|
210
182
|
console.log(
|
|
211
183
|
`@gjsify/node-gi: NODE_GI_SKIP_NATIVE_BUILD is set — skipping the native addon for ${target}. ` +
|
|
@@ -227,11 +199,10 @@ function main() {
|
|
|
227
199
|
const { status, error } = runNodeGyp();
|
|
228
200
|
if (status === 0 && !error) return 0;
|
|
229
201
|
reportFailure(status, error);
|
|
230
|
-
// Fail the install rather than half-install. This package IS its native
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// wants the other trade.
|
|
202
|
+
// Fail the install rather than half-install. This package IS its native addon: a
|
|
203
|
+
// zero exit here would trade a clear install-time failure for a deferred `import`
|
|
204
|
+
// failure in the consumer's own code. NODE_GI_SKIP_NATIVE_BUILD is the opt-in for
|
|
205
|
+
// anyone who wants the other trade.
|
|
235
206
|
return status === 0 ? 1 : status;
|
|
236
207
|
}
|
|
237
208
|
|
package/src/addon.cc
CHANGED
|
@@ -74,6 +74,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
74
74
|
exports.Set("getErrorDomain", Napi::Function::New(env, GetErrorDomain));
|
|
75
75
|
exports.Set("setErrorBuilder", Napi::Function::New(env, SetErrorBuilder));
|
|
76
76
|
exports.Set("prependSearchPath", Napi::Function::New(env, PrependSearchPath));
|
|
77
|
+
exports.Set("prependLibraryPath", Napi::Function::New(env, PrependLibraryPath));
|
|
77
78
|
exports.Set("callFunction", Napi::Function::New(env, CallFunction));
|
|
78
79
|
exports.Set("callMethod", Napi::Function::New(env, CallMethod));
|
|
79
80
|
exports.Set("hasMethod", Napi::Function::New(env, HasMethod));
|
package/src/common.h
CHANGED
|
@@ -550,6 +550,7 @@ Napi::Value GetEnumValues(const Napi::CallbackInfo& info);
|
|
|
550
550
|
Napi::Value GetErrorDomain(const Napi::CallbackInfo& info);
|
|
551
551
|
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info);
|
|
552
552
|
Napi::Value PrependSearchPath(const Napi::CallbackInfo& info);
|
|
553
|
+
Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info);
|
|
553
554
|
|
|
554
555
|
// calls.cc
|
|
555
556
|
Napi::Value CallFunction(const Napi::CallbackInfo& info);
|
package/src/repo.cc
CHANGED
|
@@ -295,4 +295,30 @@ Napi::Value PrependSearchPath(const Napi::CallbackInfo& info) {
|
|
|
295
295
|
return env.Undefined();
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// prependLibraryPath(path: string) -> void
|
|
299
|
+
// The LIBRARY twin of PrependSearchPath: where GI looks for the shared object a
|
|
300
|
+
// typelib names, as opposed to where it looks for the typelib itself.
|
|
301
|
+
//
|
|
302
|
+
// Native rather than `requireGi('GIRepository').…` because the bundled runtime
|
|
303
|
+
// ships GIRepository 3.0, whose `Repository` this bridge exposes neither as
|
|
304
|
+
// constructible nor with a `get_default()` static — measured, both throw. The C
|
|
305
|
+
// entry point has no such gap and reuses the same `DupDefaultRepository()` handle.
|
|
306
|
+
//
|
|
307
|
+
// It is the env-free repair for a typelib backer named by BARE LEAF: no variable,
|
|
308
|
+
// no re-exec, identical on node, bun and deno. See `gtk-runtime.js`
|
|
309
|
+
// (`activateGiLibraryPath`) for the incident.
|
|
310
|
+
Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info) {
|
|
311
|
+
Napi::Env env = info.Env();
|
|
312
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
313
|
+
Napi::TypeError::New(env, "prependLibraryPath(path: string)")
|
|
314
|
+
.ThrowAsJavaScriptException();
|
|
315
|
+
return env.Undefined();
|
|
316
|
+
}
|
|
317
|
+
std::string path = info[0].As<Napi::String>().Utf8Value();
|
|
318
|
+
GIRepository* repo = DupDefaultRepository();
|
|
319
|
+
gi_repository_prepend_library_path(repo, path.c_str());
|
|
320
|
+
g_object_unref(repo);
|
|
321
|
+
return env.Undefined();
|
|
322
|
+
}
|
|
323
|
+
|
|
298
324
|
} // namespace nodegi
|
package/system-gi.js
CHANGED
|
@@ -227,3 +227,43 @@ export function systemGiLibraryDirs({
|
|
|
227
227
|
export function pathCovers(wanted, current) {
|
|
228
228
|
return wanted.every((dir) => current.includes(dir));
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* dyld's OWN default fallback list, verbatim from `dyld(1)`:
|
|
233
|
+
* `$HOME/lib:/usr/local/lib:/lib:/usr/lib`.
|
|
234
|
+
*
|
|
235
|
+
* Load-bearing because `DYLD_FALLBACK_LIBRARY_PATH` REPLACES this list rather
|
|
236
|
+
* than extending it, so a composition that omits part of it hands the child a
|
|
237
|
+
* SMALLER search path than the same child would have had with the variable
|
|
238
|
+
* unset. MEASURED, and it cost a whole platform: both writers used to append a
|
|
239
|
+
* bare `/usr/lib`, dropping `/usr/local/lib` — where every Homebrew GTK library
|
|
240
|
+
* lives on Intel macOS — so every `gjsify` gjs command on such a Mac died in
|
|
241
|
+
* `dlopen(libsoup-3.0.0.dylib)` before it had downloaded anything (the CLI's own
|
|
242
|
+
* fetcher goes through Soup), and so did the Node-free bootstrap the Getting
|
|
243
|
+
* Started page prints. The tell that it was OURS: plain
|
|
244
|
+
* `gjs -c 'imports.gi.Soup'` loads fine with no DYLD variable set.
|
|
245
|
+
* @param {Record<string, string | undefined>} [env]
|
|
246
|
+
* @returns {string[]}
|
|
247
|
+
*/
|
|
248
|
+
export function dyldDefaultFallbackDirs(env = process.env) {
|
|
249
|
+
const home = env['HOME'];
|
|
250
|
+
return [...(home ? [`${home}/lib`] : []), '/usr/local/lib', '/lib', '/usr/lib'];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Compose a `DYLD_FALLBACK_LIBRARY_PATH` value: `wanted`, then the launching
|
|
255
|
+
* environment's own value, then {@link dyldDefaultFallbackDirs} —
|
|
256
|
+
* unconditionally, because the target is a CHILD (a re-exec, or a spawned gjs)
|
|
257
|
+
* and it must never search less than one launched without gjsify. Deduplicated:
|
|
258
|
+
* `systemGiLibraryDirs()` can legitimately yield a directory the tail also
|
|
259
|
+
* names, and a repeat costs the loader a second stat on every miss. First
|
|
260
|
+
* occurrence wins everywhere, so dropping later repeats preserves behaviour.
|
|
261
|
+
* @param {readonly string[]} wanted
|
|
262
|
+
* @param {Record<string, string | undefined>} [env]
|
|
263
|
+
* @returns {string}
|
|
264
|
+
*/
|
|
265
|
+
export function composeDyldFallback(wanted, env = process.env) {
|
|
266
|
+
return [
|
|
267
|
+
...new Set([...wanted, ...splitSearchPath(env['DYLD_FALLBACK_LIBRARY_PATH']), ...dyldDefaultFallbackDirs(env)]),
|
|
268
|
+
].join(':');
|
|
269
|
+
}
|