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