@nimbus-sh/worker 0.5.0 → 0.7.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 +2 -2
- package/dist/_shared/preview-host.d.ts +26 -8
- package/dist/_shared/preview-host.d.ts.map +1 -1
- package/dist/_shared/preview-host.js +51 -11
- package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
- package/dist/cirrus-npm-cjs.generated.js +1 -1
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +6 -5
- package/dist/facets/compose.d.ts +148 -0
- package/dist/facets/compose.d.ts.map +1 -0
- package/dist/facets/compose.js +146 -0
- package/dist/facets/durable-images.d.ts +18 -5
- package/dist/facets/durable-images.d.ts.map +1 -1
- package/dist/facets/durable-images.js +36 -7
- package/dist/facets/durable-slots.d.ts +1 -1
- package/dist/facets/durable-slots.d.ts.map +1 -1
- package/dist/facets/durable-slots.js +2 -1
- package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
- package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
- package/dist/facets/esbuild-bundle-pool.js +122 -0
- package/dist/facets/exec-telemetry.d.ts +8 -0
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +337 -123
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +1100 -449
- package/dist/facets/process.d.ts +20 -16
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +59 -28
- package/dist/facets/real-vite-fs-shim.js +2 -2
- package/dist/facets/resident-identity.d.ts +23 -0
- package/dist/facets/resident-identity.d.ts.map +1 -0
- package/dist/facets/resident-identity.js +31 -0
- package/dist/facets/vite-dev-server.d.ts +25 -47
- package/dist/facets/vite-dev-server.d.ts.map +1 -1
- package/dist/facets/vite-dev-server.js +185 -219
- package/dist/facets/wasm-image-digest.d.ts +25 -0
- package/dist/facets/wasm-image-digest.d.ts.map +1 -0
- package/dist/facets/wasm-image-digest.js +25 -0
- package/dist/facets/wasm-swap-registry.d.ts +51 -92
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +68 -227
- package/dist/git/commands.d.ts +0 -1
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +0 -3
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +6 -5
- package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +3 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +34 -108
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/npm/install-batch-facet.d.ts +3 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +45 -15
- package/dist/npm/installer.d.ts +22 -17
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +209 -193
- package/dist/npm/pre-bundle-facet.d.ts +1 -1
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +1 -1
- package/dist/npm/resolve-facet.d.ts +6 -0
- package/dist/npm/resolve-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.d.ts +21 -20
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +85 -84
- package/dist/npm/semver.d.ts +59 -0
- package/dist/npm/semver.d.ts.map +1 -0
- package/dist/npm/semver.js +226 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +47 -10
- package/dist/router/public-directory.d.ts +5 -1
- package/dist/router/public-directory.d.ts.map +1 -1
- package/dist/router/public-directory.js +9 -2
- package/dist/router/remote-api.d.ts +18 -0
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +64 -5
- package/dist/runtime/cpython-resident.d.ts.map +1 -1
- package/dist/runtime/cpython-resident.js +3 -1
- package/dist/runtime/html-entrypoint.d.ts +7 -0
- package/dist/runtime/html-entrypoint.d.ts.map +1 -1
- package/dist/runtime/html-entrypoint.js +18 -2
- package/dist/runtime/node-runner.d.ts +3 -0
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-runner.js +7 -0
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +305 -29
- package/dist/runtime/opencode-facet-runner.js +2 -2
- package/dist/runtime/package-manager.d.ts +44 -1
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +168 -3
- package/dist/runtime/ruby-resident.d.ts.map +1 -1
- package/dist/runtime/ruby-resident.js +2 -0
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/agent.js +28 -1
- package/dist/session/helpers.d.ts +38 -0
- package/dist/session/helpers.d.ts.map +1 -1
- package/dist/session/helpers.js +76 -0
- package/dist/session/init-phases.d.ts +1 -1
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +2 -12
- package/dist/session/init.d.ts +15 -1
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +91 -603
- package/dist/session/keys.d.ts +4 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +4 -0
- package/dist/session/nimbus-session.d.ts +90 -23
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +200 -110
- package/dist/session/npm-install-port.d.ts +16 -0
- package/dist/session/npm-install-port.d.ts.map +1 -0
- package/dist/session/npm-install-port.js +37 -0
- package/dist/session/port-capability.d.ts +90 -0
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +219 -9
- package/dist/session/programmatic.d.ts +125 -5
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +439 -89
- package/dist/session/routes.d.ts +39 -17
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +67 -131
- package/dist/session/rpc.d.ts +39 -16
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +137 -130
- package/dist/session/serving-port.d.ts +27 -0
- package/dist/session/serving-port.d.ts.map +1 -0
- package/dist/session/serving-port.js +27 -0
- package/dist/session/start-real-vite.d.ts +12 -0
- package/dist/session/start-real-vite.d.ts.map +1 -1
- package/dist/session/start-real-vite.js +4 -4
- package/dist/session/supervisor-op.d.ts +41 -4
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +101 -73
- package/dist/session/supervisor-rpc.d.ts +7 -7
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +6 -3
- package/dist/session/vite-command.d.ts +18 -0
- package/dist/session/vite-command.d.ts.map +1 -0
- package/dist/session/vite-command.js +609 -0
- package/dist/session/ws.d.ts +61 -0
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +122 -12
- package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +12 -85
- package/package.json +24 -4
- package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
- package/public/index.html +1 -1
- package/scripts/bundle-facet-workers.mjs +1 -1
- package/scripts/bundle-npm-cjs.mjs +2 -2
- package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
- package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
- package/dist/facets/on-demand-bundle-gate.js +0 -125
- package/dist/npm/install-args.d.ts +0 -21
- package/dist/npm/install-args.d.ts.map +0 -1
- package/dist/npm/install-args.js +0 -70
- package/dist/npm/npm-log.d.ts +0 -31
- package/dist/npm/npm-log.d.ts.map +0 -1
- package/dist/npm/npm-log.js +0 -51
|
@@ -11,15 +11,17 @@ import { PID_GEN_STRIDE } from '@nimbus-sh/core/runtime/process-table.js';
|
|
|
11
11
|
import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
|
|
12
12
|
import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
|
|
13
13
|
import { PortRegistry, createPortCapability } from '@nimbus-sh/core/runtime/port-registry.js';
|
|
14
|
-
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
14
|
+
import { CRED_KERNEL, requireVfsCred } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
15
15
|
import { endProcessInput, resizeProcess, signalProcess, writeProcessInput, } from '@nimbus-sh/core/runtime/process-input-routing.js';
|
|
16
16
|
import { z } from 'zod/v4';
|
|
17
17
|
import { SESSION_DESTROYED_KEY, SHELL_STATE_KEY_PREFIX, VITE_CONFIG_KEY } from './keys.js';
|
|
18
|
-
import { clearPortCapability,
|
|
19
|
-
import { PORT_CAPABILITY_KEY_PREFIX } from './keys.js';
|
|
18
|
+
import { clearPortCapability, isValidAppName, persistPortCapability, portRecordKey, readPortReservation, readPortReservationByName, readPortReservationByOwner, reservePort, restorePortCapability, rotatePortCapability, } from './port-capability.js';
|
|
20
19
|
import { bindPublicPortCapability, unbindPublicPortCapability } from '../router/public-directory.js';
|
|
20
|
+
import { buildPreviewHost, buildPublicPreviewHost, isPreviewHostSafeSid, readPreviewHostSuffix, } from '../_shared/preview-host.js';
|
|
21
|
+
import { RESTART_POLICY_ENV } from '../facets/manager.js';
|
|
21
22
|
import { GENERATION_KEY, assumeGeneration, generation } from '@nimbus-sh/fabric/generation.js';
|
|
22
23
|
import { HeadlessTerminal, Shell } from '@nimbus-sh/core/substrate/lifo/index.js';
|
|
24
|
+
import { textSink } from '@nimbus-sh/core/_shared/bytes.js';
|
|
23
25
|
const ShellIdSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
24
26
|
const ShellStateSchema = z.object({
|
|
25
27
|
cwd: z.string().startsWith('/'),
|
|
@@ -216,14 +218,13 @@ function startShellJob(self, command, options, job, scoped) {
|
|
|
216
218
|
catch { /* already settled */ }
|
|
217
219
|
});
|
|
218
220
|
const emit = (stream, sink) => (data) => {
|
|
219
|
-
const text = String(data);
|
|
220
221
|
if (job.background) {
|
|
221
222
|
try {
|
|
222
|
-
self.processes.
|
|
223
|
+
self.processes.appendOutputBytes(pid, stream, data);
|
|
223
224
|
}
|
|
224
225
|
catch { /* ring gone */ }
|
|
225
226
|
}
|
|
226
|
-
sink?.(
|
|
227
|
+
sink?.(data);
|
|
227
228
|
};
|
|
228
229
|
const run = shell.execute(line, {
|
|
229
230
|
// A named shell already holds its own cwd and env; passing them again
|
|
@@ -259,7 +260,20 @@ function startShellJob(self, command, options, job, scoped) {
|
|
|
259
260
|
}
|
|
260
261
|
catch { } } };
|
|
261
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The shell, process table and VFS all key on absolute POSIX paths. A
|
|
265
|
+
* relative `cwd` arriving here means a caller skipped its own boundary
|
|
266
|
+
* normalization (the SDK resolves it); handed to the shell it degrades to
|
|
267
|
+
* silently wrong cwd semantics and ENOENT writes. Name the field and fail.
|
|
268
|
+
*/
|
|
269
|
+
function assertAbsoluteExecCwd(options) {
|
|
270
|
+
const cwd = options.cwd;
|
|
271
|
+
if (cwd !== undefined && (typeof cwd !== 'string' || !cwd.startsWith('/'))) {
|
|
272
|
+
throw new Error(`cwd must be an absolute POSIX path starting with '/', got ${JSON.stringify(cwd)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
262
275
|
export async function rpcExec(self, command, options = {}) {
|
|
276
|
+
assertAbsoluteExecCwd(options);
|
|
263
277
|
await ensureProgrammaticReady(self, options);
|
|
264
278
|
return withShellState(self, options, false, (scoped) => execOnShell(self, command, options, scoped));
|
|
265
279
|
}
|
|
@@ -271,8 +285,8 @@ async function execOnShell(self, command, options, scoped) {
|
|
|
271
285
|
let timedOut = false;
|
|
272
286
|
const job = startShellJob(self, command, options, {
|
|
273
287
|
background: false,
|
|
274
|
-
onStdout: (d) => stdout.push(d),
|
|
275
|
-
onStderr: (d) => stderr.push(d),
|
|
288
|
+
onStdout: textSink((d) => stdout.push(d)),
|
|
289
|
+
onStderr: textSink((d) => stderr.push(d)),
|
|
276
290
|
}, scoped);
|
|
277
291
|
let result;
|
|
278
292
|
try {
|
|
@@ -351,8 +365,18 @@ function collectJobOutput(self, pid) {
|
|
|
351
365
|
* process surface (`listProcesses`, `processLogs`, `killProcess`).
|
|
352
366
|
*/
|
|
353
367
|
export async function rpcStartProcess(self, command, options = {}) {
|
|
368
|
+
assertAbsoluteExecCwd(options);
|
|
354
369
|
await ensureProgrammaticReady(self, options);
|
|
355
|
-
|
|
370
|
+
if (options.restart !== undefined && options.restart !== 'never' && options.restart !== 'on-failure') {
|
|
371
|
+
throw new Error(`startProcess: restart must be 'never' or 'on-failure', got ${String(options.restart)}`);
|
|
372
|
+
}
|
|
373
|
+
// The policy rides the command's environment: the resident the command
|
|
374
|
+
// starts reads it off its env at launch and journals it, so the shell,
|
|
375
|
+
// the SDK and a child_process spawn all carry it the same way.
|
|
376
|
+
const withPolicy = options.restart === 'on-failure'
|
|
377
|
+
? { ...options, env: { ...(options.env ?? {}), [RESTART_POLICY_ENV]: 'on-failure' } }
|
|
378
|
+
: options;
|
|
379
|
+
return withShellState(self, withPolicy, true, (scoped) => startOnShell(self, command, withPolicy, scoped));
|
|
356
380
|
}
|
|
357
381
|
async function startOnShell(self, command, options, scoped) {
|
|
358
382
|
const job = startShellJob(self, command, options, { background: true }, scoped);
|
|
@@ -397,7 +421,15 @@ async function startOnShell(self, command, options, scoped) {
|
|
|
397
421
|
* the handoff, the facet is the live process, and it reports its own exit.
|
|
398
422
|
*/
|
|
399
423
|
function finishBackgroundJob(self, pid, command, exitCode) {
|
|
400
|
-
|
|
424
|
+
// The shell line returned, but the pid may still be serving: a resident
|
|
425
|
+
// facet the node runner handed off under this pid, or an in-process dev
|
|
426
|
+
// server a builtin (`vite`) adopted this pid for and registered a port
|
|
427
|
+
// under. Either way the process is alive — marking it exited would put a
|
|
428
|
+
// dead pid on a live port, and the app verbs identify a server by the pid
|
|
429
|
+
// serving its port.
|
|
430
|
+
const stillServing = self.facetManager?.hasResidentProcess(pid) === true
|
|
431
|
+
|| self.portRegistry.getAll().some((live) => live.pid === pid);
|
|
432
|
+
if (exitCode === 0 && stillServing)
|
|
401
433
|
return;
|
|
402
434
|
self.processes.exit(pid, exitCode);
|
|
403
435
|
if (!self.processes.getExit(pid))
|
|
@@ -517,52 +549,380 @@ export async function rpcProcessLogs(self, pid, options = {}) {
|
|
|
517
549
|
}
|
|
518
550
|
export async function rpcListPorts(self) {
|
|
519
551
|
await ensureProgrammaticReady(self);
|
|
520
|
-
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
// survive anything.
|
|
524
|
-
await Promise.all(entries.map((entry) => persistPortCapability(self, entry.port, entry.capability)));
|
|
525
|
-
return entries.map(serializePort);
|
|
552
|
+
// The composed manager's copy is the one implementation — persistence
|
|
553
|
+
// at tell-time included.
|
|
554
|
+
return self.ensureFacetManager().apps.listPorts();
|
|
526
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Browser-facing URL for an application, built inside the session: the
|
|
558
|
+
* host form when the deployment carries a preview suffix (name first, port
|
|
559
|
+
* otherwise; the public bearer form when public and a capability exists),
|
|
560
|
+
* else the path form on the origin the session was last reached at. Null
|
|
561
|
+
* when the session has never been reached over HTTP and has no suffix —
|
|
562
|
+
* an embedder builds its own from the port and capability in that case.
|
|
563
|
+
*/
|
|
564
|
+
export function appUrl(self, app) {
|
|
565
|
+
const sid = sessionIdOf(self);
|
|
566
|
+
const suffix = readPreviewHostSuffix(self.env);
|
|
567
|
+
if (suffix !== null && sid !== null && isPreviewHostSafeSid(sid)) {
|
|
568
|
+
const label = app.name ?? app.port;
|
|
569
|
+
if (app.visibility === 'public' && typeof app.capability === 'string') {
|
|
570
|
+
return `https://${buildPublicPreviewHost(sid, label, app.capability, suffix)}/`;
|
|
571
|
+
}
|
|
572
|
+
return `https://${buildPreviewHost(sid, label, suffix)}/`;
|
|
573
|
+
}
|
|
574
|
+
const origin = self.sessionOrigin;
|
|
575
|
+
if (!origin || sid === null)
|
|
576
|
+
return null;
|
|
577
|
+
const base = self.sessionBasePath || `/s/${sid}`;
|
|
578
|
+
return app.name ? `${origin}${base}/app/${app.name}/` : `${origin}${base}/port/${app.port}/`;
|
|
579
|
+
}
|
|
580
|
+
/** The sid this DO serves: the last segment of its name, `tn:sub:sid`. */
|
|
581
|
+
function sessionIdOf(self) {
|
|
582
|
+
const name = self.ctx.id?.name;
|
|
583
|
+
if (typeof name === 'string' && name.length > 0) {
|
|
584
|
+
const cut = name.lastIndexOf(':');
|
|
585
|
+
if (cut > 0)
|
|
586
|
+
return name.slice(cut + 1);
|
|
587
|
+
}
|
|
588
|
+
const fromBase = (self.sessionBasePath || '').replace(/^\/s\//, '');
|
|
589
|
+
return fromBase.length > 0 ? fromBase : null;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* The port-centric surface, kept for every caller that has a port and
|
|
593
|
+
* nothing else — a dev server or a process outside the
|
|
594
|
+
* resident lifecycle. When the port's occupant carries an identity the
|
|
595
|
+
* exposure is the same lazy reservation `apps.expose` makes; when it does
|
|
596
|
+
* not, this compatibility path writes the row port-only, as before. One implementation:
|
|
597
|
+
* `applyExposure` below.
|
|
598
|
+
*/
|
|
527
599
|
export async function rpcExposePort(self, port, options) {
|
|
528
600
|
await ensureProgrammaticReady(self);
|
|
529
601
|
const n = Number(port);
|
|
530
602
|
const entry = self.portRegistry.get(n);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
603
|
+
const identity = entry === undefined ? null : await self.facetManager.residentIdentity(entry.pid);
|
|
604
|
+
if (identity?.ephemeral) {
|
|
605
|
+
throw new Error(`port ${n} is served by the second instance of its program, which is not the durable one`);
|
|
606
|
+
}
|
|
607
|
+
const exposed = await applyExposure(self, n, identity?.owner ?? null, options ?? {});
|
|
608
|
+
return {
|
|
609
|
+
port: n,
|
|
610
|
+
listening: !!entry,
|
|
611
|
+
pid: entry?.pid ?? null,
|
|
612
|
+
registeredAt: entry?.registeredAt ?? null,
|
|
613
|
+
capability: exposed.capability,
|
|
614
|
+
visibility: exposed.visibility,
|
|
615
|
+
owner: exposed.owner,
|
|
616
|
+
name: exposed.name,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The identity-centric surface: the target names a running application —
|
|
621
|
+
* by port, by pid, or by name — and the exposure is what makes it durable
|
|
622
|
+
* under its identity: the port is reserved for the owner lazily, the
|
|
623
|
+
* capability minted when public and bound in the directory, the name
|
|
624
|
+
* stored on the reservation. Returns the address the caller can reach.
|
|
625
|
+
*/
|
|
626
|
+
export async function rpcExposeApp(self, target, options = {}) {
|
|
627
|
+
await ensureProgrammaticReady(self);
|
|
628
|
+
const resolved = await resolveAppTarget(self, target);
|
|
629
|
+
if (resolved.port === null) {
|
|
630
|
+
throw new Error(`${describeTarget(target)} has not bound a port yet — expose it once it is listening`);
|
|
631
|
+
}
|
|
632
|
+
if (resolved.owner === null) {
|
|
633
|
+
// Every running process has an identity, so this is only a port nothing
|
|
634
|
+
// serves and no reservation names.
|
|
635
|
+
throw new Error(`nothing is serving ${describeTarget(target)} — start the server, then expose it`);
|
|
636
|
+
}
|
|
637
|
+
const exposed = await applyExposure(self, resolved.port, resolved.owner, options);
|
|
638
|
+
const live = self.portRegistry.get(resolved.port);
|
|
639
|
+
return {
|
|
640
|
+
owner: resolved.owner,
|
|
641
|
+
name: exposed.name,
|
|
642
|
+
port: resolved.port,
|
|
643
|
+
pid: live?.pid ?? null,
|
|
644
|
+
capability: exposed.capability,
|
|
645
|
+
visibility: exposed.visibility,
|
|
646
|
+
url: appUrl(self, { port: resolved.port, ...exposed }),
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* The one exposure implementation. With an owner: reserve the port for it
|
|
651
|
+
* (a conflict if another owner holds it), carrying the name and visibility
|
|
652
|
+
* onto the reservation and adopting the live capability so the URL in
|
|
653
|
+
* circulation is the one the reservation re-adopts after a reset; public
|
|
654
|
+
* exposures mint a capability if none is live yet and bind the directory.
|
|
655
|
+
* Without an owner: the row is written port-only, exactly as `exposePort`
|
|
656
|
+
* always did.
|
|
657
|
+
*/
|
|
658
|
+
async function applyExposure(self, port, owner, options) {
|
|
659
|
+
if (options.name !== undefined && !isValidAppName(options.name)) {
|
|
660
|
+
throw new Error(`'${options.name}' is not a valid app name: one DNS label, not all digits, not 24 hex`);
|
|
661
|
+
}
|
|
662
|
+
const entry = self.portRegistry.get(port);
|
|
663
|
+
if (owner !== null) {
|
|
664
|
+
await assertServingOwner(self, port, owner);
|
|
665
|
+
const occupied = new Set(self.portRegistry.getAll().map((live) => live.port));
|
|
666
|
+
occupied.delete(port);
|
|
667
|
+
await reservePort(self.ctx, {
|
|
668
|
+
owner,
|
|
669
|
+
kind: 'derived',
|
|
670
|
+
preferredPort: port,
|
|
671
|
+
occupiedPorts: occupied,
|
|
672
|
+
...(entry !== undefined ? { capability: entry.capability } : {}),
|
|
673
|
+
...(options.visibility !== undefined ? { visibility: options.visibility } : {}),
|
|
674
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
if (entry)
|
|
679
|
+
await persistPortCapability(self, port, entry.capability);
|
|
680
|
+
// A visibility or name choice is a write on the stored row, not a
|
|
681
|
+
// reservation claim: owner and capability stay exactly as they were.
|
|
682
|
+
if (options.visibility !== undefined || options.name !== undefined) {
|
|
683
|
+
await self.ctx.storage.transaction(async (txn) => {
|
|
684
|
+
const stored = await readPortReservation({ storage: txn }, port);
|
|
685
|
+
if (stored === null)
|
|
686
|
+
return;
|
|
687
|
+
const visibility = options.visibility ?? stored.visibility;
|
|
688
|
+
const name = options.name ?? stored.name;
|
|
689
|
+
if (visibility === stored.visibility && name === stored.name)
|
|
690
|
+
return;
|
|
691
|
+
await txn.put(portRecordKey(port), {
|
|
543
692
|
owner: stored.owner,
|
|
693
|
+
kind: stored.kind,
|
|
544
694
|
capability: stored.capability,
|
|
545
|
-
visibility
|
|
695
|
+
visibility,
|
|
696
|
+
...(name !== undefined ? { name } : {}),
|
|
546
697
|
});
|
|
547
|
-
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
let record = await readPortReservation(self.ctx, port);
|
|
702
|
+
if (record?.visibility === 'public' && record.capability === null && owner !== null) {
|
|
703
|
+
// Public before the application has bound: mint now, so the URL exists
|
|
704
|
+
// and the binding re-adopts it, exactly as ensureDurableApp does.
|
|
705
|
+
await reservePort(self.ctx, {
|
|
706
|
+
owner, preferredPort: port, occupiedPorts: new Set(), capability: createPortCapability(),
|
|
548
707
|
});
|
|
708
|
+
record = await readPortReservation(self.ctx, port);
|
|
549
709
|
}
|
|
550
|
-
|
|
551
|
-
|
|
710
|
+
// The live registration must answer the stored capability, or a URL
|
|
711
|
+
// minted before the bind 404s until the next restore.
|
|
712
|
+
if (record?.capability !== null && record?.capability !== undefined) {
|
|
713
|
+
const capability = record.capability;
|
|
714
|
+
if (record.owner !== null) {
|
|
715
|
+
await assertServingOwner(self, port, record.owner, () => self.portRegistry.restoreCapability(port, capability));
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
self.portRegistry.restoreCapability(port, capability);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (record?.visibility === 'public' && record.capability !== null) {
|
|
552
722
|
// A port that goes public must be resolvable: the capability the URL
|
|
553
723
|
// carries has to name this session in the directory, and a non-legacy
|
|
554
724
|
// deployment without the binding is refused loudly — never half-public.
|
|
555
|
-
await bindPublicPortCapability(self,
|
|
725
|
+
await bindPublicPortCapability(self, record.capability, port, record.name);
|
|
556
726
|
}
|
|
557
727
|
return {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
registeredAt: entry?.registeredAt ?? null,
|
|
562
|
-
capability: entry?.capability ?? null,
|
|
728
|
+
owner: record?.owner ?? owner,
|
|
729
|
+
name: record?.name ?? null,
|
|
730
|
+
capability: record?.capability ?? entry?.capability ?? null,
|
|
563
731
|
visibility: record?.visibility ?? 'scoped',
|
|
564
732
|
};
|
|
565
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Mint a new capability for the application and rebind the directory:
|
|
736
|
+
* every URL built on the old one stops resolving. The registry adopts the
|
|
737
|
+
* new value at once if the port is live, so the new URL answers without
|
|
738
|
+
* waiting for a restore.
|
|
739
|
+
*/
|
|
740
|
+
export async function rpcRotateLink(self, target) {
|
|
741
|
+
await ensureProgrammaticReady(self);
|
|
742
|
+
const resolved = await resolveAppTarget(self, target);
|
|
743
|
+
if (resolved.port === null || resolved.reservation === null) {
|
|
744
|
+
throw new Error(`${describeTarget(target)} is not exposed — nothing to rotate`);
|
|
745
|
+
}
|
|
746
|
+
const previous = resolved.reservation;
|
|
747
|
+
if (previous.owner !== null)
|
|
748
|
+
await assertServingOwner(self, resolved.port, previous.owner);
|
|
749
|
+
if (previous.visibility === 'public' && previous.capability !== null) {
|
|
750
|
+
await unbindPublicPortCapability(self, previous.capability);
|
|
751
|
+
}
|
|
752
|
+
if (previous.owner !== null)
|
|
753
|
+
await assertServingOwner(self, resolved.port, previous.owner);
|
|
754
|
+
const capability = await rotatePortCapability(self, resolved.port, createPortCapability());
|
|
755
|
+
if (capability === null)
|
|
756
|
+
throw new Error(`${describeTarget(target)} is not exposed — nothing to rotate`);
|
|
757
|
+
if (previous.owner !== null) {
|
|
758
|
+
await assertServingOwner(self, resolved.port, previous.owner, () => self.portRegistry.restoreCapability(resolved.port, capability));
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
self.portRegistry.restoreCapability(resolved.port, capability);
|
|
762
|
+
}
|
|
763
|
+
if (previous.visibility === 'public') {
|
|
764
|
+
await bindPublicPortCapability(self, capability, resolved.port, previous.name);
|
|
765
|
+
}
|
|
766
|
+
const live = self.portRegistry.get(resolved.port);
|
|
767
|
+
return {
|
|
768
|
+
owner: resolved.owner ?? previous.owner ?? '',
|
|
769
|
+
name: previous.name ?? null,
|
|
770
|
+
port: resolved.port,
|
|
771
|
+
pid: live?.pid ?? null,
|
|
772
|
+
capability,
|
|
773
|
+
visibility: previous.visibility,
|
|
774
|
+
url: appUrl(self, { port: resolved.port, name: previous.name, capability, visibility: previous.visibility }),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
/** A name or owner identifies a reservation, never whoever happens to occupy its port. */
|
|
778
|
+
async function assertServingOwner(self, port, owner, adopt) {
|
|
779
|
+
for (;;) {
|
|
780
|
+
const live = self.portRegistry.get(port);
|
|
781
|
+
if (live === undefined)
|
|
782
|
+
return;
|
|
783
|
+
const identity = await self.facetManager.residentIdentity(live.pid);
|
|
784
|
+
if (self.portRegistry.get(port)?.pid !== live.pid)
|
|
785
|
+
continue;
|
|
786
|
+
if (identity?.owner !== owner || identity.ephemeral) {
|
|
787
|
+
throw new Error(`port ${port} is served by a different process (owner ${identity?.owner ?? 'none'})`);
|
|
788
|
+
}
|
|
789
|
+
adopt?.();
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/** Every stamped identity, with the URL each is reachable at. */
|
|
794
|
+
export async function rpcListApps(self) {
|
|
795
|
+
await ensureProgrammaticReady(self);
|
|
796
|
+
const apps = await self.facetManager.listResidentApps();
|
|
797
|
+
return apps.map((app) => ({
|
|
798
|
+
...app,
|
|
799
|
+
url: app.port === null ? null : appUrl(self, { ...app, port: app.port }),
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* End an application: kill its live pids, release the reservation, purge
|
|
804
|
+
* its journal rows, free the durable slot and its storage, unbind the
|
|
805
|
+
* directory — `removeDurableApp`, addressed by any target.
|
|
806
|
+
*/
|
|
807
|
+
export async function rpcRemoveApp(self, target) {
|
|
808
|
+
await ensureProgrammaticReady(self);
|
|
809
|
+
const resolved = await resolveAppTarget(self, target);
|
|
810
|
+
if (resolved.owner === null) {
|
|
811
|
+
throw new Error(`${describeTarget(target)} names no application — nothing to remove`);
|
|
812
|
+
}
|
|
813
|
+
const owner = resolved.owner;
|
|
814
|
+
// The manager's removal kills what the journal knows. A server nothing
|
|
815
|
+
// journalled — the dev servers above all — is found the way every verb
|
|
816
|
+
// finds it, by the identity of the pid serving a port, and ended through
|
|
817
|
+
// the session's own kill, which is the one that also stops an in-process
|
|
818
|
+
// dev server rather than only marking its pid dead.
|
|
819
|
+
const fm = self.facetManager;
|
|
820
|
+
let killed = 0;
|
|
821
|
+
for (const live of self.portRegistry.getAll()) {
|
|
822
|
+
const identity = await fm.residentIdentity(live.pid);
|
|
823
|
+
if (identity === null || identity.ephemeral || identity.owner !== owner)
|
|
824
|
+
continue;
|
|
825
|
+
if ((await rpcKillProcess(self, live.pid)).ok)
|
|
826
|
+
killed += 1;
|
|
827
|
+
}
|
|
828
|
+
const durable = await rpcRemoveDurableApp(self, owner);
|
|
829
|
+
return { ...durable, removed: durable.removed || killed > 0 };
|
|
830
|
+
}
|
|
831
|
+
function describeTarget(target) {
|
|
832
|
+
if (typeof target === 'number')
|
|
833
|
+
return `target ${target}`;
|
|
834
|
+
if (typeof target === 'string')
|
|
835
|
+
return `'${target}'`;
|
|
836
|
+
if ('port' in target)
|
|
837
|
+
return `port ${target.port}`;
|
|
838
|
+
if ('pid' in target)
|
|
839
|
+
return `pid ${target.pid}`;
|
|
840
|
+
if ('name' in target)
|
|
841
|
+
return `app '${target.name}'`;
|
|
842
|
+
return `owner '${target.owner}'`;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Resolve an app target to what the verbs need: the owner (from the
|
|
846
|
+
* serving pid's journal row, or the reservation), the port, and the
|
|
847
|
+
* reservation if one exists. A bare number is a port when something
|
|
848
|
+
* listens on it or a reservation names it, else a pid; a string is a name
|
|
849
|
+
* first, then an owner.
|
|
850
|
+
*/
|
|
851
|
+
async function resolveAppTarget(self, target) {
|
|
852
|
+
const fm = self.facetManager;
|
|
853
|
+
const byPort = async (port) => {
|
|
854
|
+
const live = self.portRegistry.get(port);
|
|
855
|
+
const reservation = await readPortReservation(self.ctx, port);
|
|
856
|
+
const identity = live === undefined ? null : await fm.residentIdentity(live.pid);
|
|
857
|
+
if (identity?.ephemeral) {
|
|
858
|
+
throw new Error(`port ${port} is served by the second instance of its program, which is not the durable one`);
|
|
859
|
+
}
|
|
860
|
+
return { owner: identity?.owner ?? reservation?.owner ?? null, port, reservation };
|
|
861
|
+
};
|
|
862
|
+
const byPid = async (pid) => {
|
|
863
|
+
const identity = await fm.residentIdentity(pid);
|
|
864
|
+
if (identity === null)
|
|
865
|
+
return null;
|
|
866
|
+
if (identity.ephemeral) {
|
|
867
|
+
throw new Error(`pid ${pid} is the second instance of its program, which is not the durable one`);
|
|
868
|
+
}
|
|
869
|
+
const port = self.portRegistry.getAll().find((live) => live.pid === pid)?.port ?? identity.port ?? null;
|
|
870
|
+
const reservation = port === null ? null : await readPortReservation(self.ctx, port);
|
|
871
|
+
return { owner: identity.owner ?? null, port, reservation };
|
|
872
|
+
};
|
|
873
|
+
const byOwner = async (owner) => {
|
|
874
|
+
const held = await readPortReservationByOwner(self.ctx, owner);
|
|
875
|
+
if (held !== null)
|
|
876
|
+
return { owner, port: held.port, reservation: held.reservation };
|
|
877
|
+
const app = (await fm.listResidentApps()).find((candidate) => candidate.owner === owner);
|
|
878
|
+
return app === undefined ? null : { owner, port: app.port, reservation: null };
|
|
879
|
+
};
|
|
880
|
+
const byName = async (name) => {
|
|
881
|
+
const held = await readPortReservationByName(self.ctx, name);
|
|
882
|
+
return held === null ? null : { owner: held.reservation.owner, port: held.port, reservation: held.reservation };
|
|
883
|
+
};
|
|
884
|
+
if (typeof target === 'number') {
|
|
885
|
+
if (!Number.isInteger(target) || target <= 0)
|
|
886
|
+
throw new Error(`target ${target} is not a port or a pid`);
|
|
887
|
+
if (self.portRegistry.has(target) || (await readPortReservation(self.ctx, target)) !== null) {
|
|
888
|
+
return byPort(target);
|
|
889
|
+
}
|
|
890
|
+
const asPid = await byPid(target);
|
|
891
|
+
if (asPid !== null)
|
|
892
|
+
return asPid;
|
|
893
|
+
if (target <= 65535)
|
|
894
|
+
return byPort(target);
|
|
895
|
+
throw new Error(`nothing listens on port ${target} and no process ${target} exists`);
|
|
896
|
+
}
|
|
897
|
+
if (typeof target === 'string') {
|
|
898
|
+
const asName = await byName(target);
|
|
899
|
+
if (asName !== null)
|
|
900
|
+
return asName;
|
|
901
|
+
const asOwner = await byOwner(target);
|
|
902
|
+
if (asOwner !== null)
|
|
903
|
+
return asOwner;
|
|
904
|
+
throw new Error(`no application is named or owned '${target}'`);
|
|
905
|
+
}
|
|
906
|
+
if ('port' in target)
|
|
907
|
+
return byPort(Number(target.port));
|
|
908
|
+
if ('pid' in target) {
|
|
909
|
+
const asPid = await byPid(Number(target.pid));
|
|
910
|
+
if (asPid === null) {
|
|
911
|
+
throw new Error(`pid ${target.pid} is not running and has no launch record`);
|
|
912
|
+
}
|
|
913
|
+
return asPid;
|
|
914
|
+
}
|
|
915
|
+
if ('name' in target) {
|
|
916
|
+
const asName = await byName(target.name);
|
|
917
|
+
if (asName === null)
|
|
918
|
+
throw new Error(`no application is named '${target.name}'`);
|
|
919
|
+
return asName;
|
|
920
|
+
}
|
|
921
|
+
const asOwner = await byOwner(target.owner);
|
|
922
|
+
if (asOwner === null)
|
|
923
|
+
throw new Error(`no application is owned by '${target.owner}'`);
|
|
924
|
+
return asOwner;
|
|
925
|
+
}
|
|
566
926
|
/**
|
|
567
927
|
* The embedder's durable-application seam: reserve (or re-answer) the port
|
|
568
928
|
* `owner` holds, minting the capability the application's public URL is
|
|
@@ -573,32 +933,10 @@ export async function rpcExposePort(self, port, options) {
|
|
|
573
933
|
*/
|
|
574
934
|
export async function rpcEnsureDurableApp(self, input) {
|
|
575
935
|
await ensureProgrammaticReady(self);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const occupied = new Set(self.portRegistry.getAll().map((entry) => entry.port));
|
|
581
|
-
const port = await reservePort(self.ctx, {
|
|
582
|
-
owner,
|
|
583
|
-
preferredPort: input.preferredPort,
|
|
584
|
-
occupiedPorts: occupied,
|
|
585
|
-
// Minted here, not at boot: the URL is built from this capability, and
|
|
586
|
-
// re-drive re-adopts it out of the same row.
|
|
587
|
-
capability: createPortCapability(),
|
|
588
|
-
...(input.visibility !== undefined ? { visibility: input.visibility } : {}),
|
|
589
|
-
});
|
|
590
|
-
const record = await readPortReservation(self.ctx, port);
|
|
591
|
-
if (record?.visibility === 'public' && record.capability !== null) {
|
|
592
|
-
// The URL is built on this capability before the app has ever run:
|
|
593
|
-
// publish it to the directory now, loudly refusing a deployment whose
|
|
594
|
-
// binding is missing rather than minting an unroutable URL.
|
|
595
|
-
await bindPublicPortCapability(self, record.capability, port);
|
|
596
|
-
}
|
|
597
|
-
return {
|
|
598
|
-
port,
|
|
599
|
-
capability: record?.capability ?? null,
|
|
600
|
-
visibility: record?.visibility ?? 'scoped',
|
|
601
|
-
};
|
|
936
|
+
// The composed manager owns the durable-app verbs — one implementation,
|
|
937
|
+
// capability minted there so a URL handed out before boot is the one the
|
|
938
|
+
// eventual binding re-adopts.
|
|
939
|
+
return self.ensureFacetManager().apps.ensureDurableApp(input);
|
|
602
940
|
}
|
|
603
941
|
export async function rpcUnexposePort(self, port) {
|
|
604
942
|
await ensureProgrammaticReady(self);
|
|
@@ -621,26 +959,8 @@ export async function rpcUnexposePort(self, port) {
|
|
|
621
959
|
*/
|
|
622
960
|
export async function rpcRemoveDurableApp(self, owner) {
|
|
623
961
|
await ensureProgrammaticReady(self);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
// Which durable address is being released — the reservation's row names it
|
|
628
|
-
// before the manager retires it.
|
|
629
|
-
const rows = await self.ctx.storage.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
|
|
630
|
-
let port = null;
|
|
631
|
-
for (const [key, record] of rows) {
|
|
632
|
-
const parsed = PortRecordSchema.safeParse(record);
|
|
633
|
-
if (!parsed.success || parsed.data.owner !== owner)
|
|
634
|
-
continue;
|
|
635
|
-
const n = Number(key.slice(PORT_CAPABILITY_KEY_PREFIX.length));
|
|
636
|
-
if (Number.isInteger(n) && n > 0) {
|
|
637
|
-
port = n;
|
|
638
|
-
break;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
self.ensureFacetManager();
|
|
642
|
-
const removed = await self.facetManager.removeDurableApp(owner);
|
|
643
|
-
return { owner, removed, port };
|
|
962
|
+
// The composed manager owns the durable-app verbs — validation included.
|
|
963
|
+
return self.ensureFacetManager().apps.removeDurableApp(owner);
|
|
644
964
|
}
|
|
645
965
|
/**
|
|
646
966
|
* Route an embedder request that carries a port capability. The embedder has
|
|
@@ -658,10 +978,34 @@ export async function rpcRouteCapabilityPort(self, port, capability, request, pa
|
|
|
658
978
|
const routed = await self.portRegistry.routeCapabilityRequest(Number(port), String(capability), request, pathname);
|
|
659
979
|
return routed ?? new Response('Not found', { status: 404 });
|
|
660
980
|
}
|
|
661
|
-
|
|
981
|
+
/**
|
|
982
|
+
* `spawnWorker` for a colocated embedder holding the DO stub: boot the
|
|
983
|
+
* embedder's own Worker-class program — its main module, inline modules and
|
|
984
|
+
* content-addressed text/wasm modules — as one of this session's resident
|
|
985
|
+
* processes, and answer with the pid, the runner's boot payload and the
|
|
986
|
+
* process's facet (`fetch`/`connect`, bound to the resident handle; no
|
|
987
|
+
* release — `killProcess(pid)` ends it). Deliberately absent from the remote
|
|
988
|
+
* HTTP dispatcher: the facet is a live handle, and a remote token holder is
|
|
989
|
+
* not the embedder.
|
|
990
|
+
*/
|
|
991
|
+
export async function rpcSpawnWorker(self, workerCode, command, cwd, opts = {}) {
|
|
992
|
+
await ensureProgrammaticReady(self);
|
|
993
|
+
if (typeof workerCode !== 'string' || workerCode.length === 0) {
|
|
994
|
+
throw new Error('spawnWorker: workerCode must be a non-empty string');
|
|
995
|
+
}
|
|
996
|
+
self.ensureFacetManager();
|
|
997
|
+
return self.facetManager.spawnWorker(workerCode, String(command), String(cwd), opts);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* `files.delete`. Absent a `cred` this acts as CRED_KERNEL — what it has
|
|
1001
|
+
* always done, and the embedder's trusted surface: only a caller holding the
|
|
1002
|
+
* DO binding reaches it. A `cred` binds the removal to that identity instead,
|
|
1003
|
+
* the same view `SqliteVFS.as(cred)` gives in-process.
|
|
1004
|
+
*/
|
|
1005
|
+
export async function rpcDeleteFile(self, path, options = {}, cred) {
|
|
662
1006
|
await ensureProgrammaticReady(self);
|
|
663
1007
|
const p = String(path).replace(/^\/+/, '');
|
|
664
|
-
const vfs = self.sqliteFs.as(CRED_KERNEL);
|
|
1008
|
+
const vfs = self.sqliteFs.as(cred === undefined ? CRED_KERNEL : requireVfsCred(cred, 'files.delete'));
|
|
665
1009
|
if (!vfs.exists(p))
|
|
666
1010
|
return;
|
|
667
1011
|
if (vfs.isDirectory(p)) {
|
|
@@ -887,17 +1231,23 @@ function resetInMemorySessionState(self) {
|
|
|
887
1231
|
self.shell = null;
|
|
888
1232
|
self.shellProcessPid = null;
|
|
889
1233
|
self.terminal = null;
|
|
890
|
-
self.
|
|
1234
|
+
self.facetManagerComposed = null;
|
|
891
1235
|
self.facetProcessManager = null;
|
|
892
1236
|
self.esbuildService = null;
|
|
893
1237
|
self.viteDevServer = null;
|
|
1238
|
+
// The installer's pre-bundler and the dev server's on-demand path share
|
|
1239
|
+
// this pool; it goes when both of them go.
|
|
1240
|
+
try {
|
|
1241
|
+
self.bundlePool?.dispose();
|
|
1242
|
+
}
|
|
1243
|
+
catch { }
|
|
1244
|
+
self.bundlePool = null;
|
|
894
1245
|
self.cirrusReal = null;
|
|
895
1246
|
self._cirrusHmrWsClients = null;
|
|
896
1247
|
self.nimbusWrangler = null;
|
|
897
1248
|
self.npmInstaller = null;
|
|
898
1249
|
self.fetchProxyEntrypoint = null;
|
|
899
|
-
self.
|
|
900
|
-
self.runtimeFsBridges = null;
|
|
1250
|
+
self._supervisorOps = null;
|
|
901
1251
|
self._cpRegistry = null;
|
|
902
1252
|
self._viteShimPid = null;
|
|
903
1253
|
self._viteShimPort = null;
|