@nubjs/nub 0.7.2 → 0.7.3
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 +75 -32
- package/package.json +9 -9
- package/postinstall.js +8 -0
package/bin/launch.js
CHANGED
|
@@ -218,7 +218,58 @@ function cmdShimLeadsToUs(dir, verb, ourReal) {
|
|
|
218
218
|
return false;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
//
|
|
221
|
+
// Place `src` at `dest` as a hardlink, idempotently. Extracted from the .exe path so
|
|
222
|
+
// the shell sidecar below gets identical staleness + fallback semantics.
|
|
223
|
+
//
|
|
224
|
+
// Idempotent, and correct across an upgrade: `npm i -g` extracts a NEW binary at a
|
|
225
|
+
// new inode, so an existing file from a previous version is stale and must be
|
|
226
|
+
// re-linked. Comparing ino+dev is exact for a hardlink; the size fallback covers
|
|
227
|
+
// the copy path, where ino necessarily differs.
|
|
228
|
+
function stageWindowsLink(srcPath, srcStat, dest) {
|
|
229
|
+
try {
|
|
230
|
+
const cur = fs.statSync(dest);
|
|
231
|
+
if ((cur.ino && cur.ino === srcStat.ino && cur.dev === srcStat.dev) || cur.size === srcStat.size) return;
|
|
232
|
+
fs.rmSync(dest, { force: true });
|
|
233
|
+
} catch {}
|
|
234
|
+
try {
|
|
235
|
+
fs.linkSync(srcPath, dest);
|
|
236
|
+
} catch {
|
|
237
|
+
// EXDEV (prefix on a different volume from the store) or a filesystem without
|
|
238
|
+
// hardlinks: fall back to a copy. Costs the file's size on disk once, which is
|
|
239
|
+
// why it is the fallback and not the default.
|
|
240
|
+
try { fs.copyFileSync(srcPath, dest); } catch {}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// The nub-owned subdirectory the bundled POSIX shell is carried into. Must match
|
|
245
|
+
// NUB_SHELL_SUBDIR in crates/nub-cli/src/cli.rs (resolve_bundled_busybox).
|
|
246
|
+
const SHELL_SUBDIR = "nub-sh";
|
|
247
|
+
|
|
248
|
+
// Carry the bundled POSIX shell along with the .exe we just linked.
|
|
249
|
+
//
|
|
250
|
+
// `nub run` executes script bodies through the bundled busybox-w32 `sh`, and the
|
|
251
|
+
// binary resolves it RELATIVE TO ITSELF (cli.rs resolve_bundled_busybox). The win32
|
|
252
|
+
// package lays `busybox.exe` beside `bin/nub.exe`, so that holds — until the .exe is
|
|
253
|
+
// hardlinked into npm's global bin dir, whose parent has no sidecar, and every
|
|
254
|
+
// `nub run` then dies "bundled POSIX shell (busybox.exe) was not found" (#687).
|
|
255
|
+
//
|
|
256
|
+
// It goes in a SUBDIRECTORY, not beside the .exe, because `dir` is by construction a
|
|
257
|
+
// directory on the user's PATH: a bare `busybox.exe` there would shadow a busybox the
|
|
258
|
+
// user installed themselves, which is the same shadowing hazard cmdShimLeadsToUs
|
|
259
|
+
// exists to prevent — and worse here, since the name is not even ours. A subdirectory
|
|
260
|
+
// is not searched by PATH, so it is invisible to command resolution.
|
|
261
|
+
//
|
|
262
|
+
// A missing source is not an error: a NEWER launcher can run against an OLDER platform
|
|
263
|
+
// package that predates the bundled shell (<0.6.0), and those installs never had one.
|
|
264
|
+
function stageWindowsShell(pkgBinDir, dir) {
|
|
265
|
+
const from = path.join(pkgBinDir, "busybox.exe");
|
|
266
|
+
let st; try { st = fs.statSync(from); } catch { return; }
|
|
267
|
+
const into = path.join(dir, SHELL_SUBDIR);
|
|
268
|
+
try { fs.mkdirSync(into, { recursive: true }); } catch { return; }
|
|
269
|
+
stageWindowsLink(from, st, path.join(into, "busybox.exe"));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// WINDOWS: put a real `<verb>.exe` next to npm's shims, plus the shell it needs.
|
|
222
273
|
//
|
|
223
274
|
// The heal below is POSIX-only because there is no shebang or symlink fast path on
|
|
224
275
|
// Windows — every call goes cmd.exe -> nub.cmd -> node -> spawn nub.exe, and the node
|
|
@@ -226,15 +277,19 @@ function cmdShimLeadsToUs(dir, verb, ourReal) {
|
|
|
226
277
|
// of `nub.cmd` by PATHEXT, so cmd.exe reaches the binary directly. Measured on
|
|
227
278
|
// windows-latest, N=40: 95.6 -> 35.8 ms.
|
|
228
279
|
//
|
|
229
|
-
//
|
|
230
|
-
// generated: we are not the first package to start editing files npm
|
|
231
|
-
// esbuild, bun and @pnpm/exe all modify only files inside their OWN
|
|
232
|
-
// touch the global bin dir). The cost is that PowerShell and every
|
|
233
|
-
// preferring those shims and see no improvement — including nub's
|
|
234
|
-
// shell, the bundled busybox (cli.rs `resolve_bundled_busybox`),
|
|
235
|
-
// ms, i.e. nothing. `nub run` therefore does not benefit
|
|
280
|
+
// ADD-ONLY WITH RESPECT TO FILES NPM OWNS. npm's `.ps1` and extensionless shims are
|
|
281
|
+
// left exactly as generated: we are not the first package to start editing files npm
|
|
282
|
+
// owns (checked — esbuild, bun and @pnpm/exe all modify only files inside their OWN
|
|
283
|
+
// package and never touch the global bin dir). The cost is that PowerShell and every
|
|
284
|
+
// sh-family shell keep preferring those shims and see no improvement — including nub's
|
|
285
|
+
// OWN Windows script shell, the bundled busybox (cli.rs `resolve_bundled_busybox`),
|
|
286
|
+
// measured 170.3 -> 169.0 ms, i.e. nothing. `nub run` therefore does not benefit on
|
|
287
|
+
// SPEED. It does depend on this function for CORRECTNESS: relocating the .exe moves
|
|
288
|
+
// the binary away from the sidecar it resolves relative to itself, which is what
|
|
289
|
+
// stageWindowsShell repairs (#687). Reading that trade as "nub run is unaffected"
|
|
290
|
+
// is how the regression shipped.
|
|
236
291
|
//
|
|
237
|
-
//
|
|
292
|
+
// THREE RESIDUES THIS SHAPE OWNS, all from writing files npm does not track:
|
|
238
293
|
//
|
|
239
294
|
// UNINSTALL. `npm uninstall -g @nubjs/nub` removes only the shims npm generated;
|
|
240
295
|
// cmd-shim never created `<verb>.exe` and npm has run no uninstall lifecycle script
|
|
@@ -242,14 +297,15 @@ function cmdShimLeadsToUs(dir, verb, ourReal) {
|
|
|
242
297
|
// answering `nub` from cmd.exe after the user believes nub is gone — and on the
|
|
243
298
|
// hardlink path the surviving link also keeps the binary's bytes on disk. This is a
|
|
244
299
|
// real user-visible residue, not merely wasted space; do not describe it as "npm's
|
|
245
|
-
// uninstall is unaffected".
|
|
300
|
+
// uninstall is unaffected". The `nub-sh/` shell dir survives the same way, but it is
|
|
301
|
+
// NOT on PATH, so it wastes space without answering any command.
|
|
246
302
|
//
|
|
247
303
|
// UPGRADE. Once the `.exe` wins PATHEXT, cmd.exe never dispatches through npm's `.cmd`
|
|
248
304
|
// again, so THIS FUNCTION NEVER RUNS AGAIN for the users it serves and its currency
|
|
249
|
-
// check below cannot fire for them. `postinstall.js` (dropStaleWindowsExe) removes
|
|
250
|
-
// file on every install so the next call re-heals against the new
|
|
251
|
-
// only runs when lifecycle scripts do, so an `--ignore-scripts`
|
|
252
|
-
// cmd.exe executing the previous version silently.
|
|
305
|
+
// check below cannot fire for them. `postinstall.js` (dropStaleWindowsExe) removes both
|
|
306
|
+
// the file and `nub-sh/` on every install so the next call re-heals against the new
|
|
307
|
+
// binary — but that only runs when lifecycle scripts do, so an `--ignore-scripts`
|
|
308
|
+
// upgrade still leaves cmd.exe executing the previous version silently.
|
|
253
309
|
//
|
|
254
310
|
// Best-effort and silent, like every other heal step: any failure leaves a working
|
|
255
311
|
// (slower) install rather than a broken one.
|
|
@@ -264,24 +320,11 @@ function healWindowsBinDir(verb, nativePath) {
|
|
|
264
320
|
for (const dir of (process.env.PATH || "").split(path.delimiter)) {
|
|
265
321
|
if (!dir) continue;
|
|
266
322
|
if (!cmdShimLeadsToUs(dir, verb, ourReal)) continue;
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const cur = fs.statSync(dest);
|
|
274
|
-
if ((cur.ino && cur.ino === src.ino && cur.dev === src.dev) || cur.size === src.size) return;
|
|
275
|
-
fs.rmSync(dest, { force: true });
|
|
276
|
-
} catch {}
|
|
277
|
-
try {
|
|
278
|
-
fs.linkSync(nativeReal, dest);
|
|
279
|
-
} catch {
|
|
280
|
-
// EXDEV (prefix on a different volume from the store) or a filesystem without
|
|
281
|
-
// hardlinks: fall back to a copy. Costs the binary's size on disk once, which is
|
|
282
|
-
// why it is the fallback and not the default.
|
|
283
|
-
try { fs.copyFileSync(nativeReal, dest); } catch {}
|
|
284
|
-
}
|
|
323
|
+
stageWindowsLink(nativeReal, src, path.join(dir, `${verb}.exe`));
|
|
324
|
+
// Unconditional, NOT gated on the .exe having been (re)linked above: the
|
|
325
|
+
// 0.7.0-0.7.2 installs this fixes already carry a current .exe, so a shell
|
|
326
|
+
// staged only alongside a fresh link would never reach them.
|
|
327
|
+
stageWindowsShell(path.dirname(nativeReal), dir);
|
|
285
328
|
break; // the first PATH entry that dispatches to us is the one that matters
|
|
286
329
|
}
|
|
287
330
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubjs/nub",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"LICENSE"
|
|
37
37
|
],
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@nubjs/nub-darwin-arm64": "0.7.
|
|
40
|
-
"@nubjs/nub-darwin-x64": "0.7.
|
|
41
|
-
"@nubjs/nub-linux-x64": "0.7.
|
|
42
|
-
"@nubjs/nub-linux-x64-musl": "0.7.
|
|
43
|
-
"@nubjs/nub-linux-arm64": "0.7.
|
|
44
|
-
"@nubjs/nub-linux-arm64-musl": "0.7.
|
|
45
|
-
"@nubjs/nub-win32-x64": "0.7.
|
|
46
|
-
"@nubjs/nub-win32-arm64": "0.7.
|
|
39
|
+
"@nubjs/nub-darwin-arm64": "0.7.3",
|
|
40
|
+
"@nubjs/nub-darwin-x64": "0.7.3",
|
|
41
|
+
"@nubjs/nub-linux-x64": "0.7.3",
|
|
42
|
+
"@nubjs/nub-linux-x64-musl": "0.7.3",
|
|
43
|
+
"@nubjs/nub-linux-arm64": "0.7.3",
|
|
44
|
+
"@nubjs/nub-linux-arm64-musl": "0.7.3",
|
|
45
|
+
"@nubjs/nub-win32-x64": "0.7.3",
|
|
46
|
+
"@nubjs/nub-win32-arm64": "0.7.3"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/postinstall.js
CHANGED
|
@@ -199,6 +199,14 @@ function dropStaleWindowsExe() {
|
|
|
199
199
|
// delete — there is a real unrelated `nub@1.0.0` on npm.
|
|
200
200
|
if (!fs.existsSync(path.join(dir, `${verb}.cmd`))) continue;
|
|
201
201
|
try { fs.rmSync(path.join(dir, `${verb}.exe`), { force: true }); } catch {}
|
|
202
|
+
// The bundled POSIX shell the launcher carried in beside that `.exe`
|
|
203
|
+
// (healWindowsBinDir -> `nub-sh/busybox.exe`). The heal re-stages this
|
|
204
|
+
// unconditionally, so unlike the `.exe` it is not stranded by being left here —
|
|
205
|
+
// but its currency check falls back to comparing SIZE when the inode differs,
|
|
206
|
+
// and a busybox that changed while keeping its size would be kept forever.
|
|
207
|
+
// Dropping the nub-owned dir at install time closes that, and costs one hardlink
|
|
208
|
+
// on the next call.
|
|
209
|
+
try { fs.rmSync(path.join(dir, "nub-sh"), { recursive: true, force: true }); } catch {}
|
|
202
210
|
}
|
|
203
211
|
}
|
|
204
212
|
}
|