@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
package/templates/scripts/dev.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { spawn, execSync } = require('child_process');
|
|
6
|
+
const { spawn, execSync, execFileSync } = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
9
|
// ── Project root ──────────────────────────────────────────────────────────────
|
|
10
10
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
11
11
|
process.chdir(PROJECT_ROOT);
|
|
12
|
+
// Runtime scripts may be image-owned and therefore live outside the workspace.
|
|
13
|
+
// Keep the project identity explicit for validation and all supervised children.
|
|
14
|
+
process.env.MIAODA_WORKSPACE_ROOT ||= PROJECT_ROOT;
|
|
12
15
|
|
|
13
16
|
// ── Load .env ─────────────────────────────────────────────────────────────────
|
|
14
17
|
function loadEnv() {
|
|
@@ -31,13 +34,73 @@ loadEnv();
|
|
|
31
34
|
|
|
32
35
|
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
33
36
|
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
34
|
-
const MAX_RESTART_COUNT =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const MAX_RESTART_COUNT =
|
|
38
|
+
process.env.MAX_RESTART_COUNT != null && process.env.MAX_RESTART_COUNT !== ''
|
|
39
|
+
? parseInt(process.env.MAX_RESTART_COUNT, 10)
|
|
40
|
+
: Infinity;
|
|
37
41
|
const RESTART_DELAY = parseInt(process.env.RESTART_DELAY, 10) || 2;
|
|
38
42
|
const MAX_DELAY = 8;
|
|
39
43
|
const SERVER_PORT = process.env.SERVER_PORT || '3000';
|
|
40
44
|
const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8080';
|
|
45
|
+
const CACHE_HANDOFF_EXIT_CODE = 75;
|
|
46
|
+
const NEST_ORCHESTRATOR_OWNER_FILE = path.resolve(
|
|
47
|
+
process.env.MIAODA_NEST_ORCHESTRATOR_OWNER_FILE ||
|
|
48
|
+
'/tmp/event/MIAODA_NEST_ORCHESTRATOR_OWNER.json'
|
|
49
|
+
);
|
|
50
|
+
let CACHE_BOOTSTRAP_ENABLED =
|
|
51
|
+
process.env.MIAODA_CACHE_BOOTSTRAP_ENABLED === 'true';
|
|
52
|
+
let STARTUP_EXPERIMENT_ENABLED =
|
|
53
|
+
process.env.MIAODA_STARTUP_EXPERIMENT_ENABLED === 'true';
|
|
54
|
+
const CACHE_RUNTIME_SCRIPTS_ROOT = path.resolve(
|
|
55
|
+
process.env.MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT ||
|
|
56
|
+
path.join(PROJECT_ROOT, 'scripts')
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
function readExternalNestOrchestratorOwner() {
|
|
60
|
+
try {
|
|
61
|
+
const owner = JSON.parse(
|
|
62
|
+
fs.readFileSync(NEST_ORCHESTRATOR_OWNER_FILE, 'utf8')
|
|
63
|
+
);
|
|
64
|
+
const pid = Number(owner.pid);
|
|
65
|
+
if (
|
|
66
|
+
owner.schemaVersion !== 1 ||
|
|
67
|
+
owner.mode !== (process.env.MIAODA_NEST_START_MODE || 'workspace') ||
|
|
68
|
+
!Number.isSafeInteger(pid) ||
|
|
69
|
+
pid <= 1 ||
|
|
70
|
+
pid === process.pid
|
|
71
|
+
) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return { pid, mode: owner.mode };
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function dependencyHandoffAlreadyCompleted() {
|
|
82
|
+
if (!fs.existsSync(path.join(PROJECT_ROOT, 'node_modules'))) return false;
|
|
83
|
+
try {
|
|
84
|
+
const cacheRoot = path.resolve(
|
|
85
|
+
process.env.MIAODA_CACHE_ROOT ||
|
|
86
|
+
path.join(PROJECT_ROOT, '.miaoda-cache', 'current')
|
|
87
|
+
);
|
|
88
|
+
const readyFile = path.resolve(
|
|
89
|
+
process.env.MIAODA_DEPENDENCY_READY_FILE ||
|
|
90
|
+
path.join(PROJECT_ROOT, '.miaoda-runtime', 'dependencies-ready.json')
|
|
91
|
+
);
|
|
92
|
+
const generation = JSON.parse(
|
|
93
|
+
fs.readFileSync(path.join(cacheRoot, 'generation.json'), 'utf8')
|
|
94
|
+
);
|
|
95
|
+
const ready = JSON.parse(fs.readFileSync(readyFile, 'utf8'));
|
|
96
|
+
return (
|
|
97
|
+
/^[a-f0-9]{64}$/.test(generation.generationId || '') &&
|
|
98
|
+
ready.generationId === generation.generationId
|
|
99
|
+
);
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
41
104
|
|
|
42
105
|
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
43
106
|
|
|
@@ -50,11 +113,16 @@ const devLogFd = fs.openSync(devLogPath, 'a');
|
|
|
50
113
|
function timestamp() {
|
|
51
114
|
const now = new Date();
|
|
52
115
|
return (
|
|
53
|
-
now.getFullYear() +
|
|
54
|
-
|
|
55
|
-
String(now.
|
|
56
|
-
|
|
57
|
-
String(now.
|
|
116
|
+
now.getFullYear() +
|
|
117
|
+
'-' +
|
|
118
|
+
String(now.getMonth() + 1).padStart(2, '0') +
|
|
119
|
+
'-' +
|
|
120
|
+
String(now.getDate()).padStart(2, '0') +
|
|
121
|
+
' ' +
|
|
122
|
+
String(now.getHours()).padStart(2, '0') +
|
|
123
|
+
':' +
|
|
124
|
+
String(now.getMinutes()).padStart(2, '0') +
|
|
125
|
+
':' +
|
|
58
126
|
String(now.getSeconds()).padStart(2, '0')
|
|
59
127
|
);
|
|
60
128
|
}
|
|
@@ -68,21 +136,33 @@ const STDOUT_MAX_INFLIGHT = 1000;
|
|
|
68
136
|
function writeOutput(msg) {
|
|
69
137
|
// File first and synchronously — read-logs reads this file; it must never be gated
|
|
70
138
|
// by the terminal consumer.
|
|
71
|
-
try {
|
|
139
|
+
try {
|
|
140
|
+
fs.writeSync(devStdLogFd, msg);
|
|
141
|
+
} catch {}
|
|
72
142
|
// stdout mirror via async fs.write: if process.stdout is a pty/pipe whose consumer
|
|
73
143
|
// stalls, the block happens on the libuv threadpool, NOT the event loop — so the
|
|
74
144
|
// synchronous log-FILE writes above (and subsequent readline callbacks) keep running.
|
|
75
145
|
// Drop overflow when too many writes are already pending (best-effort mirror).
|
|
76
|
-
if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) {
|
|
146
|
+
if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
77
149
|
_stdoutInFlight++;
|
|
78
|
-
try {
|
|
150
|
+
try {
|
|
151
|
+
fs.write(1, msg, () => {
|
|
152
|
+
_stdoutInFlight--;
|
|
153
|
+
});
|
|
154
|
+
} catch {
|
|
155
|
+
_stdoutInFlight--;
|
|
156
|
+
}
|
|
79
157
|
}
|
|
80
158
|
|
|
81
159
|
/** Structured event log → terminal + dev.std.log + dev.log */
|
|
82
160
|
function logEvent(level, name, message) {
|
|
83
161
|
const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
|
|
84
162
|
writeOutput(msg);
|
|
85
|
-
try {
|
|
163
|
+
try {
|
|
164
|
+
fs.writeSync(devLogFd, msg);
|
|
165
|
+
} catch {}
|
|
86
166
|
}
|
|
87
167
|
|
|
88
168
|
// ── Process group management ──────────────────────────────────────────────────
|
|
@@ -94,11 +174,16 @@ function killProcessGroup(pid, signal) {
|
|
|
94
174
|
|
|
95
175
|
function killOrphansByPort(port) {
|
|
96
176
|
try {
|
|
97
|
-
const pids = execSync(`lsof -ti :${port}`, {
|
|
177
|
+
const pids = execSync(`lsof -ti :${port}`, {
|
|
178
|
+
encoding: 'utf8',
|
|
179
|
+
timeout: 5000,
|
|
180
|
+
}).trim();
|
|
98
181
|
if (pids) {
|
|
99
182
|
const pidList = pids.split('\n').filter(Boolean);
|
|
100
183
|
for (const p of pidList) {
|
|
101
|
-
try {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(parseInt(p, 10), 'SIGKILL');
|
|
186
|
+
} catch {}
|
|
102
187
|
}
|
|
103
188
|
return pidList;
|
|
104
189
|
}
|
|
@@ -111,16 +196,23 @@ let stopping = false;
|
|
|
111
196
|
const managedProcesses = []; // { name, pid, child }
|
|
112
197
|
|
|
113
198
|
function sleep(ms) {
|
|
114
|
-
return new Promise(
|
|
199
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
115
200
|
}
|
|
116
201
|
|
|
117
202
|
/**
|
|
118
203
|
* Start and supervise a process with auto-restart and log piping.
|
|
119
204
|
* Returns a promise that resolves when the process loop ends.
|
|
120
205
|
*/
|
|
121
|
-
function startProcess({
|
|
206
|
+
function startProcess({
|
|
207
|
+
name,
|
|
208
|
+
command,
|
|
209
|
+
args,
|
|
210
|
+
cleanupPort,
|
|
211
|
+
stopRestartOnExitCodes = [],
|
|
212
|
+
}) {
|
|
122
213
|
const logFilePath = path.join(LOG_DIR, `${name}.std.log`);
|
|
123
214
|
const logFd = fs.openSync(logFilePath, 'a');
|
|
215
|
+
const logicalLogFds = new Map();
|
|
124
216
|
|
|
125
217
|
const entry = { name, pid: null, child: null };
|
|
126
218
|
managedProcesses.push(entry);
|
|
@@ -141,14 +233,39 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
141
233
|
entry.child = child;
|
|
142
234
|
|
|
143
235
|
const startTime = Date.now();
|
|
144
|
-
logEvent(
|
|
236
|
+
logEvent(
|
|
237
|
+
'INFO',
|
|
238
|
+
name,
|
|
239
|
+
`Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`
|
|
240
|
+
);
|
|
145
241
|
|
|
146
242
|
// Pipe stdout and stderr through readline for timestamped logging
|
|
147
|
-
const pipeLines =
|
|
148
|
-
const rl = readline.createInterface({
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
243
|
+
const pipeLines = stream => {
|
|
244
|
+
const rl = readline.createInterface({
|
|
245
|
+
input: stream,
|
|
246
|
+
crlfDelay: Infinity,
|
|
247
|
+
});
|
|
248
|
+
rl.on('line', line => {
|
|
249
|
+
const tagged = /^MIAODA_LOG\t(client|server)\t(.*)$/.exec(line);
|
|
250
|
+
const logicalName = tagged?.[1] || name;
|
|
251
|
+
const logicalLine = tagged?.[2] ?? line;
|
|
252
|
+
const msg = `[${timestamp()}] [${logicalName}] ${logicalLine}\n`;
|
|
253
|
+
try {
|
|
254
|
+
fs.writeSync(logFd, msg);
|
|
255
|
+
} catch {}
|
|
256
|
+
if (tagged) {
|
|
257
|
+
try {
|
|
258
|
+
let logicalFd = logicalLogFds.get(logicalName);
|
|
259
|
+
if (logicalFd === undefined) {
|
|
260
|
+
logicalFd = fs.openSync(
|
|
261
|
+
path.join(LOG_DIR, `${logicalName}.std.log`),
|
|
262
|
+
'a'
|
|
263
|
+
);
|
|
264
|
+
logicalLogFds.set(logicalName, logicalFd);
|
|
265
|
+
}
|
|
266
|
+
fs.writeSync(logicalFd, msg);
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
152
269
|
writeOutput(msg);
|
|
153
270
|
});
|
|
154
271
|
};
|
|
@@ -159,15 +276,16 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
159
276
|
// NOTE: must use 'exit', not 'close'. With shell:true, grandchild processes
|
|
160
277
|
// (e.g. nest's server) inherit stdout pipes. 'close' won't fire until ALL
|
|
161
278
|
// pipe holders exit, causing dev.js to hang when npm/nest dies but server survives.
|
|
162
|
-
const exitCode = await new Promise(
|
|
163
|
-
child.on('exit',
|
|
279
|
+
const exitCode = await new Promise(resolve => {
|
|
280
|
+
child.on('exit', code => resolve(code ?? 1));
|
|
164
281
|
child.on('error', () => resolve(1));
|
|
165
282
|
});
|
|
283
|
+
const completedHandoff = stopRestartOnExitCodes.includes(exitCode);
|
|
166
284
|
|
|
167
285
|
// Kill the entire process group
|
|
168
286
|
if (entry.pid) {
|
|
169
287
|
killProcessGroup(entry.pid, 'SIGTERM');
|
|
170
|
-
await sleep(2000);
|
|
288
|
+
await sleep(completedHandoff ? 100 : 2000);
|
|
171
289
|
killProcessGroup(entry.pid, 'SIGKILL');
|
|
172
290
|
}
|
|
173
291
|
entry.pid = null;
|
|
@@ -177,31 +295,74 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
177
295
|
if (cleanupPort) {
|
|
178
296
|
const orphans = killOrphansByPort(cleanupPort);
|
|
179
297
|
if (orphans.length > 0) {
|
|
180
|
-
logEvent(
|
|
298
|
+
logEvent(
|
|
299
|
+
'WARN',
|
|
300
|
+
name,
|
|
301
|
+
`Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`
|
|
302
|
+
);
|
|
181
303
|
await sleep(500);
|
|
182
304
|
}
|
|
183
305
|
}
|
|
184
306
|
|
|
185
307
|
if (stopping) break;
|
|
186
308
|
|
|
309
|
+
if (completedHandoff) {
|
|
310
|
+
logEvent(
|
|
311
|
+
'INFO',
|
|
312
|
+
name,
|
|
313
|
+
`Process completed one-way handoff with code ${exitCode}`
|
|
314
|
+
);
|
|
315
|
+
try {
|
|
316
|
+
fs.closeSync(logFd);
|
|
317
|
+
} catch {}
|
|
318
|
+
for (const logicalFd of logicalLogFds.values()) {
|
|
319
|
+
try {
|
|
320
|
+
fs.closeSync(logicalFd);
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
return exitCode;
|
|
324
|
+
}
|
|
325
|
+
|
|
187
326
|
const runDuration = (Date.now() - startTime) / 1000;
|
|
188
327
|
if (runDuration >= 60) {
|
|
189
328
|
restartCount = 0;
|
|
190
|
-
logEvent(
|
|
329
|
+
logEvent(
|
|
330
|
+
'INFO',
|
|
331
|
+
name,
|
|
332
|
+
`Ran for ${Math.round(runDuration)}s, resetting restart counter`
|
|
333
|
+
);
|
|
191
334
|
} else {
|
|
192
335
|
restartCount++;
|
|
193
336
|
}
|
|
194
337
|
if (restartCount >= MAX_RESTART_COUNT) {
|
|
195
|
-
logEvent(
|
|
338
|
+
logEvent(
|
|
339
|
+
'ERROR',
|
|
340
|
+
name,
|
|
341
|
+
`Max restart count (${MAX_RESTART_COUNT}) reached, giving up`
|
|
342
|
+
);
|
|
196
343
|
break;
|
|
197
344
|
}
|
|
198
345
|
|
|
199
|
-
const delay = Math.min(
|
|
200
|
-
|
|
346
|
+
const delay = Math.min(
|
|
347
|
+
RESTART_DELAY * (1 << Math.max(0, restartCount - 1)),
|
|
348
|
+
MAX_DELAY
|
|
349
|
+
);
|
|
350
|
+
logEvent(
|
|
351
|
+
'WARN',
|
|
352
|
+
name,
|
|
353
|
+
`Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`
|
|
354
|
+
);
|
|
201
355
|
await sleep(delay * 1000);
|
|
202
356
|
}
|
|
203
357
|
|
|
204
|
-
try {
|
|
358
|
+
try {
|
|
359
|
+
fs.closeSync(logFd);
|
|
360
|
+
} catch {}
|
|
361
|
+
for (const logicalFd of logicalLogFds.values()) {
|
|
362
|
+
try {
|
|
363
|
+
fs.closeSync(logicalFd);
|
|
364
|
+
} catch {}
|
|
365
|
+
}
|
|
205
366
|
};
|
|
206
367
|
|
|
207
368
|
return run();
|
|
@@ -231,7 +392,11 @@ async function cleanup() {
|
|
|
231
392
|
// Force kill any remaining
|
|
232
393
|
for (const entry of managedProcesses) {
|
|
233
394
|
if (entry.pid) {
|
|
234
|
-
logEvent(
|
|
395
|
+
logEvent(
|
|
396
|
+
'WARN',
|
|
397
|
+
'main',
|
|
398
|
+
`Force killing process group (PGID: ${entry.pid})`
|
|
399
|
+
);
|
|
235
400
|
killProcessGroup(entry.pid, 'SIGKILL');
|
|
236
401
|
}
|
|
237
402
|
}
|
|
@@ -242,8 +407,12 @@ async function cleanup() {
|
|
|
242
407
|
|
|
243
408
|
logEvent('INFO', 'main', 'All processes stopped');
|
|
244
409
|
|
|
245
|
-
try {
|
|
246
|
-
|
|
410
|
+
try {
|
|
411
|
+
fs.closeSync(devStdLogFd);
|
|
412
|
+
} catch {}
|
|
413
|
+
try {
|
|
414
|
+
fs.closeSync(devLogFd);
|
|
415
|
+
} catch {}
|
|
247
416
|
|
|
248
417
|
process.exit(0);
|
|
249
418
|
}
|
|
@@ -261,48 +430,253 @@ function cleanStaleDist() {
|
|
|
261
430
|
}
|
|
262
431
|
}
|
|
263
432
|
|
|
433
|
+
async function followExternalNestOrchestratorOrStartLocal(
|
|
434
|
+
serverStartupRuntimeScript
|
|
435
|
+
) {
|
|
436
|
+
const owner = readExternalNestOrchestratorOwner();
|
|
437
|
+
if (!owner) {
|
|
438
|
+
return startProcess({
|
|
439
|
+
name: 'server',
|
|
440
|
+
command: process.execPath,
|
|
441
|
+
args: [serverStartupRuntimeScript],
|
|
442
|
+
cleanupPort: SERVER_PORT,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
logEvent(
|
|
447
|
+
'INFO',
|
|
448
|
+
'server',
|
|
449
|
+
`Following image-owned Nest orchestrator pid=${owner.pid} mode=${owner.mode}`
|
|
450
|
+
);
|
|
451
|
+
while (!stopping) {
|
|
452
|
+
try {
|
|
453
|
+
process.kill(owner.pid, 0);
|
|
454
|
+
await sleep(200);
|
|
455
|
+
} catch {
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (stopping) return 0;
|
|
460
|
+
|
|
461
|
+
// If the image-owned orchestrator exits, resume the normal supervised
|
|
462
|
+
// workspace service. By this point node_modules is expected to be ready; if
|
|
463
|
+
// it is not, server-startup-runtime keeps the original bounded wait.
|
|
464
|
+
logEvent(
|
|
465
|
+
'WARN',
|
|
466
|
+
'server',
|
|
467
|
+
`Image-owned Nest orchestrator pid=${owner.pid} exited; starting local supervisor`
|
|
468
|
+
);
|
|
469
|
+
return startProcess({
|
|
470
|
+
name: 'server',
|
|
471
|
+
command: process.execPath,
|
|
472
|
+
args: [serverStartupRuntimeScript],
|
|
473
|
+
cleanupPort: SERVER_PORT,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
264
477
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
265
478
|
async function main() {
|
|
266
479
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
267
480
|
|
|
268
|
-
|
|
481
|
+
if (process.env.MIAODA_HMR_ROUTING_MODE === 'same-origin') {
|
|
482
|
+
delete process.env.MIAODA_WS_HOST;
|
|
483
|
+
logEvent(
|
|
484
|
+
'INFO',
|
|
485
|
+
'main',
|
|
486
|
+
'Using same-origin HMR routing; ignored the dedicated MIAODA_WS_HOST'
|
|
487
|
+
);
|
|
488
|
+
}
|
|
269
489
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
490
|
+
const coordinatorRuntimeScript = path.join(
|
|
491
|
+
CACHE_RUNTIME_SCRIPTS_ROOT,
|
|
492
|
+
'cache-runtime-coordinator.mjs'
|
|
493
|
+
);
|
|
494
|
+
const serverStartupRuntimeScript = path.join(
|
|
495
|
+
CACHE_RUNTIME_SCRIPTS_ROOT,
|
|
496
|
+
'server-startup-runtime.mjs'
|
|
497
|
+
);
|
|
498
|
+
const workspaceClientRuntimeScript = path.join(
|
|
499
|
+
CACHE_RUNTIME_SCRIPTS_ROOT,
|
|
500
|
+
'workspace-client-runtime.mjs'
|
|
501
|
+
);
|
|
502
|
+
if (
|
|
503
|
+
CACHE_BOOTSTRAP_ENABLED &&
|
|
504
|
+
!STARTUP_EXPERIMENT_ENABLED &&
|
|
505
|
+
dependencyHandoffAlreadyCompleted()
|
|
506
|
+
) {
|
|
507
|
+
CACHE_BOOTSTRAP_ENABLED = false;
|
|
508
|
+
process.env.MIAODA_CACHE_BOOTSTRAP_ENABLED = 'false';
|
|
509
|
+
logEvent(
|
|
510
|
+
'INFO',
|
|
511
|
+
'main',
|
|
512
|
+
'Dependency handoff already completed; starting original services directly'
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (STARTUP_EXPERIMENT_ENABLED) {
|
|
516
|
+
try {
|
|
517
|
+
for (const requiredPath of [
|
|
518
|
+
serverStartupRuntimeScript,
|
|
519
|
+
workspaceClientRuntimeScript,
|
|
520
|
+
]) {
|
|
521
|
+
if (!fs.existsSync(requiredPath)) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`MIAODA_STARTUP_EXPERIMENT_INPUT_MISSING: ${requiredPath}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
STARTUP_EXPERIMENT_ENABLED = false;
|
|
529
|
+
process.env.MIAODA_STARTUP_EXPERIMENT_ENABLED = 'false';
|
|
530
|
+
logEvent(
|
|
531
|
+
'WARN',
|
|
532
|
+
'main',
|
|
533
|
+
`Startup experiment unavailable; using original services: ${error.message}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (CACHE_BOOTSTRAP_ENABLED && !STARTUP_EXPERIMENT_ENABLED) {
|
|
538
|
+
process.env.MIAODA_CACHE_VALIDATION_RECEIPT_FILE ||= path.join(
|
|
539
|
+
PROJECT_ROOT,
|
|
540
|
+
'.miaoda-runtime',
|
|
541
|
+
'cache-validation-receipt.json'
|
|
542
|
+
);
|
|
543
|
+
try {
|
|
544
|
+
for (const requiredPath of [coordinatorRuntimeScript]) {
|
|
545
|
+
if (!fs.existsSync(requiredPath)) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`MIAODA_CACHE_BOOTSTRAP_INPUT_MISSING: ${requiredPath}`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
execFileSync(
|
|
552
|
+
process.execPath,
|
|
553
|
+
[coordinatorRuntimeScript, '--validate-only'],
|
|
554
|
+
{
|
|
555
|
+
cwd: PROJECT_ROOT,
|
|
556
|
+
env: { ...process.env },
|
|
557
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
CACHE_BOOTSTRAP_ENABLED = false;
|
|
562
|
+
const reason = error?.stderr?.toString().trim() || error.message;
|
|
563
|
+
logEvent(
|
|
564
|
+
'WARN',
|
|
565
|
+
'main',
|
|
566
|
+
`Cache generation preflight rejected; using dependency-backed startup: ${reason}`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
277
569
|
}
|
|
278
570
|
|
|
279
|
-
|
|
280
|
-
const serverPromise = startProcess({
|
|
281
|
-
name: 'server',
|
|
282
|
-
command: 'npm',
|
|
283
|
-
args: ['run', 'dev:server'],
|
|
284
|
-
cleanupPort: SERVER_PORT,
|
|
285
|
-
});
|
|
571
|
+
cleanStaleDist();
|
|
286
572
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
573
|
+
const initializeActionPlugins = () => {
|
|
574
|
+
writeOutput('\n🔌 Initializing action plugins...\n');
|
|
575
|
+
try {
|
|
576
|
+
execSync('fullstack-cli action-plugin init', {
|
|
577
|
+
cwd: PROJECT_ROOT,
|
|
578
|
+
stdio: 'inherit',
|
|
579
|
+
});
|
|
580
|
+
writeOutput('✅ Action plugins initialized\n\n');
|
|
581
|
+
} catch {
|
|
582
|
+
writeOutput(
|
|
583
|
+
'⚠️ Action plugin initialization failed, continuing anyway...\n\n'
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// The transition runtime resolves Action Plugin dependencies from the sealed
|
|
589
|
+
// dependency cache. Running the project CLI here would reintroduce a project
|
|
590
|
+
// node_modules dependency before restoration completes.
|
|
591
|
+
if (!CACHE_BOOTSTRAP_ENABLED && !STARTUP_EXPERIMENT_ENABLED) {
|
|
592
|
+
initializeActionPlugins();
|
|
593
|
+
} else if (!STARTUP_EXPERIMENT_ENABLED) {
|
|
594
|
+
logEvent(
|
|
595
|
+
'INFO',
|
|
596
|
+
'main',
|
|
597
|
+
'Skipped dynamic Action Plugin initialization in cache-only mode'
|
|
598
|
+
);
|
|
599
|
+
}
|
|
293
600
|
|
|
294
601
|
writeOutput(`📋 Dev processes running. Press Ctrl+C to stop.\n`);
|
|
295
602
|
writeOutput(`📄 Logs: ${devStdLogPath}\n\n`);
|
|
296
603
|
|
|
297
|
-
|
|
298
|
-
|
|
604
|
+
if (STARTUP_EXPERIMENT_ENABLED) {
|
|
605
|
+
logEvent(
|
|
606
|
+
'INFO',
|
|
607
|
+
'main',
|
|
608
|
+
`Starting normal workspace Vite with Nest mode ${
|
|
609
|
+
process.env.MIAODA_NEST_START_MODE || 'workspace'
|
|
610
|
+
}`
|
|
611
|
+
);
|
|
612
|
+
const serverPromise =
|
|
613
|
+
(process.env.MIAODA_NEST_START_MODE || 'workspace') ===
|
|
614
|
+
'bundle-handoff'
|
|
615
|
+
? followExternalNestOrchestratorOrStartLocal(
|
|
616
|
+
serverStartupRuntimeScript
|
|
617
|
+
)
|
|
618
|
+
: startProcess({
|
|
619
|
+
name: 'server',
|
|
620
|
+
command: process.execPath,
|
|
621
|
+
args: [serverStartupRuntimeScript],
|
|
622
|
+
cleanupPort: SERVER_PORT,
|
|
623
|
+
});
|
|
624
|
+
const clientPromise = startProcess({
|
|
625
|
+
name: 'client',
|
|
626
|
+
command: process.execPath,
|
|
627
|
+
args: [workspaceClientRuntimeScript],
|
|
628
|
+
cleanupPort: CLIENT_DEV_PORT,
|
|
629
|
+
});
|
|
630
|
+
await Promise.all([serverPromise, clientPromise]);
|
|
631
|
+
} else if (CACHE_BOOTSTRAP_ENABLED) {
|
|
632
|
+
const exitCode = await startProcess({
|
|
633
|
+
name: 'coordinator',
|
|
634
|
+
command: process.execPath,
|
|
635
|
+
args: [coordinatorRuntimeScript],
|
|
636
|
+
stopRestartOnExitCodes: [CACHE_HANDOFF_EXIT_CODE],
|
|
637
|
+
});
|
|
638
|
+
if (exitCode === CACHE_HANDOFF_EXIT_CODE && !stopping) {
|
|
639
|
+
CACHE_BOOTSTRAP_ENABLED = false;
|
|
640
|
+
process.env.MIAODA_CACHE_BOOTSTRAP_ENABLED = 'false';
|
|
641
|
+
process.env.NODE_PATH = '';
|
|
642
|
+
logEvent(
|
|
643
|
+
'INFO',
|
|
644
|
+
'main',
|
|
645
|
+
'Dependencies restored; cache runtime retired before original services start'
|
|
646
|
+
);
|
|
647
|
+
initializeActionPlugins();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (
|
|
652
|
+
!STARTUP_EXPERIMENT_ENABLED &&
|
|
653
|
+
!CACHE_BOOTSTRAP_ENABLED &&
|
|
654
|
+
!stopping
|
|
655
|
+
) {
|
|
656
|
+
// Feature-off and post-cache handoff keep the original process boundary
|
|
657
|
+
// and exact npm commands. Failures therefore surface through the original
|
|
658
|
+
// Preview starting/failed/retry UI instead of a cached-page fallback.
|
|
659
|
+
const serverPromise = startProcess({
|
|
660
|
+
name: 'server',
|
|
661
|
+
command: 'npm',
|
|
662
|
+
args: ['run', 'dev:server'],
|
|
663
|
+
cleanupPort: SERVER_PORT,
|
|
664
|
+
});
|
|
665
|
+
const clientPromise = startProcess({
|
|
666
|
+
name: 'client',
|
|
667
|
+
command: 'npm',
|
|
668
|
+
args: ['run', 'dev:client'],
|
|
669
|
+
cleanupPort: CLIENT_DEV_PORT,
|
|
670
|
+
});
|
|
671
|
+
await Promise.all([serverPromise, clientPromise]);
|
|
672
|
+
}
|
|
299
673
|
|
|
300
674
|
if (!cleanupDone) {
|
|
301
675
|
await cleanup();
|
|
302
676
|
}
|
|
303
677
|
}
|
|
304
678
|
|
|
305
|
-
main().catch(
|
|
679
|
+
main().catch(err => {
|
|
306
680
|
console.error('Fatal error:', err);
|
|
307
681
|
process.exit(1);
|
|
308
682
|
});
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
# 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
export MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT="${MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT:-$SCRIPT_DIR}"
|
|
11
|
+
export MIAODA_WORKSPACE_ROOT="${MIAODA_WORKSPACE_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
10
12
|
|
|
11
13
|
if [ -n "${SANDBOX_ID:-}" ]; then
|
|
12
14
|
exec node "$SCRIPT_DIR/dev.js" "$@"
|