@nimbus-sh/worker 0.4.0 → 0.6.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.
Files changed (95) hide show
  1. package/README.md +46 -43
  2. package/dist/_shared/preview-host.d.ts +33 -2
  3. package/dist/_shared/preview-host.d.ts.map +1 -1
  4. package/dist/_shared/preview-host.js +73 -8
  5. package/dist/_shared/session-router.d.ts +43 -0
  6. package/dist/_shared/session-router.d.ts.map +1 -1
  7. package/dist/_shared/session-router.js +59 -12
  8. package/dist/facets/durable-images.d.ts +59 -0
  9. package/dist/facets/durable-images.d.ts.map +1 -0
  10. package/dist/facets/durable-images.js +94 -0
  11. package/dist/facets/durable-slots.d.ts +45 -0
  12. package/dist/facets/durable-slots.d.ts.map +1 -0
  13. package/dist/facets/durable-slots.js +95 -0
  14. package/dist/facets/esbuild-transform.d.ts +10 -0
  15. package/dist/facets/esbuild-transform.d.ts.map +1 -0
  16. package/dist/facets/esbuild-transform.js +36 -0
  17. package/dist/facets/manager.d.ts +244 -6
  18. package/dist/facets/manager.d.ts.map +1 -1
  19. package/dist/facets/manager.js +901 -117
  20. package/dist/facets/resident-identity.d.ts +23 -0
  21. package/dist/facets/resident-identity.d.ts.map +1 -0
  22. package/dist/facets/resident-identity.js +31 -0
  23. package/dist/git/commands.d.ts +22 -1
  24. package/dist/git/commands.d.ts.map +1 -1
  25. package/dist/git/commands.js +429 -425
  26. package/dist/git-bundle.generated.d.ts +2 -2
  27. package/dist/git-bundle.generated.d.ts.map +1 -1
  28. package/dist/git-bundle.generated.js +3 -3
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -1
  32. package/dist/node-shims-artifact.generated.js +3 -3
  33. package/dist/router/index.d.ts.map +1 -1
  34. package/dist/router/index.js +97 -3
  35. package/dist/router/public-directory-do.d.ts +17 -0
  36. package/dist/router/public-directory-do.d.ts.map +1 -0
  37. package/dist/router/public-directory-do.js +16 -0
  38. package/dist/router/public-directory.d.ts +79 -0
  39. package/dist/router/public-directory.d.ts.map +1 -0
  40. package/dist/router/public-directory.js +122 -0
  41. package/dist/router/remote-api.d.ts +27 -1
  42. package/dist/router/remote-api.d.ts.map +1 -1
  43. package/dist/router/remote-api.js +48 -2
  44. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  45. package/dist/runtime/cpython-resident.js +3 -1
  46. package/dist/runtime/node-shims.d.ts +4 -1
  47. package/dist/runtime/node-shims.d.ts.map +1 -1
  48. package/dist/runtime/node-shims.js +324 -6
  49. package/dist/runtime/package-manager.d.ts +44 -1
  50. package/dist/runtime/package-manager.d.ts.map +1 -1
  51. package/dist/runtime/package-manager.js +168 -3
  52. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  53. package/dist/runtime/ruby-resident.js +2 -0
  54. package/dist/session/agent.d.ts +11 -0
  55. package/dist/session/agent.d.ts.map +1 -1
  56. package/dist/session/agent.js +28 -1
  57. package/dist/session/helpers.js +1 -1
  58. package/dist/session/init-phases.d.ts +17 -23
  59. package/dist/session/init-phases.d.ts.map +1 -1
  60. package/dist/session/init-phases.js +17 -41
  61. package/dist/session/init.d.ts.map +1 -1
  62. package/dist/session/init.js +28 -15
  63. package/dist/session/keys.d.ts +12 -0
  64. package/dist/session/keys.d.ts.map +1 -1
  65. package/dist/session/keys.js +12 -0
  66. package/dist/session/nimbus-session.d.ts +63 -5
  67. package/dist/session/nimbus-session.d.ts.map +1 -1
  68. package/dist/session/nimbus-session.js +131 -17
  69. package/dist/session/port-capability.d.ts +141 -8
  70. package/dist/session/port-capability.d.ts.map +1 -1
  71. package/dist/session/port-capability.js +265 -8
  72. package/dist/session/programmatic.d.ts +145 -3
  73. package/dist/session/programmatic.d.ts.map +1 -1
  74. package/dist/session/programmatic.js +434 -12
  75. package/dist/session/routes.d.ts +26 -0
  76. package/dist/session/routes.d.ts.map +1 -1
  77. package/dist/session/routes.js +117 -25
  78. package/dist/session/rpc.d.ts +24 -1
  79. package/dist/session/rpc.d.ts.map +1 -1
  80. package/dist/session/rpc.js +65 -98
  81. package/dist/session/shell-socket.d.ts +76 -0
  82. package/dist/session/shell-socket.d.ts.map +1 -0
  83. package/dist/session/shell-socket.js +172 -0
  84. package/dist/session/start-real-vite.js +1 -1
  85. package/dist/session/supervisor-op.d.ts +42 -0
  86. package/dist/session/supervisor-op.d.ts.map +1 -0
  87. package/dist/session/supervisor-op.js +96 -0
  88. package/dist/session/supervisor-rpc.d.ts +6 -10
  89. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  90. package/dist/session/supervisor-rpc.js +91 -71
  91. package/dist/session/ws.d.ts.map +1 -1
  92. package/dist/session/ws.js +19 -4
  93. package/package.json +4 -4
  94. package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
  95. package/public/s/index.html +101 -5
