@nubjs/nub 0.0.25 → 0.0.27

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/bin/launch.js CHANGED
@@ -28,7 +28,8 @@
28
28
  // The native binary selects its verb from argv[0]'s basename (nub vs nubx); the
29
29
  // healed trampoline exec's bin/<verb> in the platform package (which ships both
30
30
  // names), so no argv0 override is needed past the heal.
31
- const { spawnSync } = require("child_process");
31
+ const { spawn } = require("child_process");
32
+ const os = require("os");
32
33
  const fs = require("fs");
33
34
  const path = require("path");
34
35
  const { platformPackage } = require("../platform.js");
@@ -140,13 +141,35 @@ module.exports = function launch(argv0Name) {
140
141
  healPathEntry(verb, binPath);
141
142
  // This call still runs through Node; spawn the native binary. argv0 basename of
142
143
  // binPath is the verb (bin/nub or bin/nubx), so the Rust CLI dispatches correctly
143
- // without an argv0 override.
144
+ // without an argv0 override. We use async `spawn` (not `spawnSync`) ONLY so this
145
+ // Node launcher can forward terminating signals to the native child: `spawnSync`
146
+ // blocks the event loop, so a SIGTERM (docker stop on a `nub run` entrypoint whose
147
+ // first-ever call hasn't been healed to the sh trampoline yet) would terminate
148
+ // this launcher and orphan the workload. With async spawn we relay SIGTERM/INT/HUP
149
+ // to the child — which then relays to its own subtree — and mirror its exit
150
+ // status. (Subsequent calls skip Node entirely via the healed trampoline's `exec`,
151
+ // where signals reach the binary directly.)
144
152
  const opts = { stdio: "inherit", windowsHide: true };
145
153
  if (argv0Name) opts.argv0 = argv0Name; // belt-and-suspenders for the bin/nub fallback path
146
- const result = spawnSync(binPath, process.argv.slice(2), opts);
147
- if (result.error) {
148
- console.error(`@nubjs/nub: failed to launch ${binPath}: ${result.error.message}`);
154
+ const child = spawn(binPath, process.argv.slice(2), opts);
155
+ let forwarding = true;
156
+ const forward = (sig) => {
157
+ if (forwarding && child.pid) {
158
+ try { process.kill(child.pid, sig); } catch {}
159
+ }
160
+ };
161
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) process.on(sig, () => forward(sig));
162
+ child.on("error", (err) => {
163
+ console.error(`@nubjs/nub: failed to launch ${binPath}: ${err.message}`);
149
164
  process.exit(1);
150
- }
151
- process.exit(result.status == null ? 1 : result.status);
165
+ });
166
+ child.on("exit", (code, signal) => {
167
+ forwarding = false;
168
+ if (signal) {
169
+ // Mirror death-by-signal as 128+signo, matching the native binary and a shell.
170
+ const signo = (os.constants && os.constants.signals && os.constants.signals[signal]) || 0;
171
+ process.exit(signo ? 128 + signo : 1);
172
+ }
173
+ process.exit(code == null ? 1 : code);
174
+ });
152
175
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubjs/nub",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "TypeScript-first developer supertool — a fast script runner and TS runtime powered by Node.js",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/nubjs/nub",
@@ -9,18 +9,22 @@
9
9
  "nub": "bin/nub",
10
10
  "nubx": "bin/nubx"
11
11
  },
12
+ "scripts": {
13
+ "postinstall": "node postinstall.js"
14
+ },
12
15
  "files": [
13
16
  "bin",
14
- "platform.js"
17
+ "platform.js",
18
+ "postinstall.js"
15
19
  ],
16
20
  "optionalDependencies": {
17
- "@nubjs/nub-darwin-arm64": "0.0.25",
18
- "@nubjs/nub-darwin-x64": "0.0.25",
19
- "@nubjs/nub-linux-x64": "0.0.25",
20
- "@nubjs/nub-linux-x64-musl": "0.0.25",
21
- "@nubjs/nub-linux-arm64": "0.0.25",
22
- "@nubjs/nub-linux-arm64-musl": "0.0.25",
23
- "@nubjs/nub-win32-x64": "0.0.25",
24
- "@nubjs/nub-win32-arm64": "0.0.25"
21
+ "@nubjs/nub-darwin-arm64": "0.0.27",
22
+ "@nubjs/nub-darwin-x64": "0.0.27",
23
+ "@nubjs/nub-linux-x64": "0.0.27",
24
+ "@nubjs/nub-linux-x64-musl": "0.0.27",
25
+ "@nubjs/nub-linux-arm64": "0.0.27",
26
+ "@nubjs/nub-linux-arm64-musl": "0.0.27",
27
+ "@nubjs/nub-win32-x64": "0.0.27",
28
+ "@nubjs/nub-win32-arm64": "0.0.27"
25
29
  }
26
30
  }
package/postinstall.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // Set the execute bit on the platform binary at INSTALL time.
3
+ //
4
+ // npm normalizes file modes on extract: a file referenced by a package's `bin`
5
+ // field lands 0o755, everything else 0o644. The platform packages
6
+ // (`@nubjs/nub-<platform>`) deliberately declare NO `bin` field — they're carriers
7
+ // selected by npm's os/cpu filters — so their `bin/nub` / `bin/nubx` extract 0o644
8
+ // (no +x). Something must add it back.
9
+ //
10
+ // `bin/launch.js` also chmods at runtime, but that runs as the END user and chmod
11
+ // only succeeds for the file's OWNER. The canonical container/CI pattern installs
12
+ // as root (`RUN npm i -g`) then drops to a non-root user (`USER app`); that user's
13
+ // first `nub` can't chmod a root-owned 0o644 binary and dies EACCES. THIS script
14
+ // runs as the INSTALLER (root, during the image build), so it sets the bit for
15
+ // everyone before any privilege drop. The launcher chmod stays as the fallback for
16
+ // PMs that skip postinstall — it just can't cover the non-owner case, which is why
17
+ // this exists. (Same shape as esbuild / @swc / @biomejs install-time chmod.)
18
+ //
19
+ // Best-effort and silent: a missing/already-executable binary, an unsupported
20
+ // platform, or a PM that ran us in a sandbox are all non-fatal — the launcher's
21
+ // runtime chmod is the second line of defense.
22
+
23
+ const fs = require("fs");
24
+ const path = require("path");
25
+
26
+ function chmodExecutable() {
27
+ let platformPackage;
28
+ try {
29
+ ({ platformPackage } = require("./platform.js"));
30
+ } catch {
31
+ return; // platform.js absent (shouldn't happen) — let the launcher handle it.
32
+ }
33
+
34
+ const { pkg } = platformPackage();
35
+ if (!pkg) return; // unsupported platform — nothing to chmod.
36
+
37
+ const ext = process.platform === "win32" ? ".exe" : "";
38
+ for (const verb of ["nub", "nubx"]) {
39
+ let binPath;
40
+ try {
41
+ binPath = require.resolve(`${pkg}/bin/${verb}${ext}`);
42
+ } catch {
43
+ continue; // an older platform package may not ship bin/nubx.
44
+ }
45
+ try {
46
+ // Preserve read/write bits, add execute for user/group/other (umask-free —
47
+ // an install-time binary should be runnable by whoever the image runs as).
48
+ const mode = fs.statSync(binPath).mode;
49
+ fs.chmodSync(binPath, mode | 0o111);
50
+ } catch {
51
+ // Not the owner, read-only store, etc. — the launcher chmod is the fallback.
52
+ }
53
+ }
54
+ }
55
+
56
+ chmodExecutable();