@gjsify/node-gi 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/README.md +8 -0
- package/gi.js +20 -0
- package/gtk-runtime.d.ts +4 -1
- package/gtk-runtime.js +67 -8
- 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/win32-x64/node_gi.node +0 -0
package/README.md
CHANGED
|
@@ -55,6 +55,14 @@ vendored as-is — gjsify ships its own dual (GJS + Node) example/test infra.
|
|
|
55
55
|
specific target, so each new bundle is found by the mechanism that found the
|
|
56
56
|
first. The loader contract and the per-platform measurements are
|
|
57
57
|
[docs/node-gi-platform-notes.md](../../../docs/node-gi-platform-notes.md).
|
|
58
|
+
- An application that carries **its own** GI library — a typelib plus the shared
|
|
59
|
+
library behind it, staged beside the app rather than installed — names those
|
|
60
|
+
directories in `GJSIFY_GI_LIBRARY_PATH` (`:`-separated; `;` on Windows;
|
|
61
|
+
absolute paths only). node-gi reads it in JS and hands the directories to
|
|
62
|
+
`gi_repository_prepend_library_path()`, so GI can open the bare leaf its own
|
|
63
|
+
typelib records. This is the only route that survives on macOS: dyld strips
|
|
64
|
+
every `DYLD_*` from a restricted process, so a signed `.app` cannot use one.
|
|
65
|
+
Entries take precedence over the GTK bundle / system GI libdirs.
|
|
58
66
|
|
|
59
67
|
## Install
|
|
60
68
|
|
package/gi.js
CHANGED
|
@@ -70,6 +70,26 @@ function unwrapArg(value) {
|
|
|
70
70
|
if (value !== null && typeof value === 'object' && value[HANDLE] !== undefined) {
|
|
71
71
|
return value[HANDLE];
|
|
72
72
|
}
|
|
73
|
+
// An ARRAY is a container whose ELEMENTS need the same unwrapping, and until this
|
|
74
|
+
// line they did not get it: only the top-level argument was unwrapped, so an array
|
|
75
|
+
// of GObjects reached the engine as an array of JS wrappers and
|
|
76
|
+
// `NodeGiToGIArgument`'s GI_TYPE_TAG_INTERFACE branch — which accepts nothing but
|
|
77
|
+
// an External carrying the handle — threw
|
|
78
|
+
// `expected a GObject handle as a container element`. That is EVERY GI method
|
|
79
|
+
// taking an array of objects, not one method: measured on
|
|
80
|
+
// `Gio.ListStore.splice(0, n, [a, b])`, where `append(a)` on the same store
|
|
81
|
+
// succeeded because one GObject as a top-level arg was always unwrapped.
|
|
82
|
+
//
|
|
83
|
+
// `Array.isArray`, not `typeof value === 'object'`: a Uint8Array is a
|
|
84
|
+
// GByteArray/guint8[] argument the engine reads directly, and mapping over it here
|
|
85
|
+
// would turn it into a plain Array and lose that.
|
|
86
|
+
//
|
|
87
|
+
// Recursive, so a nested array and a GObject inside one are handled by the same
|
|
88
|
+
// rule rather than by a second one that drifts — and this is now the same shape
|
|
89
|
+
// `wrapReturn` has carried all along for the OTHER direction. That asymmetry is
|
|
90
|
+
// why the gap stayed invisible: a container coming BACK had its elements wrapped,
|
|
91
|
+
// a container going IN did not, and nothing read both lines at once.
|
|
92
|
+
if (Array.isArray(value)) return value.map(unwrapArg);
|
|
73
93
|
if (typeof value === 'function') return wrapCallbackFn(value);
|
|
74
94
|
return value;
|
|
75
95
|
}
|
package/gtk-runtime.d.ts
CHANGED
|
@@ -19,7 +19,10 @@ export function activateBundledGtkRuntime(native: {
|
|
|
19
19
|
prependLibraryPath?: (p: string) => void;
|
|
20
20
|
}): GtkRuntimeBundle | null;
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/** Absolute directories from `GJSIFY_GI_LIBRARY_PATH` — where an app carries its OWN GI libraries. */
|
|
23
|
+
export function appGiLibraryDirs(opts?: { platform?: NodeJS.Platform | string; env?: NodeJS.ProcessEnv }): string[];
|
|
24
|
+
|
|
25
|
+
/** Prepend GI's shared-library search path: the app's own dirs, then the GTK the policy chose. */
|
|
23
26
|
export function activateGiLibraryPath(native: { prependLibraryPath?: (p: string) => void }): string[];
|
|
24
27
|
/** TEST-ONLY: allow the activation to run again. */
|
|
25
28
|
export function resetGiLibraryPathForTests(): void;
|
package/gtk-runtime.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// uses the host's GTK exactly as before).
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
-
import { dirname, join } from 'node:path';
|
|
12
|
+
import { dirname, join, posix, win32 } from 'node:path';
|
|
13
13
|
import { existsSync } from 'node:fs';
|
|
14
14
|
import { composeDyldFallback, pathCovers, splitSearchPath, systemGiLibraryDirs } from './system-gi.js';
|
|
15
15
|
import { nativeCandidates, prebuildAddonPath } from './native-paths.js';
|
|
@@ -469,9 +469,49 @@ function gstScannerLeaf() {
|
|
|
469
469
|
|
|
470
470
|
let libraryPathActivated = false;
|
|
471
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Where an APPLICATION carries the shared libraries ITS OWN typelibs name by bare
|
|
474
|
+
* leaf — `GJSIFY_GI_LIBRARY_PATH`, `:`-separated (`;` on win32).
|
|
475
|
+
*
|
|
476
|
+
* THE GAP THIS CLOSES is a different one from the note below. An app shipping a GI
|
|
477
|
+
* library of its own (`gjsify.ship.bundledTypelibs`) stages the typelib and its
|
|
478
|
+
* backer side by side and points `GI_TYPELIB_PATH` there, so GI finds the typelib
|
|
479
|
+
* and then `g_module_open`s a bare leaf nothing has located. The launcher's
|
|
480
|
+
* `LD_LIBRARY_PATH` covers that on linux; on macOS no environment variable can,
|
|
481
|
+
* because dyld strips every `DYLD_*` from a restricted process and a signed,
|
|
482
|
+
* hardened `.app` IS restricted — so a launcher depending on one works unsigned and
|
|
483
|
+
* breaks at signing time. The dirs below do not cover it either: they answer "where
|
|
484
|
+
* is the GTK stack", never "where are this application's own libraries". Neither
|
|
485
|
+
* does `dirname(GI_TYPELIB_PATH entry)`: {@link systemGiLibraryDirs} derives a
|
|
486
|
+
* libdir that way because GI's INSTALL layout puts typelibs in
|
|
487
|
+
* `<libdir>/girepository-1.0/`, and an app stages the pair FLAT, so that derivation
|
|
488
|
+
* yields the directory's PARENT — a real path holding nothing.
|
|
489
|
+
*
|
|
490
|
+
* Only ABSOLUTE entries are kept: a relative one resolves against the working
|
|
491
|
+
* directory, which for a double-clicked `.app` is `/`. PURE in its inputs, so the
|
|
492
|
+
* win32 dialect is executable from any host.
|
|
493
|
+
* @param {object} [opts]
|
|
494
|
+
* @param {NodeJS.Platform | string} [opts.platform]
|
|
495
|
+
* @param {NodeJS.ProcessEnv} [opts.env]
|
|
496
|
+
* @returns {string[]} absolute directories, in the order the variable listed them
|
|
497
|
+
*/
|
|
498
|
+
export function appGiLibraryDirs({ platform = process.platform, env = process.env } = {}) {
|
|
499
|
+
const isAbsolute = platform === 'win32' ? win32.isAbsolute : posix.isAbsolute;
|
|
500
|
+
const out = [];
|
|
501
|
+
for (const dir of splitSearchPath(env.GJSIFY_GI_LIBRARY_PATH, platform === 'win32' ? ';' : ':')) {
|
|
502
|
+
if (isAbsolute(dir) && !out.includes(dir)) out.push(dir);
|
|
503
|
+
}
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
|
|
472
507
|
/**
|
|
473
508
|
* Tell GI where the shared libraries its typelibs name by BARE LEAF actually are
|
|
474
|
-
* — for whichever GTK the policy chose
|
|
509
|
+
* — for whichever GTK the policy chose plus whatever the app carries itself, and
|
|
510
|
+
* WITHOUT a loader environment variable.
|
|
511
|
+
*
|
|
512
|
+
* `GJSIFY_GI_LIBRARY_PATH` is not a counter-example: it is read by THIS process, in
|
|
513
|
+
* JS, and handed to GI through the binding. dyld never sees it — which is the whole
|
|
514
|
+
* point, because dyld is what refuses to see `DYLD_*` in a signed, hardened app.
|
|
475
515
|
*
|
|
476
516
|
* THE GAP THIS CLOSES. A typelib records its backer as `libgtk-4.1.dylib`, and on
|
|
477
517
|
* macOS neither a relocated bundle's `lib` dir nor Homebrew's `/usr/local/lib` is
|
|
@@ -488,10 +528,23 @@ let libraryPathActivated = false;
|
|
|
488
528
|
* still cover a dylib pulled in by ANOTHER dylib's link closure, which never
|
|
489
529
|
* passes through GI.
|
|
490
530
|
*
|
|
491
|
-
* The dirs come from `gtkSource()`, not a second opinion: the bundle's
|
|
492
|
-
* for `bundle`, `systemGiLibraryDirs()` for `system` — the pair the
|
|
493
|
-
* composes. Mixing them is the two-copies hazard #920 records.
|
|
494
|
-
*
|
|
531
|
+
* The GTK dirs come from `gtkSource()`, not a second opinion: the bundle's
|
|
532
|
+
* `libDir` for `bundle`, `systemGiLibraryDirs()` for `system` — the pair the
|
|
533
|
+
* re-exec composes. Mixing them is the two-copies hazard #920 records. The
|
|
534
|
+
* application's own dirs ({@link appGiLibraryDirs}) are added on TOP of whichever
|
|
535
|
+
* of those the policy picked, and independently of it: an app carrying its own GI
|
|
536
|
+
* library needs it found whether the GTK behind it is a bundle, the host's, or —
|
|
537
|
+
* `source === 'none'` — neither.
|
|
538
|
+
*
|
|
539
|
+
* ORDER: the app's entries come FIRST. That only decides an OVERLAP — a leaf both
|
|
540
|
+
* places hold — since a name only one of them has is found either way. An
|
|
541
|
+
* explicitly-set variable is consent and outranks an inferred location (the
|
|
542
|
+
* precedence `GJSIFY_GTK_RUNTIME` already has over the probed bundle candidates),
|
|
543
|
+
* and on an overlap the app's copy is the one its OWN typelib was compiled against:
|
|
544
|
+
* the pair ships as a unit, and splitting it is not a missing library but an ABI
|
|
545
|
+
* mismatch — #910's failure shape, the expensive one.
|
|
546
|
+
*
|
|
547
|
+
* Idempotent, and never fatal: an addon predating the binding changes nothing.
|
|
495
548
|
* @param {{ prependLibraryPath?: (p: string) => void }} native the loaded addon
|
|
496
549
|
* @returns {string[]} the directories handed to GI (empty when there was nothing to add)
|
|
497
550
|
*/
|
|
@@ -501,12 +554,18 @@ export function activateGiLibraryPath(native) {
|
|
|
501
554
|
if (typeof native?.prependLibraryPath !== 'function') return [];
|
|
502
555
|
|
|
503
556
|
const source = gtkSource();
|
|
504
|
-
const
|
|
557
|
+
const gtkDirs =
|
|
505
558
|
source === 'bundle' ? [resolveGtkRuntimeBundle()?.libDir] : source === 'system' ? systemGiLibraryDirs() : [];
|
|
559
|
+
const dirs = [];
|
|
560
|
+
// Deduplicated: an app that names a directory the policy already found would
|
|
561
|
+
// otherwise grow GI's search path by a second, identical entry.
|
|
562
|
+
for (const dir of [...appGiLibraryDirs(), ...gtkDirs.filter(Boolean)]) {
|
|
563
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
564
|
+
}
|
|
506
565
|
const applied = [];
|
|
507
566
|
// LAST wins with a prepend, so walking in reverse leaves the array's own
|
|
508
567
|
// order intact in GI's search path.
|
|
509
|
-
for (const dir of dirs.
|
|
568
|
+
for (const dir of dirs.reverse()) {
|
|
510
569
|
native.prependLibraryPath(dir);
|
|
511
570
|
applied.unshift(dir);
|
|
512
571
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.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
|