@lark-apaas/fullstack-cli 1.1.59 → 1.1.61-alpha.20260818172555
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/dist/client-dependency-graph.d.ts +37 -0
- package/dist/client-dependency-graph.js +466 -0
- package/dist/index.js +3482 -143
- package/package.json +4 -1
- package/templates/scripts/cache-generation-preflight.mjs +1346 -0
- package/templates/scripts/cache-generation-prune.mjs +174 -0
- package/templates/scripts/cache-runtime-coordinator.mjs +487 -0
- package/templates/scripts/dev.js +433 -59
- package/templates/scripts/dev.sh +2 -0
- package/templates/scripts/lint.js +2 -25
- package/templates/scripts/patch-vite-dependency-graph-hash.mjs +141 -0
- package/templates/scripts/server-cache-module-resolver.cjs +63 -0
- package/templates/scripts/server-cache-runtime.mjs +681 -0
- package/templates/scripts/server-startup-runtime.mjs +496 -0
- package/templates/scripts/server-transition-runtime.mjs +655 -0
- package/templates/scripts/vite-cache-runtime.mjs +3100 -0
- package/templates/scripts/workspace-client-runtime.mjs +176 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const projectRoot = path.resolve(
|
|
10
|
+
process.env.MIAODA_WORKSPACE_ROOT || process.cwd()
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
// The bundle orchestrator may be started directly by the image before the
|
|
14
|
+
// project npm task exists. Load the same project .env contract as dev.js so the
|
|
15
|
+
// later source service receives identical application configuration.
|
|
16
|
+
const projectEnvFile = path.join(projectRoot, '.env');
|
|
17
|
+
if (fs.existsSync(projectEnvFile)) {
|
|
18
|
+
for (const line of fs.readFileSync(projectEnvFile, 'utf8').split('\n')) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
21
|
+
const separator = trimmed.indexOf('=');
|
|
22
|
+
if (separator < 1) continue;
|
|
23
|
+
const key = trimmed.slice(0, separator).trim();
|
|
24
|
+
const value = trimmed.slice(separator + 1).trim();
|
|
25
|
+
if (!(key in process.env)) process.env[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const scriptsRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const workspaceReadyFile = path.resolve(
|
|
30
|
+
process.env.MIAODA_WORKSPACE_READY_FILE || '/tmp/event/WORKSPACE_READY'
|
|
31
|
+
);
|
|
32
|
+
const bundleFallbackFile = path.resolve(
|
|
33
|
+
process.env.MIAODA_NEST_BUNDLE_FALLBACK_FILE ||
|
|
34
|
+
'/tmp/event/MIAODA_NEST_BUNDLE_FALLBACK'
|
|
35
|
+
);
|
|
36
|
+
const serverHost = process.env.SERVER_HOST || '0.0.0.0';
|
|
37
|
+
const serverPort = Number(process.env.SERVER_PORT || 3000);
|
|
38
|
+
const requestedMode = process.env.MIAODA_NEST_START_MODE || 'workspace';
|
|
39
|
+
const supportedModes = new Set([
|
|
40
|
+
'bundle-handoff',
|
|
41
|
+
'workspace',
|
|
42
|
+
'dist',
|
|
43
|
+
'ts-node',
|
|
44
|
+
]);
|
|
45
|
+
const runtimeStartedAtEpochMs = Date.now();
|
|
46
|
+
const startedAt = process.hrtime.bigint();
|
|
47
|
+
let stopping = false;
|
|
48
|
+
let serviceChild;
|
|
49
|
+
let compilerChild;
|
|
50
|
+
let sourceWatcher;
|
|
51
|
+
let restartTimer;
|
|
52
|
+
let compiling = false;
|
|
53
|
+
let compileQueued = false;
|
|
54
|
+
let restartingService = false;
|
|
55
|
+
|
|
56
|
+
if (!supportedModes.has(requestedMode)) {
|
|
57
|
+
throw new Error(`MIAODA_NEST_START_MODE_INVALID: ${requestedMode}`);
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
!Number.isSafeInteger(serverPort) ||
|
|
61
|
+
serverPort <= 0 ||
|
|
62
|
+
serverPort > 65_535
|
|
63
|
+
) {
|
|
64
|
+
throw new Error('MIAODA_NEST_SERVER_PORT_INVALID');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function durationMs(since = startedAt) {
|
|
68
|
+
return Number(process.hrtime.bigint() - since) / 1_000_000;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function emit(phase, since, details = {}) {
|
|
72
|
+
const atEpochMs = Date.now();
|
|
73
|
+
const phaseDurationMs = Number(durationMs(since).toFixed(3));
|
|
74
|
+
process.stdout.write(
|
|
75
|
+
`${JSON.stringify({
|
|
76
|
+
event: 'miaoda_workspace_runtime_phase',
|
|
77
|
+
runtime: 'nest',
|
|
78
|
+
mode: requestedMode,
|
|
79
|
+
phase,
|
|
80
|
+
atEpochMs,
|
|
81
|
+
phaseStartedAtEpochMs: Number((atEpochMs - phaseDurationMs).toFixed(3)),
|
|
82
|
+
runtimeStartedAtEpochMs,
|
|
83
|
+
durationMs: phaseDurationMs,
|
|
84
|
+
...details,
|
|
85
|
+
})}\n`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function delay(ms) {
|
|
90
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function markBundleFallback(reason) {
|
|
94
|
+
fs.mkdirSync(path.dirname(bundleFallbackFile), { recursive: true });
|
|
95
|
+
const temporaryFile = `${bundleFallbackFile}.${process.pid}.tmp`;
|
|
96
|
+
fs.writeFileSync(
|
|
97
|
+
temporaryFile,
|
|
98
|
+
`${JSON.stringify({ schemaVersion: 1, reason, at: new Date().toISOString() })}\n`,
|
|
99
|
+
{ mode: 0o600 }
|
|
100
|
+
);
|
|
101
|
+
fs.renameSync(temporaryFile, bundleFallbackFile);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function childRunning(child) {
|
|
105
|
+
return Boolean(
|
|
106
|
+
child?.pid && child.exitCode === null && child.signalCode === null
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function terminate(child, signal = 'SIGTERM') {
|
|
111
|
+
if (!child?.pid) return;
|
|
112
|
+
try {
|
|
113
|
+
process.kill(-child.pid, signal);
|
|
114
|
+
} catch {
|
|
115
|
+
try {
|
|
116
|
+
child.kill(signal);
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function workspaceDependenciesReady() {
|
|
122
|
+
try {
|
|
123
|
+
return (
|
|
124
|
+
fs.statSync(workspaceReadyFile).isFile() &&
|
|
125
|
+
fs.statSync(path.join(projectRoot, 'node_modules')).isDirectory()
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function waitForDependencies() {
|
|
133
|
+
const waitStartedAt = process.hrtime.bigint();
|
|
134
|
+
while (!stopping && !workspaceDependenciesReady()) await delay(100);
|
|
135
|
+
if (stopping) throw new Error('MIAODA_NEST_STARTUP_STOPPING');
|
|
136
|
+
emit('dependency_wait', waitStartedAt, { workspaceReadyFile });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function probeHost(host) {
|
|
140
|
+
if (!host || host === '0.0.0.0') return '127.0.0.1';
|
|
141
|
+
if (host === '::' || host === '[::]') return '::1';
|
|
142
|
+
return host.replace(/^\[|\]$/g, '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function waitForPort(child = serviceChild) {
|
|
146
|
+
const listenStartedAt = process.hrtime.bigint();
|
|
147
|
+
const deadline = Date.now() + 60_000;
|
|
148
|
+
const host = probeHost(serverHost);
|
|
149
|
+
while (!stopping && Date.now() < deadline) {
|
|
150
|
+
if (child && !childRunning(child)) {
|
|
151
|
+
throw new Error('MIAODA_NEST_PROCESS_EXITED_BEFORE_READY');
|
|
152
|
+
}
|
|
153
|
+
const connected = await new Promise(resolve => {
|
|
154
|
+
const socket = net.createConnection({ host, port: serverPort });
|
|
155
|
+
socket.once('connect', () => {
|
|
156
|
+
socket.destroy();
|
|
157
|
+
resolve(true);
|
|
158
|
+
});
|
|
159
|
+
socket.once('error', () => resolve(false));
|
|
160
|
+
});
|
|
161
|
+
if (connected) {
|
|
162
|
+
emit('listen_ready', listenStartedAt, {
|
|
163
|
+
host: serverHost,
|
|
164
|
+
port: serverPort,
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await delay(100);
|
|
169
|
+
}
|
|
170
|
+
throw new Error('MIAODA_NEST_READY_TIMEOUT');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function spawnInherited(command, args, extraEnv = {}) {
|
|
174
|
+
return spawn(command, args, {
|
|
175
|
+
cwd: projectRoot,
|
|
176
|
+
detached: true,
|
|
177
|
+
stdio: 'inherit',
|
|
178
|
+
env: {
|
|
179
|
+
...process.env,
|
|
180
|
+
...extraEnv,
|
|
181
|
+
MIAODA_CACHE_BOOTSTRAP_ENABLED: 'false',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function runCommand(command, args, phase) {
|
|
187
|
+
const phaseStartedAt = process.hrtime.bigint();
|
|
188
|
+
compilerChild = spawnInherited(command, args);
|
|
189
|
+
const result = await new Promise((resolve, reject) => {
|
|
190
|
+
compilerChild.once('error', reject);
|
|
191
|
+
compilerChild.once('exit', (code, signal) => resolve({ code, signal }));
|
|
192
|
+
});
|
|
193
|
+
compilerChild = undefined;
|
|
194
|
+
emit(phase, phaseStartedAt, result);
|
|
195
|
+
if (result.code !== 0) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`MIAODA_NEST_COMMAND_FAILED: ${command} ${args.join(' ')} code=${result.code} signal=${result.signal}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function resolveProjectModule(relativePath) {
|
|
203
|
+
const resolved = path.join(projectRoot, 'node_modules', relativePath);
|
|
204
|
+
if (!fs.existsSync(resolved)) {
|
|
205
|
+
throw new Error(`MIAODA_NEST_TOOL_MISSING: ${relativePath}`);
|
|
206
|
+
}
|
|
207
|
+
return resolved;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function initializeActionPlugins() {
|
|
211
|
+
const cli = [
|
|
212
|
+
process.env.MIAODA_FULLSTACK_CLI_BIN,
|
|
213
|
+
path.join(
|
|
214
|
+
projectRoot,
|
|
215
|
+
'node_modules',
|
|
216
|
+
'@lark-apaas',
|
|
217
|
+
'fullstack-cli',
|
|
218
|
+
'bin',
|
|
219
|
+
'cli.js'
|
|
220
|
+
),
|
|
221
|
+
'/usr/lib/node_modules/@lark-apaas/fullstack-cli/bin/cli.js',
|
|
222
|
+
].find(candidate => candidate && fs.existsSync(candidate));
|
|
223
|
+
if (!cli) {
|
|
224
|
+
throw new Error('MIAODA_ACTION_PLUGIN_CLI_MISSING');
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
await runCommand(
|
|
228
|
+
process.execPath,
|
|
229
|
+
[cli, 'action-plugin', 'init'],
|
|
230
|
+
'action_plugin_init'
|
|
231
|
+
);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
// Match the original dev.js contract: plugin discovery failure is visible
|
|
234
|
+
// in logs but does not replace the Nest process with a platform failure.
|
|
235
|
+
process.stderr.write(
|
|
236
|
+
`${JSON.stringify({
|
|
237
|
+
event: 'miaoda_action_plugin_init_failed',
|
|
238
|
+
continueStartup: true,
|
|
239
|
+
error: error instanceof Error ? error.message : String(error),
|
|
240
|
+
})}\n`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function spawnWorkspaceService() {
|
|
246
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
247
|
+
return spawnInherited(npmCommand, ['run', 'dev:server']);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function spawnDistService() {
|
|
251
|
+
return spawnInherited(
|
|
252
|
+
process.execPath,
|
|
253
|
+
[
|
|
254
|
+
'-r',
|
|
255
|
+
resolveProjectModule('tsconfig-paths/register.js'),
|
|
256
|
+
path.join(projectRoot, 'dist', 'server', 'main.js'),
|
|
257
|
+
],
|
|
258
|
+
{
|
|
259
|
+
// Keep direct runtime modes aligned with `npm run dev:server`.
|
|
260
|
+
NODE_ENV: 'development',
|
|
261
|
+
// TypeScript preserves path aliases in emitted JavaScript. Point the
|
|
262
|
+
// existing tsconfig-paths resolver at dist so @server/* and @shared/*
|
|
263
|
+
// resolve to compiled files instead of the TypeScript source tree.
|
|
264
|
+
TS_NODE_PROJECT: path.join(projectRoot, 'tsconfig.node.json'),
|
|
265
|
+
TS_NODE_BASEURL: path.join(projectRoot, 'dist'),
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function spawnTsNodeService() {
|
|
271
|
+
const sourceEntry = path.join(projectRoot, 'server', 'main.ts');
|
|
272
|
+
return spawnInherited(
|
|
273
|
+
process.execPath,
|
|
274
|
+
[
|
|
275
|
+
'-r',
|
|
276
|
+
resolveProjectModule('ts-node/register/transpile-only.js'),
|
|
277
|
+
'-r',
|
|
278
|
+
resolveProjectModule('tsconfig-paths/register.js'),
|
|
279
|
+
'-e',
|
|
280
|
+
`require(${JSON.stringify(sourceEntry)})`,
|
|
281
|
+
],
|
|
282
|
+
{
|
|
283
|
+
// `npm run dev:server` sets this in package.json. The direct ts-node
|
|
284
|
+
// path must preserve the same Nest module graph and development behavior.
|
|
285
|
+
NODE_ENV: 'development',
|
|
286
|
+
TS_NODE_PROJECT: path.join(projectRoot, 'tsconfig.node.json'),
|
|
287
|
+
TS_NODE_TRANSPILE_ONLY: 'true',
|
|
288
|
+
// A restored workspace can contain stale JavaScript next to TypeScript
|
|
289
|
+
// sources. Prefer the TypeScript file so a watcher restart always runs
|
|
290
|
+
// the source the user just edited instead of the old emitted file.
|
|
291
|
+
TS_NODE_PREFER_TS_EXTS: 'true',
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function watchSource(onChange) {
|
|
297
|
+
const watchers = [];
|
|
298
|
+
for (const relative of ['server', 'shared']) {
|
|
299
|
+
const directory = path.join(projectRoot, relative);
|
|
300
|
+
if (!fs.existsSync(directory)) continue;
|
|
301
|
+
watchers.push(
|
|
302
|
+
fs.watch(directory, { recursive: true }, (_event, filename) => {
|
|
303
|
+
if (!filename || !/\.(?:[cm]?[jt]sx?|json)$/.test(filename)) return;
|
|
304
|
+
clearTimeout(restartTimer);
|
|
305
|
+
restartTimer = setTimeout(onChange, 120);
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
sourceWatcher = { close: () => watchers.forEach(watcher => watcher.close()) };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function restartService(spawnService) {
|
|
313
|
+
if (stopping || restartingService) return;
|
|
314
|
+
restartingService = true;
|
|
315
|
+
const oldChild = serviceChild;
|
|
316
|
+
try {
|
|
317
|
+
terminate(oldChild);
|
|
318
|
+
if (oldChild && childRunning(oldChild)) {
|
|
319
|
+
await Promise.race([
|
|
320
|
+
new Promise(resolve => oldChild.once('exit', resolve)),
|
|
321
|
+
delay(1_000),
|
|
322
|
+
]);
|
|
323
|
+
terminate(oldChild, 'SIGKILL');
|
|
324
|
+
}
|
|
325
|
+
if (!stopping) serviceChild = spawnService();
|
|
326
|
+
} finally {
|
|
327
|
+
restartingService = false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function waitForServiceLifetime() {
|
|
332
|
+
while (!stopping) {
|
|
333
|
+
const observedChild = serviceChild;
|
|
334
|
+
if (!observedChild) return { code: 1, signal: null };
|
|
335
|
+
const exit = await new Promise((resolve, reject) => {
|
|
336
|
+
observedChild.once('error', reject);
|
|
337
|
+
observedChild.once('exit', (code, signal) => resolve({ code, signal }));
|
|
338
|
+
});
|
|
339
|
+
while (restartingService && !stopping) await delay(10);
|
|
340
|
+
if (serviceChild !== observedChild) continue;
|
|
341
|
+
return exit;
|
|
342
|
+
}
|
|
343
|
+
return { code: 0, signal: null };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function compileDist() {
|
|
347
|
+
const tsc = resolveProjectModule('typescript/bin/tsc');
|
|
348
|
+
await runCommand(
|
|
349
|
+
process.execPath,
|
|
350
|
+
[tsc, '--project', 'tsconfig.node.json', '--incremental'],
|
|
351
|
+
'typescript_compile'
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function queueDistRebuild() {
|
|
356
|
+
if (compiling) {
|
|
357
|
+
compileQueued = true;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
compiling = true;
|
|
361
|
+
void (async () => {
|
|
362
|
+
try {
|
|
363
|
+
await compileDist();
|
|
364
|
+
await restartService(spawnDistService);
|
|
365
|
+
emit('source_restart', process.hrtime.bigint(), {
|
|
366
|
+
reason: 'dist-rebuilt',
|
|
367
|
+
});
|
|
368
|
+
} catch (error) {
|
|
369
|
+
process.stderr.write(
|
|
370
|
+
`${JSON.stringify({
|
|
371
|
+
event: 'miaoda_nest_dist_rebuild_failed',
|
|
372
|
+
error: error instanceof Error ? error.message : String(error),
|
|
373
|
+
})}\n`
|
|
374
|
+
);
|
|
375
|
+
} finally {
|
|
376
|
+
compiling = false;
|
|
377
|
+
if (compileQueued) {
|
|
378
|
+
compileQueued = false;
|
|
379
|
+
queueDistRebuild();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
})();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function runDependencyBackedMode(mode) {
|
|
386
|
+
await waitForDependencies();
|
|
387
|
+
await initializeActionPlugins();
|
|
388
|
+
if (mode === 'workspace') {
|
|
389
|
+
const spawnStartedAt = process.hrtime.bigint();
|
|
390
|
+
serviceChild = spawnWorkspaceService();
|
|
391
|
+
emit('process_spawn', spawnStartedAt, { pid: serviceChild.pid });
|
|
392
|
+
} else if (mode === 'dist') {
|
|
393
|
+
await compileDist();
|
|
394
|
+
const spawnStartedAt = process.hrtime.bigint();
|
|
395
|
+
serviceChild = spawnDistService();
|
|
396
|
+
emit('process_spawn', spawnStartedAt, {
|
|
397
|
+
pid: serviceChild.pid,
|
|
398
|
+
nodeEnv: 'development',
|
|
399
|
+
entry: 'dist/server/main.js',
|
|
400
|
+
});
|
|
401
|
+
watchSource(queueDistRebuild);
|
|
402
|
+
} else {
|
|
403
|
+
const spawnStartedAt = process.hrtime.bigint();
|
|
404
|
+
serviceChild = spawnTsNodeService();
|
|
405
|
+
emit('process_spawn', spawnStartedAt, {
|
|
406
|
+
pid: serviceChild.pid,
|
|
407
|
+
nodeEnv: 'development',
|
|
408
|
+
entry: 'server/main.ts',
|
|
409
|
+
transpileOnly: true,
|
|
410
|
+
preferTsExtensions: true,
|
|
411
|
+
});
|
|
412
|
+
watchSource(() => void restartService(spawnTsNodeService));
|
|
413
|
+
}
|
|
414
|
+
await waitForPort();
|
|
415
|
+
emit('startup', startedAt, { pid: serviceChild.pid, port: serverPort });
|
|
416
|
+
return waitForServiceLifetime();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function runBundleHandoff() {
|
|
420
|
+
// Once dependencies exist, never put the stale bundle back in front during
|
|
421
|
+
// a supervisor retry. This preserves the original Preview failure/retry UI
|
|
422
|
+
// when the complete workspace service fails after the transition window.
|
|
423
|
+
if (workspaceDependenciesReady()) {
|
|
424
|
+
emit('bundle_skipped', process.hrtime.bigint(), {
|
|
425
|
+
reason: 'workspace-dependencies-ready',
|
|
426
|
+
});
|
|
427
|
+
return runDependencyBackedMode('workspace');
|
|
428
|
+
}
|
|
429
|
+
const runtime = path.join(scriptsRoot, 'server-cache-runtime.mjs');
|
|
430
|
+
if (!fs.existsSync(runtime)) {
|
|
431
|
+
markBundleFallback('runtime-missing');
|
|
432
|
+
process.stderr.write(
|
|
433
|
+
`${JSON.stringify({
|
|
434
|
+
event: 'miaoda_nest_bundle_unavailable',
|
|
435
|
+
reason: 'runtime-missing',
|
|
436
|
+
})}\n`
|
|
437
|
+
);
|
|
438
|
+
return runDependencyBackedMode('workspace');
|
|
439
|
+
}
|
|
440
|
+
const spawnStartedAt = process.hrtime.bigint();
|
|
441
|
+
serviceChild = spawnInherited(process.execPath, [runtime]);
|
|
442
|
+
emit('process_spawn', spawnStartedAt, {
|
|
443
|
+
pid: serviceChild.pid,
|
|
444
|
+
childRuntime: 'server-cache-runtime',
|
|
445
|
+
});
|
|
446
|
+
const exitPromise = new Promise((resolve, reject) => {
|
|
447
|
+
serviceChild.once('error', reject);
|
|
448
|
+
serviceChild.once('exit', (code, signal) => resolve({ code, signal }));
|
|
449
|
+
});
|
|
450
|
+
try {
|
|
451
|
+
await Promise.race([
|
|
452
|
+
waitForPort(),
|
|
453
|
+
exitPromise.then(exit => {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`MIAODA_NEST_BUNDLE_EXITED_BEFORE_READY: code=${exit.code} signal=${exit.signal}`
|
|
456
|
+
);
|
|
457
|
+
}),
|
|
458
|
+
]);
|
|
459
|
+
emit('startup', startedAt, { pid: serviceChild.pid, port: serverPort });
|
|
460
|
+
return exitPromise;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
463
|
+
markBundleFallback(reason);
|
|
464
|
+
process.stderr.write(
|
|
465
|
+
`${JSON.stringify({
|
|
466
|
+
event: 'miaoda_nest_bundle_fallback',
|
|
467
|
+
reason,
|
|
468
|
+
})}\n`
|
|
469
|
+
);
|
|
470
|
+
terminate(serviceChild);
|
|
471
|
+
return runDependencyBackedMode('workspace');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function close(signal) {
|
|
476
|
+
if (stopping) return;
|
|
477
|
+
stopping = true;
|
|
478
|
+
clearTimeout(restartTimer);
|
|
479
|
+
sourceWatcher?.close();
|
|
480
|
+
terminate(compilerChild, signal);
|
|
481
|
+
terminate(serviceChild, signal);
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
terminate(compilerChild, 'SIGKILL');
|
|
484
|
+
terminate(serviceChild, 'SIGKILL');
|
|
485
|
+
}, 1_000).unref();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
489
|
+
process.once(signal, () => void close(signal));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const exit =
|
|
493
|
+
requestedMode === 'bundle-handoff'
|
|
494
|
+
? await runBundleHandoff()
|
|
495
|
+
: await runDependencyBackedMode(requestedMode);
|
|
496
|
+
process.exit(exit?.code ?? (stopping ? 0 : 1));
|