@gjsify/node-gi 0.35.0 → 0.37.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/node-gi",
3
- "version": "0.35.0",
3
+ "version": "0.37.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
@@ -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, and run the node-gyp source build only
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. Until this script existed, `package.json` declared
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. Measured on a `windows-latest` runner
10
- // against `@gjsify/node-gi@0.26.1`, whose tarball DOES contain
11
- // `prebuilds/win32-x64/node_gi.node`:
12
- //
13
- // win-gi-gyp-flags: import lib for 'girepository-2.0' not found
14
- // gyp ERR! configure error
15
- // → npm rolls the install back; the package is not installed at all
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 its `node-gyp-build-test` bin, which `require()`s
27
- // the addon and falls back to a source build if the load throws. On exactly the
28
- // platforms whose whole point is the prebuild that is a FALSE NEGATIVE: the
29
- // win32/darwin addon's DLL/dylib closure is only findable after
30
- // `gtk-runtime.js` has wired the batteries-included GTK bundle into `PATH` /
31
- // `DYLD_FALLBACK_LIBRARY_PATH`, which `index.js` does at import time and a bare
32
- // install-time `require()` of the `.node` never does. A load-based gate would
33
- // therefore call a perfectly good prebuild broken and rebuild — reproducing the
34
- // exact failure above. Existence of the file the loader actually probes is the
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
- // • its skip gate is that false-negative load test (above) — the disqualifier;
42
- // • its probe order is `build/` FIRST, then the prebuild, the OPPOSITE of
43
- // node-gi's deliberate consumer default (see native-paths.js
44
- // `nativeCandidates`), so the two would disagree about which binary matters;
45
- // • it cannot produce ONE actionable diagnosis — on failure it propagates a
46
- // bare node-gyp exit code, which is the status quo this change removes;
47
- // • it would add a runtime `dependencies` entry to a Tier-2 package whose dep
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
- // (packages/node-gi/node-gi/.gitignore), so a fresh checkout has none and this
57
- // script builds exactly as before — which is what the ~12 `npm install
58
- // --foreground-scripts` steps in node-gi.yml and the three
59
- // `node-gi-prebuild-*` legs in release.yml rely on to produce
60
- // `build/Release/node_gi.node` for `scripts/stage-prebuild.mjs`. Depending on a
61
- // directory being ABSENT is too implicit to be the only guarantee, so the build
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
- // confirmed prebuild file for this exact host skips the build, so a bug here
69
- // degrades to the previous behaviour rather than to a package with no addon.
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? `NODE_GI_BUILD_FROM_SOURCE` is
83
- * ours; `npm_config_build_from_source` is what npm exports for
84
- * `--build-from-source` (the flag every node-gyp-build package honours), so a CI
85
- * job or a maintainer can force the build without knowing node-gi's own name for
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
- * compiler output stays visible (it is the actual diagnosis; the framed message
107
- * below only tells the reader what to DO about it).
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
- * JS entry if it happens to be resolvable, else the `node-gyp` shim that npm,
111
- * yarn and pnpm all put on `PATH` for lifecycle scripts (`node-gyp.cmd` on
112
- * Windows, hence `shell` there).
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
- // bundled copy lives in npm's own tree and is not, which is why the PATH
123
- // shim above stays the normal path. `createRequire().resolve` rather than
124
- // `import.meta.resolve` — the latter is only synchronous from Node 20.6,
125
- // and `engines.node` is `>=20`.
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
- * Keyed on two paths `files` deliberately does not publish (`test/` and the
140
- * staging tool), because the distinction changes the advice: in a checkout
141
- * `prebuilds/` is gitignored, so its absence is expected and the source build IS
142
- * the path; in an installed copy a missing prebuild for a PROMISED target is a
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 very different reasons the prebuild was missing,
153
- * because the reader's next action differs each time: a checkout is SUPPOSED to
154
- * build; an undeclared platform means "build it yourself or use a supported
155
- * target"; and a DECLARED platform with no artifact in an installed copy is a
156
- * packaging regression that should be reported, not worked around (precisely how
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
- // addon via NODE_GI_NATIVE afterwards.
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
- // addon: a zero exit here would trade a clear install-time failure for a
232
- // deferred `import` failure in the consumer's own code, and npm's rollback is
233
- // the honest outcome. NODE_GI_SKIP_NATIVE_BUILD is the opt-in for anyone who
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/class.cc CHANGED
@@ -617,6 +617,21 @@ static void NodeGiClassInit(gpointer g_class, gpointer class_data) {
617
617
  // init_template. Independent of the child binding above — Children /
618
618
  // InternalChildren behaviour is unchanged.
619
619
  NodeGiInstallTemplateScopeOnClass(cd, g_class);
620
+ } else if (widgetType == 0) {
621
+ // GtkWidget is not registered in the type registry THIS addon calls into. The
622
+ // class in hand descends from one (registerClass resolved its parent), so the
623
+ // honest reading is not "your class is wrong" but "there are two GObject copies
624
+ // in this process" — the addon bound one libgobject and GTK registered its types
625
+ // in another. Reported separately because the subclass wording below sent #1120
626
+ // hunting a JS bug for a dyld one: an unrelocated darwin prebuild kept the build
627
+ // host's absolute Homebrew paths and bound Homebrew's libgobject while the
628
+ // batteries-included bundle's libgtk used its own.
629
+ g_warning(
630
+ "node-gi: GtkWidget is not registered in this process's GObject type registry, "
631
+ "so the Template on %s cannot be installed. This means TWO GLib/GObject copies "
632
+ "are loaded — check that the addon and GTK resolve to the same libgobject "
633
+ "(otool -L on the addon; DYLD_PRINT_LIBRARIES=1 to list what loaded)",
634
+ g_type_name(G_TYPE_FROM_CLASS(g_class)));
620
635
  } else if (!isWidget) {
621
636
  g_warning(
622
637
  "node-gi: a Template was set on %s, which is not a Gtk.Widget subclass — "