@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
package/dist/session/init.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { Shell, createCurlCommand, createNpmCommand, NPM_VERSION, createTopCommand, createWatchCommand, createHelpCommand, rehydrateGlobalPackages, } from '@nimbus-sh/core/substrate/lifo/index.js';
|
|
37
37
|
import { createKillCommand } from '@nimbus-sh/core/substrate/lifo/commands/system/kill.js';
|
|
38
|
+
import { textSink } from '@nimbus-sh/core/_shared/bytes.js';
|
|
38
39
|
import { NimbusWorkspace } from '@nimbus-sh/core/workspace';
|
|
39
40
|
import { CRED_KERNEL, requireVfsCred } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
40
41
|
import { WebSocketTerminal } from '../facets/ws-terminal.js';
|
|
@@ -42,50 +43,41 @@ import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
|
42
43
|
import { runFresh } from '../runtime/node-runner.js';
|
|
43
44
|
import { runBunScript, BUN_VERSION } from '../runtime/bun-runner.js';
|
|
44
45
|
import { buildRuntimeHandler, resolveRuntimeScriptPath, } from '@nimbus-sh/core/runtime/runtime-registry.js';
|
|
45
|
-
import { parseViteConfigSource } from '@nimbus-sh/core/runtime/vite-config-parser.js';
|
|
46
|
-
import { startRealVite } from './start-real-vite.js';
|
|
47
|
-
import { findHtmlScriptEntrypoint, rewriteViteBuildHtml } from '../runtime/html-entrypoint.js';
|
|
48
46
|
import { normalizeVfsPath, parentVfsPath, resolveVfsPath, stripLeadingSlashes } from '@nimbus-sh/core/vfs/path.js';
|
|
49
|
-
import {
|
|
50
|
-
import { shouldUseRealVite } from '../facets/cirrus-real.js';
|
|
51
|
-
import { makeLongRunningPortStub, resolveLongRunningPort, expandArgvShellDefaults, } from '@nimbus-sh/core/runtime/long-running-handle.js';
|
|
47
|
+
import { errorText } from '@nimbus-sh/core/_shared/error-text.js';
|
|
52
48
|
import { NimbusWrangler } from '../wrangler/nimbus-wrangler.js';
|
|
53
|
-
import { filterWranglerFlags, detectBundlerBin, checkNodeModulesGuard, detectUnsupportedWranglerConfig, } from './helpers.js';
|
|
49
|
+
import { filterWranglerFlags, detectBundlerBin, checkNodeModulesGuard, detectUnsupportedWranglerConfig, refusedNextSubcommand, NEXT_REFUSAL_MESSAGE, } from './helpers.js';
|
|
50
|
+
import { createViteCommand } from './vite-command.js';
|
|
54
51
|
import { HeredocHandler, LineEditorExtender } from '@nimbus-sh/core/shell/features.js';
|
|
55
52
|
import { registerShellEntrypointCommands } from '@nimbus-sh/core/shell/shell-entrypoints.js';
|
|
56
53
|
import { makeChshCommand } from '@nimbus-sh/core/substrate/lifo/shell/default-shell.js';
|
|
57
54
|
import { installNpmBinFallbackResolver } from '../shell/npm-bin-entrypoints.js';
|
|
58
|
-
import {
|
|
59
|
-
import { npmLogEnabled } from '../npm/npm-log.js';
|
|
60
|
-
import { materializeNpmBinShims } from '../npm/bin-links.js';
|
|
55
|
+
import { createNpmInstallPort } from './npm-install-port.js';
|
|
61
56
|
import { makeNimbusVerbHandler, createRuntimeCommandHintResolver, } from '../runtime/package-manager.js';
|
|
57
|
+
import { rpcExposeApp, rpcListApps, rpcRemoveApp, rpcRotateLink } from './programmatic.js';
|
|
62
58
|
import { listInstalledRuntimes, rehydrateInstalledRuntimes, registerRunnerFactory, } from '@nimbus-sh/core/runtime/installed-runtimes.js';
|
|
63
59
|
// Runtime factories (clang/python/ruby/bash/wasm) are imported lazily at
|
|
64
60
|
// first-use inside their registered handlers — see the registrations below.
|
|
65
61
|
// Keeping them off the top-level import graph shaves their module-eval cost
|
|
66
62
|
// (zod, embedded socket-kernel/shim sources, wasm loaders) out of the
|
|
67
63
|
// one-time Worker Startup Time paid on every fresh-isolate cold run.
|
|
68
|
-
import { hasSeededProject, SEED_PROJECT_DIR } from '@nimbus-sh/core/vfs/seed-project.js';
|
|
64
|
+
import { hasSeededProject, SEED_PROJECT_DIR, SEED_PROJECT_NAME, SEED_PROJECT_TILDE } from '@nimbus-sh/core/vfs/seed-project.js';
|
|
69
65
|
import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
|
|
70
66
|
import { generation } from '@nimbus-sh/fabric/generation.js';
|
|
71
67
|
import { stripAnsi } from '@nimbus-sh/core/runtime/process-logs.js';
|
|
72
68
|
import { DEFAULT_MOUNT_POINTS, NODE_VERSION, } from '@nimbus-sh/core/constants.js';
|
|
73
|
-
import { ensureSessionStateSchema, loadShellState, stampHydratedAt, countSessionStateKeys, loadKernelMounts, persistKernelMounts,
|
|
69
|
+
import { ensureSessionStateSchema, loadShellState, stampHydratedAt, countSessionStateKeys, loadKernelMounts, persistKernelMounts, loadScrollback, } from './state-store.js';
|
|
74
70
|
import { recordRecoveryEvent } from '@nimbus-sh/platform/oom-discriminator.js';
|
|
75
71
|
import { sessionAiEnv } from './ai.js';
|
|
76
72
|
import { routeSessionLoopback } from './loopback.js';
|
|
77
73
|
import { setPhase } from './init-phases.js';
|
|
74
|
+
import { shellTerminalTee } from './ws.js';
|
|
78
75
|
import { VITE_CONFIG_KEY } from './keys.js';
|
|
79
|
-
import { clearPortCapability } from './port-capability.js';
|
|
80
|
-
function resolveNpmPrefix(prefix, cwd) {
|
|
81
|
-
return prefix.startsWith('/')
|
|
82
|
-
? normalizeVfsPath(prefix)
|
|
83
|
-
: resolveVfsPath(prefix, cwd || '/home/user');
|
|
84
|
-
}
|
|
85
76
|
function quoteShellArgument(value) {
|
|
86
77
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
87
78
|
}
|
|
88
|
-
export async function initSession(self, ws) {
|
|
79
|
+
export async function initSession(self, ws, options = {}) {
|
|
80
|
+
const replayScrollback = options.resume !== 'wake';
|
|
89
81
|
self.ensureSqliteFs();
|
|
90
82
|
const kernelFs = self.sqliteFs.as(CRED_KERNEL);
|
|
91
83
|
self.ensureFacetManager();
|
|
@@ -114,17 +106,7 @@ export async function initSession(self, ws) {
|
|
|
114
106
|
// facet and the scrollback replay below is wire-phase work.
|
|
115
107
|
// Phase B will tag in once we start building the kernel.
|
|
116
108
|
setPhase(self, 'wire', 'init-session');
|
|
117
|
-
self.terminal = new WebSocketTerminal(ws, (
|
|
118
|
-
try {
|
|
119
|
-
appendScrollback(self.ctx, frame, Date.now());
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
122
|
-
try {
|
|
123
|
-
console.warn('[B\'.3] appendScrollback failed:', e?.message || e);
|
|
124
|
-
}
|
|
125
|
-
catch { }
|
|
126
|
-
}
|
|
127
|
-
});
|
|
109
|
+
self.terminal = new WebSocketTerminal(ws, shellTerminalTee(self));
|
|
128
110
|
// [B'.3] Replay persisted scrollback BEFORE the cold-start UI gate.
|
|
129
111
|
// On rehydrate (hasPersistedState=true) we emit the prior
|
|
130
112
|
// session's terminal contents as a single batched write — the
|
|
@@ -138,16 +120,18 @@ export async function initSession(self, ws) {
|
|
|
138
120
|
// same scrollback both times. The cap eviction keeps total bytes
|
|
139
121
|
// bounded.
|
|
140
122
|
if (persisted.hasPersistedState) {
|
|
141
|
-
|
|
142
|
-
const replay = loadScrollback(self.ctx);
|
|
143
|
-
if (replay.length > 0)
|
|
144
|
-
self.terminal.write(replay);
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
123
|
+
if (replayScrollback) {
|
|
147
124
|
try {
|
|
148
|
-
|
|
125
|
+
const replay = loadScrollback(self.ctx);
|
|
126
|
+
if (replay.length > 0)
|
|
127
|
+
self.terminal.write(replay);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
try {
|
|
131
|
+
console.warn('[B\'.3] scrollback replay failed:', e?.message || e);
|
|
132
|
+
}
|
|
133
|
+
catch { }
|
|
149
134
|
}
|
|
150
|
-
catch { }
|
|
151
135
|
}
|
|
152
136
|
// The scrollback stops mid-command when the previous instance was
|
|
153
137
|
// reset under it — the platform kills the isolate without running a
|
|
@@ -262,8 +246,8 @@ export async function initSession(self, ws) {
|
|
|
262
246
|
stdout: parent.isFdTerminal?.(1) ?? false,
|
|
263
247
|
stderr: parent.isFdTerminal?.(2) ?? false,
|
|
264
248
|
},
|
|
265
|
-
onStdout: (data) => parent.stdout.write(data),
|
|
266
|
-
onStderr: (data) => parent.stderr.write(data),
|
|
249
|
+
onStdout: textSink((data) => parent.stdout.write(data)),
|
|
250
|
+
onStderr: textSink((data) => parent.stderr.write(data)),
|
|
267
251
|
commandContext: {
|
|
268
252
|
pid: identity.pid,
|
|
269
253
|
cred: identity.cred,
|
|
@@ -589,6 +573,14 @@ export async function initSession(self, ws) {
|
|
|
589
573
|
vfs: sqliteFs,
|
|
590
574
|
registry: pkgRegistry,
|
|
591
575
|
getHome: nimbusGetHome,
|
|
576
|
+
// The application verbs are the session's own (programmatic.ts):
|
|
577
|
+
// the shell, the SDK and the Agent all reach the same policy.
|
|
578
|
+
apps: {
|
|
579
|
+
expose: (target, options) => rpcExposeApp(self, target, options),
|
|
580
|
+
list: () => rpcListApps(self),
|
|
581
|
+
rotateLink: (target) => rpcRotateLink(self, target),
|
|
582
|
+
remove: (target) => rpcRemoveApp(self, target),
|
|
583
|
+
},
|
|
592
584
|
warmRuntime: async (target, ctx) => {
|
|
593
585
|
// The runtime name, which is what `nimbus install python` installs
|
|
594
586
|
// — not the name the user typed. Left as 'python' through the
|
|
@@ -813,8 +805,8 @@ export async function initSession(self, ws) {
|
|
|
813
805
|
const shellResult = await shell.execute(command, {
|
|
814
806
|
cwd: ctx.cwd,
|
|
815
807
|
env: ctx.env,
|
|
816
|
-
onStdout: (d) => ctx.stdout.write(d),
|
|
817
|
-
onStderr: (d) => ctx.stderr.write(d),
|
|
808
|
+
onStdout: textSink((d) => ctx.stdout.write(d)),
|
|
809
|
+
onStderr: textSink((d) => ctx.stderr.write(d)),
|
|
818
810
|
});
|
|
819
811
|
return shellResult.exitCode;
|
|
820
812
|
}
|
|
@@ -1070,443 +1062,7 @@ export async function initSession(self, ws) {
|
|
|
1070
1062
|
}
|
|
1071
1063
|
});
|
|
1072
1064
|
// ── vite command: start/stop the dev server ──────────────────────────
|
|
1073
|
-
registry.register('vite',
|
|
1074
|
-
const args = ctx.args || [];
|
|
1075
|
-
const cwd = normalizeVfsPath(ctx.cwd || '/home/user');
|
|
1076
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
1077
|
-
ctx.stdout.write('Usage: vite [command] [options]\n\n');
|
|
1078
|
-
ctx.stdout.write('Commands:\n');
|
|
1079
|
-
ctx.stdout.write(' (default) Start dev server\n');
|
|
1080
|
-
ctx.stdout.write(' build Build for production\n');
|
|
1081
|
-
ctx.stdout.write(' preview Serve the built dist/\n');
|
|
1082
|
-
ctx.stdout.write(' stop Stop dev server\n\n');
|
|
1083
|
-
ctx.stdout.write('Options:\n');
|
|
1084
|
-
ctx.stdout.write(' --root <dir> Project root\n');
|
|
1085
|
-
ctx.stdout.write(' --port <n> Server port\n');
|
|
1086
|
-
return 0;
|
|
1087
|
-
}
|
|
1088
|
-
self.ensureSqliteFs();
|
|
1089
|
-
const viteConfig = {};
|
|
1090
|
-
for (const cfgName of ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']) {
|
|
1091
|
-
const cfgPath = cwd + '/' + cfgName;
|
|
1092
|
-
if (kernelFs.exists(cfgPath)) {
|
|
1093
|
-
try {
|
|
1094
|
-
let cfgCode = kernelFs.readFileString(cfgPath);
|
|
1095
|
-
// Transform TS to JS
|
|
1096
|
-
if (cfgName.endsWith('.ts')) {
|
|
1097
|
-
if (!self.esbuildService)
|
|
1098
|
-
self.esbuildService = new EsbuildService(kernelFs);
|
|
1099
|
-
const t = await self.esbuildService.transform(cfgCode, { loader: 'ts', format: 'esm' });
|
|
1100
|
-
cfgCode = t.code;
|
|
1101
|
-
}
|
|
1102
|
-
Object.assign(viteConfig, parseViteConfigSource(cfgCode));
|
|
1103
|
-
}
|
|
1104
|
-
catch (e) {
|
|
1105
|
-
ctx.stderr.write(`Warning: could not parse ${cfgName}: ${e?.message}\n`);
|
|
1106
|
-
}
|
|
1107
|
-
break;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
// ── vite build ──
|
|
1111
|
-
if (args[0] === 'build') {
|
|
1112
|
-
if (!self.esbuildService)
|
|
1113
|
-
self.esbuildService = new EsbuildService(kernelFs);
|
|
1114
|
-
const htmlPath = cwd + '/index.html';
|
|
1115
|
-
let entryPoint = cwd + '/src/main.tsx';
|
|
1116
|
-
let origHtml = '';
|
|
1117
|
-
try {
|
|
1118
|
-
origHtml = kernelFs.readFileString(htmlPath);
|
|
1119
|
-
const htmlEntrypoint = await findHtmlScriptEntrypoint(origHtml);
|
|
1120
|
-
if (htmlEntrypoint)
|
|
1121
|
-
entryPoint = cwd + '/' + stripLeadingSlashes(htmlEntrypoint);
|
|
1122
|
-
}
|
|
1123
|
-
catch {
|
|
1124
|
-
ctx.stderr.write('Warning: no index.html\n');
|
|
1125
|
-
}
|
|
1126
|
-
if (!kernelFs.exists(entryPoint)) {
|
|
1127
|
-
const alts = [cwd + '/src/main.tsx', cwd + '/src/main.ts', cwd + '/src/index.tsx', cwd + '/src/index.ts'];
|
|
1128
|
-
entryPoint = alts.find(p => kernelFs.exists(p)) || entryPoint;
|
|
1129
|
-
}
|
|
1130
|
-
ctx.stdout.write('Building for production...\n');
|
|
1131
|
-
ctx.stdout.write(' Entry: ' + entryPoint + '\n');
|
|
1132
|
-
const t0 = Date.now();
|
|
1133
|
-
try {
|
|
1134
|
-
const outDir = viteConfig.outDir || 'dist';
|
|
1135
|
-
const distDir = cwd + '/' + outDir;
|
|
1136
|
-
// Detect which packages are installed vs need CDN
|
|
1137
|
-
const nmDir = cwd + '/node_modules';
|
|
1138
|
-
const externals = [];
|
|
1139
|
-
const cdnPackages = [];
|
|
1140
|
-
for (const pkg of ['react', 'react-dom', 'react/jsx-runtime', 'react-dom/client']) {
|
|
1141
|
-
const pkgBase = pkg.split('/')[0];
|
|
1142
|
-
if (!kernelFs.exists(nmDir + '/' + pkgBase)) {
|
|
1143
|
-
externals.push(pkg);
|
|
1144
|
-
if (!cdnPackages.includes(pkgBase))
|
|
1145
|
-
cdnPackages.push(pkgBase);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
if (viteConfig.alias)
|
|
1149
|
-
externals.push(...Object.keys(viteConfig.alias));
|
|
1150
|
-
// Bundle JS
|
|
1151
|
-
const result = await self.esbuildService.build([entryPoint], {
|
|
1152
|
-
bundle: true, format: 'esm', target: 'es2020', platform: 'browser',
|
|
1153
|
-
minify: true, outdir: '/' + distDir + '/assets',
|
|
1154
|
-
external: externals.length > 0 ? externals : undefined,
|
|
1155
|
-
});
|
|
1156
|
-
if (result.errors?.length) {
|
|
1157
|
-
for (const e of result.errors)
|
|
1158
|
-
ctx.stderr.write(' error: ' + e.text + '\n');
|
|
1159
|
-
return 1;
|
|
1160
|
-
}
|
|
1161
|
-
// Generate content hash for filenames
|
|
1162
|
-
let jsContent = '';
|
|
1163
|
-
for (const f of result.outputFiles || []) {
|
|
1164
|
-
jsContent = f.contents;
|
|
1165
|
-
}
|
|
1166
|
-
const hashNum = jsContent.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
|
|
1167
|
-
const hash = (hashNum >>> 0).toString(36).padStart(6, '0');
|
|
1168
|
-
// Write JS with hashed filename
|
|
1169
|
-
const jsFilename = 'index-' + hash + '.js';
|
|
1170
|
-
const jsPath = distDir + '/assets/' + jsFilename;
|
|
1171
|
-
kernelFs.mkdir(distDir + '/assets', { recursive: true });
|
|
1172
|
-
kernelFs.writeFile(jsPath, jsContent);
|
|
1173
|
-
ctx.stdout.write(' \x1b[2m' + outDir + '/assets/' + jsFilename + '\x1b[0m ' + (jsContent.length / 1024).toFixed(2) + ' kB\n');
|
|
1174
|
-
// Collect all CSS files from src/
|
|
1175
|
-
let allCss = '';
|
|
1176
|
-
const collectCss = (dir) => {
|
|
1177
|
-
try {
|
|
1178
|
-
for (const e of kernelFs.readdir(dir)) {
|
|
1179
|
-
const fp = dir + '/' + e.name;
|
|
1180
|
-
if (e.type === 'directory')
|
|
1181
|
-
collectCss(fp);
|
|
1182
|
-
else if (e.name.endsWith('.css')) {
|
|
1183
|
-
try {
|
|
1184
|
-
allCss += kernelFs.readFileString(fp) + '\n';
|
|
1185
|
-
}
|
|
1186
|
-
catch { }
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
catch { }
|
|
1191
|
-
};
|
|
1192
|
-
collectCss(cwd + '/src');
|
|
1193
|
-
const cssFilename = 'index-' + hash + '.css';
|
|
1194
|
-
if (allCss.trim()) {
|
|
1195
|
-
kernelFs.writeFile(distDir + '/assets/' + cssFilename, allCss);
|
|
1196
|
-
ctx.stdout.write(' \x1b[2m' + outDir + '/assets/' + cssFilename + '\x1b[0m ' + (allCss.length / 1024).toFixed(2) + ' kB\n');
|
|
1197
|
-
}
|
|
1198
|
-
// Generate dist/index.html
|
|
1199
|
-
if (origHtml) {
|
|
1200
|
-
const distHtml = await rewriteViteBuildHtml(origHtml, {
|
|
1201
|
-
jsFilename,
|
|
1202
|
-
cssFilename: allCss.trim() ? cssFilename : undefined,
|
|
1203
|
-
removeImportMap: cdnPackages.length === 0,
|
|
1204
|
-
});
|
|
1205
|
-
kernelFs.writeFile(distDir + '/index.html', distHtml);
|
|
1206
|
-
ctx.stdout.write(' \x1b[2m' + outDir + '/index.html\x1b[0m ' + (distHtml.length / 1024).toFixed(2) + ' kB\n');
|
|
1207
|
-
if (cdnPackages.length > 0) {
|
|
1208
|
-
ctx.stdout.write(' \x1b[33mNote: ' + cdnPackages.join(', ') + ' loaded from CDN (not bundled)\x1b[0m\n');
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
ctx.stdout.write('\n\x1b[32m\u2713 built in ' + ((Date.now() - t0) / 1000).toFixed(2) + 's\x1b[0m\n');
|
|
1212
|
-
return 0;
|
|
1213
|
-
}
|
|
1214
|
-
catch (e) {
|
|
1215
|
-
ctx.stderr.write('Build error: ' + (e?.message || e) + '\n');
|
|
1216
|
-
return 1;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
// ── vite preview ──
|
|
1220
|
-
if (args[0] === 'preview') {
|
|
1221
|
-
ctx.stdout.write('Serving dist/ — open ' + self.viteBasePath + '/\n');
|
|
1222
|
-
const distRoot = cwd + '/' + (viteConfig.outDir || 'dist');
|
|
1223
|
-
if (!kernelFs.exists(distRoot)) {
|
|
1224
|
-
ctx.stderr.write('dist/ not found. Run vite build first.\n');
|
|
1225
|
-
return 1;
|
|
1226
|
-
}
|
|
1227
|
-
// Start vite on the dist directory
|
|
1228
|
-
if (!self.esbuildService)
|
|
1229
|
-
self.esbuildService = new EsbuildService(kernelFs);
|
|
1230
|
-
if (self.viteDevServer?.isRunning)
|
|
1231
|
-
self.viteDevServer.stop();
|
|
1232
|
-
const previewBasePath = self.viteBasePath;
|
|
1233
|
-
// process metadata support: same long-running treatment as the
|
|
1234
|
-
// dev path, just on the dist/ directory.
|
|
1235
|
-
const previewPort = viteConfig.port || 4173; // vite preview default
|
|
1236
|
-
const previewProcEntry = self.processes.spawn('vite preview (' + distRoot + ')', [], distRoot, { longRunning: true });
|
|
1237
|
-
self.viteDevServer = new ViteDevServer({
|
|
1238
|
-
vfs: self.sqliteFs, esbuild: self.esbuildService, root: distRoot,
|
|
1239
|
-
onHmrMessage: () => { },
|
|
1240
|
-
sql: self.ctx.storage.sql,
|
|
1241
|
-
basePath: previewBasePath,
|
|
1242
|
-
env: self.env,
|
|
1243
|
-
ctx: self.ctx,
|
|
1244
|
-
port: previewPort,
|
|
1245
|
-
pid: previewProcEntry.pid,
|
|
1246
|
-
processes: self.processes,
|
|
1247
|
-
});
|
|
1248
|
-
self.viteDevServer.start();
|
|
1249
|
-
try {
|
|
1250
|
-
const previewStub = makeLongRunningPortStub(self.viteDevServer);
|
|
1251
|
-
self.portRegistry.bindFacetStub(previewProcEntry.pid, previewStub);
|
|
1252
|
-
await clearPortCapability(self, previewPort);
|
|
1253
|
-
self.portRegistry.register(previewPort, previewProcEntry.pid);
|
|
1254
|
-
self._viteShimPid = previewProcEntry.pid;
|
|
1255
|
-
self._viteShimPort = previewPort;
|
|
1256
|
-
}
|
|
1257
|
-
catch { }
|
|
1258
|
-
try {
|
|
1259
|
-
await self.ctx.storage.put(VITE_CONFIG_KEY, { root: distRoot, basePath: previewBasePath, port: previewPort });
|
|
1260
|
-
}
|
|
1261
|
-
catch { }
|
|
1262
|
-
ctx.stdout.write('Serving at ' + previewBasePath + '/ \x1b[2m(pid=' + previewProcEntry.pid + ', port=' + previewPort + ')\x1b[0m\n');
|
|
1263
|
-
return 0;
|
|
1264
|
-
}
|
|
1265
|
-
// ── vite stop ──
|
|
1266
|
-
if (args[0] === 'stop') {
|
|
1267
|
-
let stopped = false;
|
|
1268
|
-
if (self.cirrusReal?.isRunning) {
|
|
1269
|
-
self.cirrusReal.stop(self.ctx);
|
|
1270
|
-
self.cirrusReal = null;
|
|
1271
|
-
stopped = true;
|
|
1272
|
-
}
|
|
1273
|
-
if (self.viteDevServer?.isRunning) {
|
|
1274
|
-
self.viteDevServer.stop();
|
|
1275
|
-
self.viteDevServer = null;
|
|
1276
|
-
try {
|
|
1277
|
-
await self.ctx.storage.delete(VITE_CONFIG_KEY);
|
|
1278
|
-
}
|
|
1279
|
-
catch { }
|
|
1280
|
-
stopped = true;
|
|
1281
|
-
}
|
|
1282
|
-
// Primitive #3 teardown — symmetric with the start path. Always
|
|
1283
|
-
// safe to call: unregisterByPid is idempotent, exit() guards
|
|
1284
|
-
// against re-marking already-terminal entries.
|
|
1285
|
-
if (self._viteShimPid != null) {
|
|
1286
|
-
try {
|
|
1287
|
-
self.portRegistry.unregisterByPid(self._viteShimPid);
|
|
1288
|
-
}
|
|
1289
|
-
catch { }
|
|
1290
|
-
try {
|
|
1291
|
-
self.processes.exit(self._viteShimPid, 0);
|
|
1292
|
-
}
|
|
1293
|
-
catch { }
|
|
1294
|
-
notifyTerminalEvent(self.terminal, {
|
|
1295
|
-
type: 'exit', pid: self._viteShimPid, code: 0, command: 'vite',
|
|
1296
|
-
});
|
|
1297
|
-
self._viteShimPid = null;
|
|
1298
|
-
self._viteShimPort = null;
|
|
1299
|
-
}
|
|
1300
|
-
if (stopped) {
|
|
1301
|
-
ctx.stdout.write('\x1b[33mDev server stopped.\x1b[0m\n');
|
|
1302
|
-
}
|
|
1303
|
-
else {
|
|
1304
|
-
ctx.stdout.write('No dev server running.\n');
|
|
1305
|
-
}
|
|
1306
|
-
return 0;
|
|
1307
|
-
}
|
|
1308
|
-
// ── vite (default: dev server) ──
|
|
1309
|
-
let vfsRoot = cwd;
|
|
1310
|
-
for (let i = 0; i < args.length; i++) {
|
|
1311
|
-
if (args[i] === '--root' && args[i + 1])
|
|
1312
|
-
vfsRoot = resolveVfsPath(args[i + 1], cwd);
|
|
1313
|
-
}
|
|
1314
|
-
if (viteConfig.root && viteConfig.root !== '.') {
|
|
1315
|
-
vfsRoot = resolveVfsPath(viteConfig.root, cwd);
|
|
1316
|
-
}
|
|
1317
|
-
vfsRoot = normalizeVfsPath(vfsRoot);
|
|
1318
|
-
// Argv expansion: package.json scripts commonly write
|
|
1319
|
-
// `--port ${PORT:-3000}`. Resolve it once and feed both Vite
|
|
1320
|
-
// backends so `/api/stats`, `/preview/`, and port tabs agree.
|
|
1321
|
-
const expandedArgs = expandArgvShellDefaults(args, ctx.env || {});
|
|
1322
|
-
const vitePortDefault = 5173;
|
|
1323
|
-
const resolvedPort = resolveLongRunningPort({
|
|
1324
|
-
argv: expandedArgs,
|
|
1325
|
-
env: ctx.env,
|
|
1326
|
-
configPort: viteConfig.port,
|
|
1327
|
-
fallback: vitePortDefault,
|
|
1328
|
-
});
|
|
1329
|
-
// ── Preflight: node_modules guard ────────────────────────────────────
|
|
1330
|
-
// Direct `vite` invocation requires installed deps. Bail loudly BEFORE
|
|
1331
|
-
// spawning a dev server that would just serve broken modules and
|
|
1332
|
-
// confuse the user. --force / --no-install-check bypasses the check.
|
|
1333
|
-
const bypassInstallCheck = expandedArgs.includes('--force') || expandedArgs.includes('--no-install-check');
|
|
1334
|
-
if (!bypassInstallCheck) {
|
|
1335
|
-
const guard = checkNodeModulesGuard(kernelFs, vfsRoot);
|
|
1336
|
-
if (guard.missing) {
|
|
1337
|
-
ctx.stderr.write('\x1b[31m\u2718\x1b[0m \x1b[1mnode_modules/ not found\x1b[0m' +
|
|
1338
|
-
(guard.depCount > 0 ? ` (${guard.depCount} dependencies declared)` : '') + '\n' +
|
|
1339
|
-
' Run \x1b[36mnpm install\x1b[0m in ' + vfsRoot + ' first,\n' +
|
|
1340
|
-
' or re-run with \x1b[36m--force\x1b[0m to skip this check.\n');
|
|
1341
|
-
return 1;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
if (self.viteDevServer?.isRunning)
|
|
1345
|
-
self.viteDevServer.stop();
|
|
1346
|
-
// ── Real-vite mode (Phase 0 spike, opt-in) ─────────────────────────
|
|
1347
|
-
// NIMBUS_REAL_VITE=1 or `nimbusDevServer: 'real'` in vite.config.ts
|
|
1348
|
-
// routes the session through a dynamic-worker facet running the
|
|
1349
|
-
// real `vite` npm package. The in-process Cirrus shim is bypassed.
|
|
1350
|
-
//
|
|
1351
|
-
// This is EXPERIMENTAL and gated behind an explicit opt-in. Any
|
|
1352
|
-
// error here falls back to Cirrus by the user re-running without
|
|
1353
|
-
// the env flag — we do not silently fall back (fidelity over
|
|
1354
|
-
// magic).
|
|
1355
|
-
const sessionEnv = (ctx && ctx.env) || {};
|
|
1356
|
-
const useReal = shouldUseRealVite({ env: sessionEnv, viteConfig });
|
|
1357
|
-
if (useReal) {
|
|
1358
|
-
const vitePort = resolvedPort;
|
|
1359
|
-
const previewBasePath = self.viteBasePath;
|
|
1360
|
-
// One boot path, shared with hibernation-restore (start-real-vite.ts):
|
|
1361
|
-
// pre-bundles the user's vite.config, boots the facet, registers the
|
|
1362
|
-
// port, and persists the vite-config so a woken session rebuilds the
|
|
1363
|
-
// same real-vite server. Only the banner below is command-specific.
|
|
1364
|
-
const { cirrusReal, userConfigBundle, cfgPath } = await startRealVite(self, {
|
|
1365
|
-
root: vfsRoot,
|
|
1366
|
-
port: vitePort,
|
|
1367
|
-
basePath: previewBasePath,
|
|
1368
|
-
configDir: cwd,
|
|
1369
|
-
signal: ctx.signal,
|
|
1370
|
-
onConfigError: (msg) => {
|
|
1371
|
-
ctx.stderr.write('\x1b[33m!\x1b[0m vite.config bundling failed: ' + msg + '\n');
|
|
1372
|
-
ctx.stderr.write(' Real-vite will run with default config.\n');
|
|
1373
|
-
},
|
|
1374
|
-
});
|
|
1375
|
-
{
|
|
1376
|
-
// ── Boot banner (§4.3 of PHASE2-REAL-VITE-PLAN.md) ──────
|
|
1377
|
-
const snap = cirrusReal.stats.snapshot;
|
|
1378
|
-
ctx.stdout.write('\n\x1b[1;36m Nimbus: real-vite mode\x1b[0m \x1b[2m(experimental, Phase 1-4)\x1b[0m\n\n');
|
|
1379
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Preview: \x1b[36m' + previewBasePath + '/\x1b[0m\n');
|
|
1380
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Vite: ' + cirrusReal.stats.viteVersion + ' (bundled)\n');
|
|
1381
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Root: ' + vfsRoot + '\n');
|
|
1382
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Port: ' + vitePort + ' \x1b[2m(virtual routing key)\x1b[0m\n');
|
|
1383
|
-
if (snap) {
|
|
1384
|
-
const kb = (snap.totalBytes / 1024).toFixed(1);
|
|
1385
|
-
const pkgJson = snap.packageJsonCount;
|
|
1386
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Snapshot: ' + snap.fileCount + ' files / ' +
|
|
1387
|
-
kb + ' KB ' +
|
|
1388
|
-
(pkgJson ? '\x1b[2m(incl. ' + pkgJson + ' package.json, rest lazy)\x1b[0m' : '') + '\n');
|
|
1389
|
-
}
|
|
1390
|
-
if (userConfigBundle) {
|
|
1391
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Config: ' + cfgPath + ' \x1b[2m(' +
|
|
1392
|
-
(userConfigBundle.length / 1024).toFixed(0) + ' KB bundled)\x1b[0m\n');
|
|
1393
|
-
}
|
|
1394
|
-
ctx.stdout.write('\n \x1b[2mWorks:\x1b[0m @vitejs/plugin-react, JSX/TSX transforms, SPA fallback, HMR.\n');
|
|
1395
|
-
ctx.stdout.write(' \x1b[2mPartial:\x1b[0m other plugins (Babel-family generally OK; SWC/Rolldown blocked).\n');
|
|
1396
|
-
ctx.stdout.write(' \x1b[2mBlocked:\x1b[0m vite build (rolldown needs node:wasi). Use cirrus for build.\n');
|
|
1397
|
-
ctx.stdout.write('\n \x1b[2mRun \x1b[0mvite stop\x1b[2m, or \x1b[0mNIMBUS_REAL_VITE=0 vite\x1b[2m for Cirrus.\x1b[0m\n\n');
|
|
1398
|
-
return 0;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
if (!self.esbuildService)
|
|
1402
|
-
self.esbuildService = new EsbuildService(kernelFs);
|
|
1403
|
-
const previewBasePath = self.viteBasePath;
|
|
1404
|
-
const viteDefine = viteConfig.define;
|
|
1405
|
-
// Vite dev servers are represented as long-running process-table
|
|
1406
|
-
// entries and port-registry handlers, so `ps`, logs, preview routing,
|
|
1407
|
-
// `vite stop`, and `kill <pid>` share the same lifecycle primitives.
|
|
1408
|
-
// Allocate PID FIRST so we can plumb it into ViteDevServer's
|
|
1409
|
-
// process-log wiring at construction time. The PID stays valid
|
|
1410
|
-
// for the life of this dev-server instance; subsequent log lines
|
|
1411
|
-
// emitted by ViteDevServer flow into the pid's stderr ring,
|
|
1412
|
-
// visible in the Process tab.
|
|
1413
|
-
//
|
|
1414
|
-
// Long-running handoff (bin-spawn contract): when invoked from a
|
|
1415
|
-
// wrapper that already allocated a pid (`npm run dev` via
|
|
1416
|
-
// shellExecuteTracked, or the npm-bin resolver), ADOPT that pid instead
|
|
1417
|
-
// of spawning a second one. One pid, one start banner, no false exit —
|
|
1418
|
-
// the wrapper stays `running` in /api/processes with this port.
|
|
1419
|
-
const binSpawn = ctx.__nimbusBinSpawn;
|
|
1420
|
-
const adoptedEntry = binSpawn?.skipSpawn && binSpawn.callerPid != null
|
|
1421
|
-
? self.processes.get(binSpawn.callerPid)
|
|
1422
|
-
: undefined;
|
|
1423
|
-
const handedOff = adoptedEntry != null;
|
|
1424
|
-
const viteProcEntry = adoptedEntry ?? self.processes.spawn('vite (' + vfsRoot + ')', expandedArgs, vfsRoot, { longRunning: true });
|
|
1425
|
-
if (handedOff)
|
|
1426
|
-
self.processes.setLongRunning(viteProcEntry.pid);
|
|
1427
|
-
self.viteDevServer = new ViteDevServer({
|
|
1428
|
-
vfs: self.sqliteFs,
|
|
1429
|
-
esbuild: self.esbuildService,
|
|
1430
|
-
root: vfsRoot,
|
|
1431
|
-
port: resolvedPort,
|
|
1432
|
-
aliases: viteConfig.alias,
|
|
1433
|
-
define: viteDefine,
|
|
1434
|
-
onHmrMessage: (msg) => {
|
|
1435
|
-
if (self.terminal)
|
|
1436
|
-
try {
|
|
1437
|
-
self.terminal.ws.send(JSON.stringify({ type: 'hmr', data: msg }));
|
|
1438
|
-
}
|
|
1439
|
-
catch { }
|
|
1440
|
-
},
|
|
1441
|
-
sql: self.ctx.storage.sql,
|
|
1442
|
-
injectBasename: viteConfig.injectBasename,
|
|
1443
|
-
basePath: previewBasePath,
|
|
1444
|
-
env: self.env,
|
|
1445
|
-
ctx: self.ctx,
|
|
1446
|
-
// process diagnostics support: wire dev-server diagnostics into the
|
|
1447
|
-
// supervisor's per-PID log store so the Process tab is no
|
|
1448
|
-
// longer silent after the banner.
|
|
1449
|
-
pid: viteProcEntry.pid,
|
|
1450
|
-
processes: self.processes,
|
|
1451
|
-
});
|
|
1452
|
-
self.viteDevServer.start();
|
|
1453
|
-
try {
|
|
1454
|
-
await self.ctx.storage.put(VITE_CONFIG_KEY, {
|
|
1455
|
-
root: vfsRoot, aliases: viteConfig.alias, define: viteDefine,
|
|
1456
|
-
injectBasename: viteConfig.injectBasename, basePath: previewBasePath,
|
|
1457
|
-
port: resolvedPort,
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
1460
|
-
catch { }
|
|
1461
|
-
// Register the port and build the long-running stub. The stub
|
|
1462
|
-
// forwards into the in-process viteDevServer through the generic
|
|
1463
|
-
// long-running adapter — same hook every future long-running
|
|
1464
|
-
// facet uses (Express, Bun.serve, http.createServer().listen()).
|
|
1465
|
-
const viteStub = makeLongRunningPortStub(self.viteDevServer);
|
|
1466
|
-
self.portRegistry.bindFacetStub(viteProcEntry.pid, viteStub);
|
|
1467
|
-
await clearPortCapability(self, resolvedPort);
|
|
1468
|
-
self.portRegistry.register(resolvedPort, viteProcEntry.pid);
|
|
1469
|
-
// Track the wiring so `vite stop` and crash-handlers can tear it
|
|
1470
|
-
// down without searching the registry.
|
|
1471
|
-
self._viteShimPid = viteProcEntry.pid;
|
|
1472
|
-
self._viteShimPort = resolvedPort;
|
|
1473
|
-
// Spawn / long-running event for the Process tab UI. Mirrors the
|
|
1474
|
-
// shellExecuteTracked banner so the user sees the same shape no
|
|
1475
|
-
// matter how vite was invoked. Suppressed on handoff — the wrapper
|
|
1476
|
-
// already emitted the single start banner and spawn event for this pid.
|
|
1477
|
-
if (!handedOff) {
|
|
1478
|
-
if (self.terminal) {
|
|
1479
|
-
self.terminal.write(`\x1b[2m[shell started (long-running): pid=${viteProcEntry.pid} cmd="vite ${expandedArgs.join(' ')}"]\x1b[0m\r\n`);
|
|
1480
|
-
}
|
|
1481
|
-
notifyTerminalEvent(self.terminal, {
|
|
1482
|
-
type: 'spawn',
|
|
1483
|
-
pid: viteProcEntry.pid,
|
|
1484
|
-
command: 'vite ' + expandedArgs.join(' '),
|
|
1485
|
-
longRunning: true,
|
|
1486
|
-
attachedTty: false,
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
// Banner — reports the resolved port and PID so the user can
|
|
1490
|
-
// verify the multi-target routing.
|
|
1491
|
-
ctx.stdout.write('\n\x1b[1;36m Nimbus Vite Dev Server\x1b[0m\n\n');
|
|
1492
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Preview: \x1b[36m' + previewBasePath + '/\x1b[0m');
|
|
1493
|
-
if (resolvedPort !== vitePortDefault) {
|
|
1494
|
-
ctx.stdout.write(' \x1b[2m(also: ' + previewBasePath + '/?port=' + resolvedPort + ')\x1b[0m');
|
|
1495
|
-
}
|
|
1496
|
-
ctx.stdout.write('\n');
|
|
1497
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Root: ' + vfsRoot + '\n');
|
|
1498
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Port: ' + resolvedPort + ' \x1b[2m(pid=' + viteProcEntry.pid + ')\x1b[0m\n');
|
|
1499
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Transforms: .ts .tsx .jsx (React JSX automatic)\n');
|
|
1500
|
-
if (viteConfig.alias)
|
|
1501
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Aliases: ' + Object.keys(viteConfig.alias).join(', ') + '\n');
|
|
1502
|
-
if (viteDefine)
|
|
1503
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Define: ' + Object.keys(viteDefine).join(', ') + '\n');
|
|
1504
|
-
const twCfg = [vfsRoot + '/tailwind.config.js', vfsRoot + '/tailwind.config.ts'].find(p => kernelFs.exists(p));
|
|
1505
|
-
if (twCfg)
|
|
1506
|
-
ctx.stdout.write(' \x1b[32m\u279C\x1b[0m Tailwind: edge-vendored Play CDN \x1b[2m(detected)\x1b[0m\n');
|
|
1507
|
-
ctx.stdout.write('\n \x1b[2mRun \x1b[0mvite stop\x1b[2m, or \x1b[0mkill ' + viteProcEntry.pid + '\x1b[2m, to stop.\x1b[0m\n\n');
|
|
1508
|
-
return 0;
|
|
1509
|
-
});
|
|
1065
|
+
registry.register('vite', createViteCommand(self));
|
|
1510
1066
|
// ── nimbus-wrangler / wrangler command: Worker dev server ─────────────
|
|
1511
1067
|
//
|
|
1512
1068
|
// `wrangler` is registered as a transparent alias for `nimbus-wrangler`
|
|
@@ -1742,8 +1298,8 @@ export async function initSession(self, ws) {
|
|
|
1742
1298
|
const result = await shell.execute(cmd, {
|
|
1743
1299
|
cwd: cmdCtx.cwd,
|
|
1744
1300
|
env: cmdCtx.env,
|
|
1745
|
-
onStdout: (d) => cmdCtx.stdout.write(d),
|
|
1746
|
-
onStderr: (d) => cmdCtx.stderr.write(d),
|
|
1301
|
+
onStdout: textSink((d) => cmdCtx.stdout.write(d)),
|
|
1302
|
+
onStderr: textSink((d) => cmdCtx.stderr.write(d)),
|
|
1747
1303
|
stdin,
|
|
1748
1304
|
terminalStdin: cmdCtx.terminalStdin,
|
|
1749
1305
|
// `npm run x` runs x on npm's own fds. Handing the nested execution a
|
|
@@ -1821,15 +1377,18 @@ export async function initSession(self, ws) {
|
|
|
1821
1377
|
});
|
|
1822
1378
|
// Wrap the caller-supplied streams so every chunk is both displayed
|
|
1823
1379
|
// AND captured in the ring buffer keyed by this PID.
|
|
1824
|
-
const tee = (stream, target) =>
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1380
|
+
const tee = (stream, target) => {
|
|
1381
|
+
const toTarget = textSink((text) => target.write(text));
|
|
1382
|
+
return (d) => {
|
|
1383
|
+
try {
|
|
1384
|
+
self.processes.appendOutputBytes(pid, stream, d);
|
|
1385
|
+
}
|
|
1386
|
+
catch { }
|
|
1387
|
+
try {
|
|
1388
|
+
toTarget(d);
|
|
1389
|
+
}
|
|
1390
|
+
catch { }
|
|
1391
|
+
};
|
|
1833
1392
|
};
|
|
1834
1393
|
let exitCode = 1;
|
|
1835
1394
|
try {
|
|
@@ -1872,7 +1431,15 @@ export async function initSession(self, ws) {
|
|
|
1872
1431
|
catch (e) {
|
|
1873
1432
|
// Surface the error in the terminal and the ring buffer.
|
|
1874
1433
|
const msg = (e && (e.stack || e.message)) || String(e);
|
|
1875
|
-
|
|
1434
|
+
const line = 'shellExecuteTracked error: ' + msg + '\n';
|
|
1435
|
+
try {
|
|
1436
|
+
self.processes.appendOutput(pid, 'stderr', line);
|
|
1437
|
+
}
|
|
1438
|
+
catch { }
|
|
1439
|
+
try {
|
|
1440
|
+
cmdCtx.stderr.write(line);
|
|
1441
|
+
}
|
|
1442
|
+
catch { }
|
|
1876
1443
|
exitCode = 1;
|
|
1877
1444
|
}
|
|
1878
1445
|
finally {
|
|
@@ -1927,8 +1494,11 @@ export async function initSession(self, ws) {
|
|
|
1927
1494
|
catch { }
|
|
1928
1495
|
},
|
|
1929
1496
|
});
|
|
1930
|
-
// Register core npm
|
|
1931
|
-
|
|
1497
|
+
// Register core npm — the install work itself goes through the port,
|
|
1498
|
+
// which owns the NpmInstaller, prefix dirs and bin materialisation.
|
|
1499
|
+
const coreNpmCmd = createNpmCommand(registry, shellExecute, kernel, {
|
|
1500
|
+
installer: createNpmInstallPort(self),
|
|
1501
|
+
});
|
|
1932
1502
|
registry.register('npm', async (ctx) => {
|
|
1933
1503
|
const args = ctx.args || [];
|
|
1934
1504
|
const sub = args[0];
|
|
@@ -2012,26 +1582,17 @@ export async function initSession(self, ws) {
|
|
|
2012
1582
|
// (the only thing the module did was hold these strings) and
|
|
2013
1583
|
// the file is deleted.
|
|
2014
1584
|
//
|
|
2015
|
-
// Next.js dev/start
|
|
2016
|
-
// child_process.fork with v8-IPC +
|
|
2017
|
-
// which Nimbus ships. We surface a
|
|
2018
|
-
// rather than letting the script hang or
|
|
2019
|
-
//
|
|
2020
|
-
// guard, NOT a generic per-framework
|
|
2021
|
-
// similar guard belongs alongside this
|
|
2022
|
-
// src/frameworks/<name>.ts file.
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
const NEXT_BLOCK_MESSAGE = '\x1b[31m\u2718\x1b[0m \x1b[1mNext.js dev server is not supported in Nimbus.\x1b[0m\n' +
|
|
2027
|
-
' Specific blockers:\n' +
|
|
2028
|
-
" 1. \x1b[2mchild_process.fork\x1b[0m IPC uses v8-serializer (Nimbus ships JSON projection).\n" +
|
|
2029
|
-
' 2. webpack / Turbopack bundlers are not integrated with the pre-bundle pipeline.\n' +
|
|
2030
|
-
' 3. Custom \x1b[2mhttp.Server\x1b[0m semantics (keep-alive, raw sockets) are facet-incompatible.\n' +
|
|
2031
|
-
'\n' +
|
|
2032
|
-
' Workaround: deploy with \x1b[36mnext build\x1b[0m + a hosted runtime,\n' +
|
|
2033
|
-
' or pass \x1b[36m--allow-next\x1b[0m to bypass at your own risk.\n';
|
|
2034
|
-
ctx.stderr.write(NEXT_BLOCK_MESSAGE);
|
|
1585
|
+
// Next.js dev/start/build/export all need the same pipeline —
|
|
1586
|
+
// custom http.Server + child_process.fork with v8-IPC +
|
|
1587
|
+
// webpack/Turbopack — none of which Nimbus ships. We surface a
|
|
1588
|
+
// deterministic refusal rather than letting the script hang or
|
|
1589
|
+
// (build) reset the isolate during spawn. The block remains a
|
|
1590
|
+
// one-off symbol-detection guard, NOT a generic per-framework
|
|
1591
|
+
// code path. Any future similar guard belongs alongside this
|
|
1592
|
+
// one — not in its own src/frameworks/<name>.ts file.
|
|
1593
|
+
const refused = refusedNextSubcommand(scriptName, script, pkg, scriptArgs);
|
|
1594
|
+
if (refused) {
|
|
1595
|
+
ctx.stderr.write(NEXT_REFUSAL_MESSAGE);
|
|
2035
1596
|
return 127;
|
|
2036
1597
|
}
|
|
2037
1598
|
const scriptTrim = script.trim();
|
|
@@ -2141,85 +1702,6 @@ export async function initSession(self, ws) {
|
|
|
2141
1702
|
catch { }
|
|
2142
1703
|
return 0;
|
|
2143
1704
|
}
|
|
2144
|
-
// npm install (no args or with packages) — use NpmInstaller v2 (batched writes)
|
|
2145
|
-
if (sub === 'install' || sub === 'i' || sub === 'add') {
|
|
2146
|
-
const installInvocation = parseNpmInstallInvocation(args.slice(1));
|
|
2147
|
-
const explicitPkgs = installInvocation.packages;
|
|
2148
|
-
const globalPrefix = installInvocation.global
|
|
2149
|
-
? resolveNpmPrefix(installInvocation.prefix ?? String(ctx.env?.npm_config_prefix || '/usr/local'), ctx.cwd || '/home/user')
|
|
2150
|
-
: null;
|
|
2151
|
-
self.ensureSqliteFs();
|
|
2152
|
-
if (globalPrefix)
|
|
2153
|
-
self.ensureGlobalPrefixDirs(globalPrefix);
|
|
2154
|
-
const installCwd = globalPrefix ? `${globalPrefix}/lib` : cwdKey;
|
|
2155
|
-
// Ensure package.json exists for bare `npm install`
|
|
2156
|
-
if (!globalPrefix && explicitPkgs.length === 0) {
|
|
2157
|
-
const pkgJsonPath = installCwd + '/package.json';
|
|
2158
|
-
if (!kernelFs.exists(pkgJsonPath)) {
|
|
2159
|
-
ctx.stderr.write('npm ERR! no package.json found\n');
|
|
2160
|
-
return 1;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
if (globalPrefix && explicitPkgs.length === 0) {
|
|
2164
|
-
ctx.stderr.write('npm ERR! missing package name for global install\n');
|
|
2165
|
-
return 1;
|
|
2166
|
-
}
|
|
2167
|
-
const pkgLabel = explicitPkgs.length > 0
|
|
2168
|
-
? `${explicitPkgs.length} packages`
|
|
2169
|
-
: 'dependencies from package.json';
|
|
2170
|
-
ctx.stdout.write(`\x1b[36mInstalling ${pkgLabel} (npm v2 — batched writes)...\x1b[0m\n`);
|
|
2171
|
-
await self.ensureNpmInstaller((msg) => {
|
|
2172
|
-
ctx.stdout.write('[npm] ' + msg + '\n');
|
|
2173
|
-
});
|
|
2174
|
-
// `--loglevel` selects npm's own log protocol on stderr, where npm
|
|
2175
|
-
// writes it. Tooling that drives npm parses those lines rather than
|
|
2176
|
-
// our prose — pi's installer advances its progress label off them.
|
|
2177
|
-
const npmLogLevel = installInvocation.loglevel;
|
|
2178
|
-
const npmLog = npmLogLevel === null ? undefined : ((level, line) => {
|
|
2179
|
-
if (npmLogEnabled(npmLogLevel, level))
|
|
2180
|
-
ctx.stderr.write(line + '\n');
|
|
2181
|
-
});
|
|
2182
|
-
try {
|
|
2183
|
-
const result = await self.npmInstaller.install(installCwd, {
|
|
2184
|
-
packages: explicitPkgs.length > 0 ? explicitPkgs : undefined,
|
|
2185
|
-
production: installInvocation.production,
|
|
2186
|
-
pid: ctx.pid,
|
|
2187
|
-
npmLog,
|
|
2188
|
-
});
|
|
2189
|
-
if (result.failed?.length > 0) {
|
|
2190
|
-
ctx.stderr.write('\x1b[31mFailed: ' + result.failed.join(', ') + '\x1b[0m\n');
|
|
2191
|
-
}
|
|
2192
|
-
// [HONEST INSTALL MESSAGE P0a] Yellow + "(N failed, see above)"
|
|
2193
|
-
// when partial. Green only when failed.length === 0.
|
|
2194
|
-
const partial = (result.failed?.length || 0) > 0;
|
|
2195
|
-
const color = partial ? '\x1b[33m' : '\x1b[32m';
|
|
2196
|
-
const suffix = partial ? ` (${result.failed.length} failed, see above)` : '';
|
|
2197
|
-
ctx.stdout.write(`\n${color}added ${result.installed?.length || 0} packages (${result.totalFiles || 0} files) in ${((result.elapsed || 0) / 1000).toFixed(1)}s${suffix}\x1b[0m\n`);
|
|
2198
|
-
if (result.cachedHits > 0) {
|
|
2199
|
-
ctx.stdout.write(`\x1b[2m (${result.cachedHits} from cache)\x1b[0m\n`);
|
|
2200
|
-
}
|
|
2201
|
-
if (globalPrefix) {
|
|
2202
|
-
// Materialize on-PATH bin shims even for partial installs. The
|
|
2203
|
-
// only writer of shims into ${globalPrefix}/bin used to be gated
|
|
2204
|
-
// behind zero failures across the whole dependency tree — but a
|
|
2205
|
-
// global install of a 100+-dep package almost always has at least
|
|
2206
|
-
// one transitive failure, so /usr/local/bin was ~never created
|
|
2207
|
-
// and the installed package's bin was unreachable. materialize →
|
|
2208
|
-
// validateEntry → resolveExistingTarget already skips bins whose
|
|
2209
|
-
// target file didn't land, so a partial install safely exposes
|
|
2210
|
-
// exactly the bins that actually installed.
|
|
2211
|
-
const linked = materializeNpmBinShims(kernelFs, `${installCwd}/node_modules`, `${globalPrefix}/bin`);
|
|
2212
|
-
if (linked > 0) {
|
|
2213
|
-
ctx.stdout.write(`\x1b[2m linked ${linked} bin${linked === 1 ? '' : 's'} into /${globalPrefix}/bin\x1b[0m\n`);
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
return result.failed?.length > 0 ? 1 : 0;
|
|
2217
|
-
}
|
|
2218
|
-
catch (e) {
|
|
2219
|
-
ctx.stderr.write(`\x1b[31mnpm install failed: ${e?.message}\x1b[0m\n`);
|
|
2220
|
-
return 1;
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
1705
|
// ── npm create <pkg> / npm init <pkg> → npx create-<pkg> ─────────
|
|
2224
1706
|
//
|
|
2225
1707
|
// Per the npm spec, `npm create X args...` and `npm init X args...`
|
|
@@ -2638,17 +2120,17 @@ export async function initSession(self, ws) {
|
|
|
2638
2120
|
catch { }
|
|
2639
2121
|
// ── Starter-app hint (only if seed sentinel still exists) ──
|
|
2640
2122
|
// We check the live VFS, not a static file, so that if the user
|
|
2641
|
-
// deletes ~/.nimbus-seeded (or
|
|
2642
|
-
// next login.
|
|
2123
|
+
// deletes ~/.nimbus-seeded (or the project dir) the hint stops
|
|
2124
|
+
// appearing on next login.
|
|
2643
2125
|
try {
|
|
2644
2126
|
if (hasSeededProject(self.sqliteFs) && kernelFs.exists(SEED_PROJECT_DIR)) {
|
|
2645
|
-
self.terminal.write(
|
|
2646
|
-
|
|
2127
|
+
self.terminal.write(`\x1b[2mStarter app ready at \x1b[36m${SEED_PROJECT_TILDE}\x1b[0m\x1b[2m — try:\x1b[0m\r\n` +
|
|
2128
|
+
` \x1b[36mcd ${SEED_PROJECT_NAME} && npm install && npm run dev\x1b[0m\r\n\r\n`);
|
|
2647
2129
|
}
|
|
2648
2130
|
}
|
|
2649
2131
|
catch { }
|
|
2650
2132
|
// ── W11: framework detection MOTD line ──
|
|
2651
|
-
// If
|
|
2133
|
+
// If the seeded project dir has a recognizable framework, print one informational line.
|
|
2652
2134
|
// Purely advisory — does not change boot behaviour. Fire-and-forget
|
|
2653
2135
|
// because initSession is sync; any failure is silently swallowed.
|
|
2654
2136
|
void (async () => {
|
|
@@ -2729,13 +2211,19 @@ export async function initSession(self, ws) {
|
|
|
2729
2211
|
// ── Start shell ──
|
|
2730
2212
|
//
|
|
2731
2213
|
// Now, and not inside the workspace, because the login files are the
|
|
2732
|
-
// user's and may name any of the commands registered above.
|
|
2733
|
-
// `shell.start()`
|
|
2734
|
-
//
|
|
2735
|
-
//
|
|
2736
|
-
|
|
2737
|
-
|
|
2214
|
+
// user's and may name any of the commands registered above. On a
|
|
2215
|
+
// reconnect it is not awaited: `shell.start()` registers the input
|
|
2216
|
+
// handler synchronously and the rc files apply as they finish, which is
|
|
2217
|
+
// what the terminal has always done. On a wake the caller has the
|
|
2218
|
+
// peer's frame in hand and delivers it the moment this returns, so the
|
|
2219
|
+
// prompt has to be on the terminal first — otherwise the line runs
|
|
2220
|
+
// alongside the rc files and its prompt lands before the shell's own.
|
|
2221
|
+
// Either way a user's broken rc file must not take the socket down.
|
|
2222
|
+
const started = workspace.start().catch((e) => {
|
|
2223
|
+
console.warn('[nimbus] shell start failed:', errorText(e));
|
|
2738
2224
|
});
|
|
2225
|
+
if (options.resume === 'wake')
|
|
2226
|
+
await started;
|
|
2739
2227
|
ws.send(JSON.stringify({ type: 'ready' }));
|
|
2740
2228
|
}
|
|
2741
2229
|
function installShellExecutionFeatures(shell, terminal) {
|