@@ -40,6 +40,7 @@ import { handleFsWatchSubscribe, handleFsWatchUnsubscribe, cleanupFsWatchOnClose
40
40
  import { parseProcessLogClientFrame } from '@nimbus-sh/core/runtime/process-io-protocol.js';
41
41
  import { applyProcessClientFrame } from '@nimbus-sh/core/runtime/process-input-routing.js';
42
42
  import { z } from 'zod/v4';
43
+ import { clearShellSocketStamp, noteShellSocketActivity } from './shell-socket.js';
43
44
  import { PRIOR_GENERATION_EXIT_REASON } from './rpc.js';
44
45
  /**
45
46
  * Snapshot the live Shell state and write it through to DO SQLite
@@ -151,6 +152,12 @@ export async function wsMessage(self, ws, message) {
151
152
  await routeProcessLogClientMessage(self, ws, attach, message);
152
153
  return;
153
154
  }
155
+ // A frame arriving is the peer proving it is still on this socket.
156
+ // The stamp goes in the attachment because isolate memory does not
157
+ // survive hibernation and the /ws upgrade has to read it afterwards.
158
+ // Rate-limited inside; see src/session/shell-socket.ts.
159
+ if (attach?.kind === 'shell')
160
+ noteShellSocketActivity(ws);
154
161
  const data = typeof message === 'string' ? message : dec.decode(message);
155
162
  const value = JSON.parse(data);
156
163
  // file-tree-watch (2026-05-15): handle fs-watch-* on this WS BEFORE
@@ -304,6 +311,11 @@ export async function wsClose(self, ws, _code, _reason, _wasClean) {
304
311
  // wrangler dev) + long-running facets must still survive the
305
312
  // terminal reconnect (see 607e472 — do NOT kill running processes
306
313
  // here). Only reap per-tab state.
314
+ // The runtime says this socket is finished, so it no longer holds the
315
+ // terminal. Drop its liveness stamp before anything else: the frame
316
+ // that triggered this close stamped it moments ago, and leaving that
317
+ // behind would make the reconnect look like a second tab.
318
+ clearShellSocketStamp(ws);
307
319
  // ── Phase 3 B'.1: transitionTo('drained') ──────────────────────────
308
320
  // The Track B' state-machine transition. Persist final shell
309
321
  // state + record a recovery_event BEFORE we null the in-memory
@@ -352,10 +364,9 @@ export async function wsClose(self, ws, _code, _reason, _wasClean) {
352
364
  // upgrade calls terminal.attach(newWs) to swap in the new socket.
353
365
  //
354
366
  // Pre-B'.5 we nulled these three fields to avoid two-tab cross-
355
- // wiring (the 409 in nimbus-session-routes.ts:97 protected against
356
- // overwriting an active shell). With phase=drained surfaced on the
357
- // host, the /ws handler can disambiguate "warm session waiting for
358
- // rejoin" (warmJoin path) from "active session busy" (still 409).
367
+ // wiring, which cost every reconnect a full rebuild. The upgrade now
368
+ // reads the sockets instead: a shell socket a peer still holds is the
369
+ // busy session it refuses, and this one is closing.
359
370
  // Reset the one-shot "wrangler alias" banner so a reconnecting user
360
371
  // sees it again — terminal-lifetime state, not session-lifetime.
361
372
  self.wranglerAliasBannerShown = false;
@@ -387,6 +398,10 @@ export async function wsError(self, ws, _error) {
387
398
  catch { /* best-effort */ }
388
399
  return;
389
400
  }
401
+ // Same reasoning as wsClose: this socket stops holding the terminal
402
+ // here. It matters more on this path, because the handler that just
403
+ // blew the 5-s cap is the one that stamped the socket.
404
+ clearShellSocketStamp(ws);
390
405
  // ── Phase 3 B'.1: transitionTo('drained') ──────────────────────────
391
406
  // Same architectural step as wsClose: persist shell state + record
392
407
  // a drained event before nulling. wsError is a different physical
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/worker",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Nimbus runtime package for Cloudflare Workers, re-exported publicly through @nimbus-sh/sdk/worker.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -84,9 +84,9 @@
84
84
  },
85
85
  "dependencies": {
86
86
  "@ai-sdk/openai-compatible": "^2.0.41",
87
- "@nimbus-sh/core": "^0.6.0",
88
- "@nimbus-sh/fabric": "^0.2.0",
89
- "@nimbus-sh/platform": "^0.1.0",
87
+ "@nimbus-sh/core": "^0.8.0",
88
+ "@nimbus-sh/fabric": "^0.4.0",
89
+ "@nimbus-sh/platform": "^0.3.0",
90
90
  "@renovatebot/pep440": "^5.0.0",
91
91
  "ai": "^6.0.182",
92
92
  "clean-git-ref": "^2.0.1",
@@ -324,7 +324,19 @@ const __BufferMod = (() => {
324
324
 
325
325
  function alloc(n, fill) { const a = new Uint8Array(n); if (fill !== undefined) a.fill(typeof fill === "number" ? fill : 0); return _wrap(a); }
326
326
  function allocUnsafe(n) { return _wrap(new Uint8Array(Number(n) || 0)); }
327
- function isBuffer(o) { return o instanceof Uint8Array && typeof o.toString === "function" && o.__isBuffer; }
327
+ // Marker check plus native-brand recognition: forwarded node:* builtins
328
+ // (zlib streams and results, crypto) hand back the host realm's own Buffer
329
+ // instances, which carry no __isBuffer marker. Their constructor brand is
330
+ // the closest cross-realm equivalent of Node's instanceof check, so
331
+ // Buffer.isBuffer stays truthful across the shim/native boundary instead
332
+ // of rejecting genuine host Buffers. Always returns a boolean (the old
333
+ // single-expression form leaked an undefined when the marker was absent).
334
+ function isBuffer(o) {
335
+ if (!(o instanceof Uint8Array)) return false;
336
+ if (o.__isBuffer) return true;
337
+ const ctor = o.constructor;
338
+ return typeof ctor === "function" && ctor.name === "Buffer";
339
+ }
328
340
  function concat(bufs, len) {
329
341
  const total = len ?? bufs.reduce((s, b) => s + b.length, 0);
330
342
  const r = new Uint8Array(total); let off = 0;
@@ -6884,6 +6896,21 @@ const __processMod = {
6884
6896
  },
6885
6897
  platform: "linux", arch: "x64",
6886
6898
  version: "v22.19.0", versions: {"node":"22.19.0","v8":"12.4.254.21","modules":"127"},
6899
+ features: Object.freeze({
6900
+ inspector: false,
6901
+ debug: false,
6902
+ uv: false,
6903
+ ipv6: true,
6904
+ tls_alpn: true,
6905
+ tls_sni: true,
6906
+ tls_ocsp: false,
6907
+ tls: true,
6908
+ openssl_is_boringssl: true,
6909
+ }),
6910
+ getBuiltinModule: (specifier) => {
6911
+ const key = String(specifier).replace(/^node:/, "");
6912
+ return Object.prototype.hasOwnProperty.call(builtins, key) ? builtins[key] : undefined;
6913
+ },
6887
6914
  execPath: "/usr/local/bin/node",
6888
6915
  execArgv: [],
6889
6916
  pid: 1, ppid: 0, title: "node",
@@ -7443,10 +7470,295 @@ const __nimbusFacetProvidedPackages = new Set(["undici"]);
7443
7470
  // timers.setInterval(...)), which clack's spinner — used by
7444
7471
  // create-cloudflare — triggers.
7445
7472
  builtins.timers = { setTimeout: globalThis.setTimeout.bind(globalThis), setInterval: globalThis.setInterval.bind(globalThis), clearTimeout: globalThis.clearTimeout.bind(globalThis), clearInterval: globalThis.clearInterval.bind(globalThis), setImmediate: (fn,...a) => globalThis.setTimeout(fn,0,...a), clearImmediate: globalThis.clearTimeout.bind(globalThis) };
7473
+ // ── zlib ───────────────────────────────────────────────────────────
7474
+ //
7475
+ // Two runtimes, one surface:
7476
+ // 1. Facet templates prepend the static import block
7477
+ // (_shared/real-node-imports.ts), which carries workerd's native
7478
+ // node:zlib — complete at the production compat date: every *Sync
7479
+ // variant, brotli/zstd, crc32, constants, and real streaming create*
7480
+ // factories. Forward verbatim: results are the host realm's own Buffer
7481
+ // instances, and __BufferMod.isBuffer recognizes that brand (see the
7482
+ // Buffer shim), so Buffer checks stay truthful without copying every
7483
+ // result or intercepting stream chunks.
7484
+ // 2. Scopes without the import block keep the CompressionStream fallback.
7485
+ // It is async-only by nature; the sync names refuse with an honest,
7486
+ // actionable error instead of pretending.
7446
7487
  builtins.zlib = (() => {
7447
- function _c(d,a) { const i=typeof d==="string"?new TextEncoder().encode(d):d; return new Response(new Blob([i]).stream().pipeThrough(new CompressionStream(a))).arrayBuffer().then(ab=>__BufferMod.from(new Uint8Array(ab))); }
7448
- function _d(d,a) { const i=d instanceof Uint8Array?d:new Uint8Array(d); return new Response(new Blob([i]).stream().pipeThrough(new DecompressionStream(a))).arrayBuffer().then(ab=>__BufferMod.from(new Uint8Array(ab))); }
7449
- return { gzip:(d,o,cb)=>{if(typeof o==="function")cb=o;_c(d,"gzip").then(r=>cb(null,r)).catch(e=>cb(e));}, gunzip:(d,o,cb)=>{if(typeof o==="function")cb=o;_d(d,"gzip").then(r=>cb(null,r)).catch(e=>cb(e));}, deflate:(d,o,cb)=>{if(typeof o==="function")cb=o;_c(d,"deflate").then(r=>cb(null,r)).catch(e=>cb(e));}, inflate:(d,o,cb)=>{if(typeof o==="function")cb=o;_d(d,"deflate").then(r=>cb(null,r)).catch(e=>cb(e));}, gzipSync:()=>{throw new Error("use async gzip()");}, gunzipSync:()=>{throw new Error("use async gunzip()");}, createGzip:()=>new __streamMod.Transform({transform(c,e,cb){_c(c,"gzip").then(r=>cb(null,r)).catch(e=>cb(e));}}), createGunzip:()=>new __streamMod.Transform({transform(c,e,cb){_d(c,"gzip").then(r=>cb(null,r)).catch(e=>cb(e));}}), createDeflate:()=>new __streamMod.Transform({transform(c,e,cb){_c(c,"deflate").then(r=>cb(null,r)).catch(e=>cb(e));}}), createInflate:()=>new __streamMod.Transform({transform(c,e,cb){_d(c,"deflate").then(r=>cb(null,r)).catch(e=>cb(e));}}), constants:{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1} };
7488
+ const __real = (typeof __real_zlib !== "undefined") ? (__real_zlib.default ?? __real_zlib) : null;
7489
+ if (__real && typeof __real.gzipSync === "function") {
7490
+ const mod = {};
7491
+ // Constants, lookup tables, crc32, and the stream factories/classes pass
7492
+ // through bound to the native module (capitalized names are classes —
7493
+ // binding would strip their prototype and break `new`).
7494
+ for (const k of Object.keys(__real)) {
7495
+ const v = __real[k];
7496
+ mod[k] = (typeof v === "function" && /^[a-z]/.test(k)) ? v.bind(__real) : v;
7497
+ }
7498
+ if (__real.promises) mod.promises = __real.promises;
7499
+ mod.default = mod;
7500
+ return mod;
7501
+ }
7502
+ function _c(i,a) { return new Response(new Blob([i]).stream().pipeThrough(new CompressionStream(a))).arrayBuffer().then(ab=>__BufferMod.from(new Uint8Array(ab))); }
7503
+ // Decompression is driven by hand rather than piped: feeding and draining
7504
+ // run together, because a write only completes while somebody reads.
7505
+ async function _d(i,a) {
7506
+ const codec = _openCodec("decompress", a);
7507
+ const chunks = [];
7508
+ let failure = null;
7509
+ const record = (reason) => { if (!failure) failure = { reason }; };
7510
+ const drain = (async () => {
7511
+ for (;;) {
7512
+ const next = await codec.reader.read();
7513
+ if (next.done) return;
7514
+ chunks.push(next.value);
7515
+ }
7516
+ })().catch(record);
7517
+ const feed = (async () => {
7518
+ await codec.writer.write(i);
7519
+ await codec.writer.close();
7520
+ })().catch(record);
7521
+ await Promise.all([drain, feed]);
7522
+ if (failure) throw _decompressFailure(failure.reason);
7523
+ return __BufferMod.from(_concat(chunks));
7524
+ }
7525
+ function _syncRefusal(name, asyncName) {
7526
+ const e = new Error("zlib." + name + ": synchronous compression is not available on this runtime (no native node:zlib in scope). Use the async zlib." + asyncName + "(data, callback) form.");
7527
+ e.code = "ERR_ZLIB_SYNC_UNAVAILABLE";
7528
+ throw e;
7529
+ }
7530
+ // Node accepts strings, ArrayBuffers, and any ArrayBufferView; one funnel
7531
+ // yields a byte-addressed Uint8Array over the caller's own region —
7532
+ // byteOffset/byteLength preserved, element values never reinterpreted.
7533
+ function _u8(d) {
7534
+ if (typeof d === "string") return new TextEncoder().encode(d);
7535
+ if (d instanceof Uint8Array) return d;
7536
+ if (ArrayBuffer.isView(d)) return new Uint8Array(d.buffer, d.byteOffset, d.byteLength);
7537
+ return new Uint8Array(d);
7538
+ }
7539
+ // Node splits decompression failures two ways, and the engine's own
7540
+ // failure is the only thing that can tell them apart: an input that ran
7541
+ // out is Z_BUF_ERROR / errno -5, everything else — bad header, bad block,
7542
+ // and a checksum or length trailer that only fails once the member is
7543
+ // complete — is Z_DATA_ERROR / errno -3. Which side of a write or close
7544
+ // the failure lands on says nothing about that, so it is not consulted.
7545
+ function _zdata(e) {
7546
+ const err = new Error(e instanceof Error && e.message ? e.message : String(e));
7547
+ err.code = "Z_DATA_ERROR";
7548
+ err.errno = -3;
7549
+ err.cause = e;
7550
+ return err;
7551
+ }
7552
+ function _zbuf(e) {
7553
+ const err = new Error("unexpected end of file");
7554
+ err.code = "Z_BUF_ERROR";
7555
+ err.errno = -5;
7556
+ if (e != null) err.cause = e;
7557
+ return err;
7558
+ }
7559
+ function _isUnexpectedEnd(e) {
7560
+ const m = e instanceof Error && typeof e.message === "string" ? e.message : String(e);
7561
+ return /unexpected end|end of (?:file|input|stream)|premature|truncated|\bEOF\b/i.test(m);
7562
+ }
7563
+ function _decompressFailure(e) { return _isUnexpectedEnd(e) ? _zbuf(e) : _zdata(e); }
7564
+ function _destroyedWrite() {
7565
+ const e = new Error("Cannot call write after a stream was destroyed");
7566
+ e.code = "ERR_STREAM_DESTROYED";
7567
+ return e;
7568
+ }
7569
+ // Node throws synchronously rather than deferring a callback that is not
7570
+ // there; this surface has no promise form to fall back on.
7571
+ function _invalidCallback(v) {
7572
+ const got = v === undefined ? "undefined"
7573
+ : v === null ? "null"
7574
+ : typeof v === "object" ? "an instance of " + ((v.constructor && v.constructor.name) || "Object")
7575
+ : "type " + typeof v + " (" + String(v) + ")";
7576
+ const e = new TypeError('The "callback" argument must be of type function. Received ' + got);
7577
+ e.code = "ERR_INVALID_ARG_TYPE";
7578
+ return e;
7579
+ }
7580
+ // Node's Unzip contract sniffs the wrapper itself: gzip magic or zlib.
7581
+ function _isGzip(u8) { return u8.length >= 2 && u8[0] === 0x1f && u8[1] === 0x8b; }
7582
+ function _concat(chunks) {
7583
+ let n = 0;
7584
+ for (const c of chunks) n += c.length;
7585
+ const out = new Uint8Array(n);
7586
+ let o = 0;
7587
+ for (const c of chunks) { out.set(c, o); o += c.length; }
7588
+ return out;
7589
+ }
7590
+ function _process(mode, fmt, input) {
7591
+ const algo = mode === "compress" ? fmt : (fmt || (_isGzip(input) ? "gzip" : "deflate"));
7592
+ return mode === "compress" ? _c(input, algo) : _d(input, algo);
7593
+ }
7594
+ function _async(mode, fmt) {
7595
+ return (d, o, cb) => {
7596
+ const callback = typeof o === "function" ? o : cb;
7597
+ if (typeof callback !== "function") throw _invalidCallback(callback);
7598
+ // Two arms, not .then().catch(): a callback that throws on success must
7599
+ // not be handed its own exception as a second, failed invocation.
7600
+ _process(mode, fmt, _u8(d)).then(
7601
+ (r) => callback(null, r),
7602
+ (e) => callback(e instanceof Error ? e : new Error(String(e))),
7603
+ );
7604
+ };
7605
+ }
7606
+ // Live codec per Transform lifetime: every write feeds one real
7607
+ // CompressionStream/DecompressionStream writer (its queue serializes
7608
+ // ordering; the write promise settles before the callback), a reader pump
7609
+ // relays output as it is produced instead of accumulating the payload,
7610
+ // and flush closes the writer then awaits full drain before completing.
7611
+ // Chunks are copied so later caller mutations cannot reach the codec, and
7612
+ // createUnzip holds copies only until two bytes decide the wrapper.
7613
+ function _openCodec(openMode, openAlgo) {
7614
+ const cs = openMode === "compress" ? new CompressionStream(openAlgo) : new DecompressionStream(openAlgo);
7615
+ return { writer: cs.writable.getWriter(), reader: cs.readable.getReader() };
7616
+ }
7617
+ // The legacy Readable has no resume notification, so room below the
7618
+ // high-water mark is observed by yielding to the task queue until the
7619
+ // consumer drains or the stream is destroyed.
7620
+ async function _drainRoom(stream) {
7621
+ while (
7622
+ stream._readableState &&
7623
+ !stream._readableState.destroyed &&
7624
+ stream.readableLength >= stream._readableState.highWaterMark
7625
+ ) {
7626
+ await new Promise((tick) => setTimeout(tick, 0));
7627
+ }
7628
+ // True when the wait ended because the stream went away, not because
7629
+ // the consumer made room.
7630
+ return !!(stream._readableState && stream._readableState.destroyed);
7631
+ }
7632
+ function _streamFactory(factoryMode, fmt) {
7633
+ return () => {
7634
+ let writer = null;
7635
+ let reader = null;
7636
+ let drained = Promise.resolve();
7637
+ let pumpFailure = null;
7638
+ let algo = fmt;
7639
+ let held = [];
7640
+ let heldLen = 0;
7641
+ let destroyed = false;
7642
+ const t = new __streamMod.Transform({
7643
+ transform: async (chunk, _enc, cb) => {
7644
+ try {
7645
+ if (destroyed) return cb(_destroyedWrite());
7646
+ const bytes = _u8(chunk).slice();
7647
+ let feed = bytes;
7648
+ if (!writer) {
7649
+ if (fmt === null && heldLen + bytes.length < 2) {
7650
+ held.push(bytes);
7651
+ heldLen += bytes.length;
7652
+ return cb();
7653
+ }
7654
+ if (heldLen > 0) feed = _concat([...held, bytes]);
7655
+ held = [];
7656
+ heldLen = 0;
7657
+ if (fmt === null) algo = _isGzip(feed) ? "gzip" : "deflate";
7658
+ start();
7659
+ }
7660
+ await writer.write(feed);
7661
+ cb();
7662
+ } catch (e) {
7663
+ cb(destroyed ? _destroyedWrite() : factoryMode === "decompress" ? _decompressFailure(e) : e);
7664
+ }
7665
+ },
7666
+ flush: async (cb) => {
7667
+ try {
7668
+ if (destroyed) return cb(_destroyedWrite());
7669
+ if (!writer) {
7670
+ if (factoryMode !== "compress") {
7671
+ // Nothing was ever fed: the input ended before a member
7672
+ // began, which is Node's unexpected-end buffer error rather
7673
+ // than corrupt data. No codec is invented to hide it.
7674
+ return cb(_zbuf(null));
7675
+ }
7676
+ start();
7677
+ }
7678
+ await writer.close();
7679
+ await drained;
7680
+ cb(pumpFailure);
7681
+ } catch (e) {
7682
+ if (pumpFailure) return cb(pumpFailure);
7683
+ if (destroyed) return cb(_destroyedWrite());
7684
+ cb(factoryMode === "decompress" ? _decompressFailure(e) : e);
7685
+ }
7686
+ },
7687
+ });
7688
+ // Every destroy exit fires 'close' on the custom Transform — even when
7689
+ // the pump sits parked inside reader.read() on truncated input that can
7690
+ // never decode. Waking there is immediate: cancel settles the parked
7691
+ // read, abort fails queued writes, and writes arriving after destroy
7692
+ // report ERR_STREAM_DESTROYED instead of silent acceptance.
7693
+ t.on("close", () => {
7694
+ destroyed = true;
7695
+ _releaseCodec();
7696
+ });
7697
+ // One cleanup path for every destroy exit: cancel the readable side,
7698
+ // abort the writable side, and stop the pump.
7699
+ function _releaseCodec() {
7700
+ if (reader) reader.cancel().catch(() => {});
7701
+ if (writer) writer.abort().catch(() => {});
7702
+ }
7703
+ function start() {
7704
+ const opened = _openCodec(factoryMode, algo);
7705
+ writer = opened.writer;
7706
+ reader = opened.reader;
7707
+ drained = (async () => {
7708
+ try {
7709
+ for (;;) {
7710
+ // A destroy can land while a read is in flight; check on both
7711
+ // sides so nothing buffers after the stream went away.
7712
+ if (destroyed) return _releaseCodec();
7713
+ const next = await reader.read();
7714
+ if (destroyed) return _releaseCodec();
7715
+ if (next.done) {
7716
+ return;
7717
+ }
7718
+ // Backpressure: once push reports pressure, park the pump and
7719
+ // do not read again until the consumer drains below the
7720
+ // high-water mark. The pending writer.write promise then holds
7721
+ // the transform callback, so producer writes stop resolving
7722
+ // instead of accumulating. A destroy while parked releases the
7723
+ // codec through the close hook above.
7724
+ if (!t.push(__BufferMod.from(next.value))) {
7725
+ if (await _drainRoom(t)) return _releaseCodec();
7726
+ }
7727
+ }
7728
+ } catch (e) {
7729
+ pumpFailure = factoryMode === "decompress"
7730
+ ? _decompressFailure(e)
7731
+ : (e instanceof Error ? e : new Error(String(e)));
7732
+ }
7733
+ })();
7734
+ }
7735
+ return t;
7736
+ };
7737
+ }
7738
+ return {
7739
+ gzip: _async("compress", "gzip"),
7740
+ gunzip: _async("decompress", "gzip"),
7741
+ deflate: _async("compress", "deflate"),
7742
+ inflate: _async("decompress", "deflate"),
7743
+ deflateRaw: _async("compress", "deflate-raw"),
7744
+ inflateRaw: _async("decompress", "deflate-raw"),
7745
+ unzip: _async("decompress", null),
7746
+ gzipSync: (d, o) => _syncRefusal("gzipSync", "gzip"),
7747
+ gunzipSync: (d, o) => _syncRefusal("gunzipSync", "gunzip"),
7748
+ deflateSync: (d, o) => _syncRefusal("deflateSync", "deflate"),
7749
+ inflateSync: (d, o) => _syncRefusal("inflateSync", "inflate"),
7750
+ deflateRawSync: (d, o) => _syncRefusal("deflateRawSync", "deflateRaw"),
7751
+ inflateRawSync: (d, o) => _syncRefusal("inflateRawSync", "inflateRaw"),
7752
+ unzipSync: (d, o) => _syncRefusal("unzipSync", "unzip"),
7753
+ createGzip: _streamFactory("compress", "gzip"),
7754
+ createGunzip: _streamFactory("decompress", "gzip"),
7755
+ createDeflate: _streamFactory("compress", "deflate"),
7756
+ createInflate: _streamFactory("decompress", "deflate"),
7757
+ createDeflateRaw: _streamFactory("compress", "deflate-raw"),
7758
+ createInflateRaw: _streamFactory("decompress", "deflate-raw"),
7759
+ createUnzip: _streamFactory("decompress", null),
7760
+ constants: { Z_NO_FLUSH: 0, Z_PARTIAL_FLUSH: 1, Z_SYNC_FLUSH: 2, Z_FULL_FLUSH: 3, Z_FINISH: 4, Z_BEST_COMPRESSION: 9, Z_DEFAULT_COMPRESSION: -1 },
7761
+ };
7450
7762
  })();
7451
7763
  builtins.readline = (() => {
7452
7764
  function emitKeypressEvents(stream) {
@@ -8280,9 +8592,11 @@ function __resolveImportsField(name, fromDir) {
8280
8592
  function __mkCompiledFn(code) {
8281
8593
  const reFn = /(?:^|\n|;)\s*(?:const|let|var)\s+__filename\s*=/m;
8282
8594
  const reDn = /(?:^|\n|;)\s*(?:const|let|var)\s+__dirname\s*=/m;
8595
+ const reRequire = /(?:^|\n|;)\s*(?:const|let|var)\s+require\s*=/m;
8283
8596
  const fnName = reFn.test(code) ? "__filename__nimbus_unused" : "__filename";
8284
8597
  const dnName = reDn.test(code) ? "__dirname__nimbus_unused" : "__dirname";
8285
- return new Function("exports", "require", "module", fnName, dnName, code);
8598
+ const requireName = reRequire.test(code) ? "require__nimbus_unused" : "require";
8599
+ return new Function("exports", requireName, "module", fnName, dnName, code);
8286
8600
  }
8287
8601
 
8288
8602
  function __exportsTarget(mod) {
@@ -8361,6 +8675,7 @@ function __loadModule(resolvedPath) {
8361
8675
  };
8362
8676
  scopedRequire.cache = __moduleCache;
8363
8677
  scopedRequire.main = __require.main;
8678
+ mod.require = scopedRequire;
8364
8679
 
8365
8680
  // X.5-M3: thread currently-loading module path through globalThis so the
8366
8681
  // URL shim null-base fallback (in node-shims url module) can compose
@@ -1835,6 +1835,23 @@
1835
1835
  }
1836
1836
  }
1837
1837
 
1838
+ function previewNavigation(url) {
1839
+ try {
1840
+ const parsed = new URL(url, location.origin);
1841
+ if (!parsed.searchParams.has('nimbus_token')) {
1842
+ return { url, attachUrl: null, attachPending: false };
1843
+ }
1844
+ parsed.searchParams.delete('nimbus_token');
1845
+ return {
1846
+ url: parsed.href,
1847
+ attachUrl: url,
1848
+ attachPending: true,
1849
+ };
1850
+ } catch {
1851
+ return { url, attachUrl: null, attachPending: false };
1852
+ }
1853
+ }
1854
+
1838
1855
  function absolutePreviewUrl(url) {
1839
1856
  return new URL(url, location.origin).href;
1840
1857
  }
@@ -1919,6 +1936,39 @@
1919
1936
  return ensurePreviewTab(appTabFor('vite')).id;
1920
1937
  }
1921
1938
 
1939
+ function previewFrameOwnsAttach(tab) {
1940
+ if (!previewFrame || !tab.attachUrl) return false;
1941
+ const src = previewFrame.getAttribute('src') || '';
1942
+ return previewFrame.dataset.previewTabId === tab.id
1943
+ && previewFrame.dataset.previewAttachGeneration === String(tab.attachGeneration || 0)
1944
+ && absolutePreviewUrl(src) === absolutePreviewUrl(tab.attachUrl);
1945
+ }
1946
+
1947
+ function refreshPortPreviewAttach(tab) {
1948
+ if (!tab || tab.type !== 'port' || tab.attachRefresh) return;
1949
+ const generation = (tab.attachGeneration || 0) + 1;
1950
+ tab.attachGeneration = generation;
1951
+ tab.attachPending = true;
1952
+ tab.attachInFlight = false;
1953
+ tab.attachUrl = null;
1954
+ tab.attachRefresh = portPreviewUrl(tab.port).then((minted) => {
1955
+ const current = previewTabs.get(tab.id);
1956
+ if (!current || current.attachGeneration !== generation) return;
1957
+ Object.assign(current, previewNavigation(minted), {
1958
+ attachGeneration: generation,
1959
+ attachInFlight: false,
1960
+ attachRefresh: null,
1961
+ });
1962
+ if (activePreviewTabId === current.id) activatePreviewTab(current.id);
1963
+ }).catch(() => {
1964
+ const current = previewTabs.get(tab.id);
1965
+ if (!current || current.attachGeneration !== generation) return;
1966
+ current.attachPending = false;
1967
+ current.attachInFlight = false;
1968
+ current.attachRefresh = null;
1969
+ });
1970
+ }
1971
+
1922
1972
  function activatePreviewTab(id) {
1923
1973
  const tab = previewTabs.get(id);
1924
1974
  if (!tab) return;
@@ -1937,8 +1987,44 @@
1937
1987
  if (markdownPreview) markdownPreview.classList.remove('active');
1938
1988
  if (previewFrame) {
1939
1989
  previewFrame.style.display = 'block';
1940
- const next = absolutePreviewUrl(tab.url);
1941
- if (previewFrame.src !== next) previewFrame.src = tab.url;
1990
+ if (tab.type === 'port' && tab.attachPending) {
1991
+ if (tab.attachInFlight) {
1992
+ if (!previewFrameOwnsAttach(tab)) refreshPortPreviewAttach(tab);
1993
+ setPreviewUrlBar(tab.url);
1994
+ return;
1995
+ }
1996
+ if (!tab.attachUrl) {
1997
+ refreshPortPreviewAttach(tab);
1998
+ setPreviewUrlBar(tab.url);
1999
+ return;
2000
+ }
2001
+ const generation = (tab.attachGeneration || 0) + 1;
2002
+ const attachUrl = tab.attachUrl;
2003
+ tab.attachGeneration = generation;
2004
+ tab.attachInFlight = true;
2005
+ previewFrame.dataset.previewTabId = tab.id;
2006
+ previewFrame.dataset.previewAttachGeneration = String(generation);
2007
+ previewFrame.addEventListener('load', () => {
2008
+ const current = previewTabs.get(tab.id);
2009
+ if (!current
2010
+ || current.attachGeneration !== generation
2011
+ || previewFrame.dataset.previewTabId !== tab.id
2012
+ || previewFrame.dataset.previewAttachGeneration !== String(generation)
2013
+ || absolutePreviewUrl(previewFrame.getAttribute('src') || '') !== absolutePreviewUrl(attachUrl)) {
2014
+ return;
2015
+ }
2016
+ current.attachPending = false;
2017
+ current.attachInFlight = false;
2018
+ current.attachUrl = null;
2019
+ current.attachRefresh = null;
2020
+ }, { once: true });
2021
+ previewFrame.src = attachUrl;
2022
+ } else {
2023
+ previewFrame.dataset.previewTabId = tab.id;
2024
+ delete previewFrame.dataset.previewAttachGeneration;
2025
+ const next = absolutePreviewUrl(tab.url);
2026
+ if (previewFrame.src !== next) previewFrame.src = tab.url;
2027
+ }
1942
2028
  }
1943
2029
  setPreviewUrlBar(tab.url);
1944
2030
  }
@@ -1982,7 +2068,13 @@
1982
2068
 
1983
2069
  for (const entry of visiblePorts) {
1984
2070
  const id = 'port:' + entry.port;
1985
- const existed = previewTabs.has(id);
2071
+ let existing = previewTabs.get(id);
2072
+ let navigation = {};
2073
+ if (!existing) {
2074
+ const minted = await portPreviewUrl(entry.port);
2075
+ existing = previewTabs.get(id);
2076
+ if (!existing) navigation = previewNavigation(minted);
2077
+ }
1986
2078
  ensurePreviewTab({
1987
2079
  id,
1988
2080
  type: 'port',
@@ -1990,9 +2082,9 @@
1990
2082
  pid: entry.pid,
1991
2083
  label: ':' + entry.port,
1992
2084
  title: 'Port ' + entry.port,
1993
- url: await portPreviewUrl(entry.port),
2085
+ ...navigation,
1994
2086
  running: true,
1995
- }, { focusNew: !existed });
2087
+ }, { focusNew: !existing });
1996
2088
  }
1997
2089
 
1998
2090
  if (activeWasRemoved || !activePreviewTabId || !previewTabs.has(activePreviewTabId)) {
@@ -2083,6 +2175,10 @@
2083
2175
  Editor.refreshMarkdownPreview().catch(() => {});
2084
2176
  return;
2085
2177
  }
2178
+ if (active?.type === 'port' && active.attachPending) {
2179
+ refreshPortPreviewAttach(active);
2180
+ return;
2181
+ }
2086
2182
  // Refreshing without a running backend should re-poll the
2087
2183
  // placeholder so users see dev-server status update promptly.
2088
2184
  if (!active || previewFrame.src === 'about:blank') {