@lark-apaas/coding-template-nestjs-react-fullstack 0.1.27-alpha.20260824122815 → 0.1.28-alpha.20260824161528
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/package.json +1 -1
- package/template/package.json +2 -2
- package/template/scripts/dev.js +60 -402
- package/template/scripts/dev.sh +6 -12
- package/template/scripts/cache-generation-preflight.mjs +0 -1346
- package/template/scripts/cache-generation-prune.mjs +0 -174
- package/template/scripts/cache-runtime-coordinator.mjs +0 -482
- package/template/scripts/patch-nest-cli-startup-instrumentation.mjs +0 -376
- package/template/scripts/patch-vite-dependency-graph-hash.mjs +0 -141
- package/template/scripts/server-cache-runtime.mjs +0 -865
- package/template/scripts/server-startup-runtime.mjs +0 -445
- package/template/scripts/vite-cache-runtime.mjs +0 -3098
- package/template/scripts/vite-runtime.config.mjs +0 -420
- package/template/scripts/workspace-client-runtime.mjs +0 -466
package/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"lint": "node ./scripts/lint.js",
|
|
31
31
|
"format": "prettier --write \"{server,client,test}/**/*.{js,ts,tsx,json,md}\"",
|
|
32
32
|
"precommit": "node scripts/hooks/run-precommit.js",
|
|
33
|
-
"upgrade": "npx -y @lark-apaas/fullstack-cli@1.1.62-alpha.
|
|
33
|
+
"upgrade": "npx -y @lark-apaas/fullstack-cli@1.1.62-alpha.20260824155956 sync --disable-gen-openapi"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@lark-apaas/fullstack-nestjs-core": "^1.1.59",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@heroicons/react": "^2.2.0",
|
|
64
64
|
"@hookform/resolvers": "^5.2.2",
|
|
65
65
|
"@lark-apaas/client-toolkit": "^1.2.63",
|
|
66
|
-
"@lark-apaas/coding-preset-vite-react": "1.0.19-alpha.
|
|
66
|
+
"@lark-apaas/coding-preset-vite-react": "1.0.19-alpha.20260824155954",
|
|
67
67
|
"@lark-apaas/fullstack-presets": "^1.1.0",
|
|
68
68
|
"@nestjs/cli": "^10.4.9",
|
|
69
69
|
"@nestjs/testing": "^10.4.20",
|
package/template/scripts/dev.js
CHANGED
|
@@ -9,9 +9,6 @@ const readline = require('readline');
|
|
|
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;
|
|
15
12
|
|
|
16
13
|
// ── Load .env ─────────────────────────────────────────────────────────────────
|
|
17
14
|
function loadEnv() {
|
|
@@ -34,143 +31,15 @@ loadEnv();
|
|
|
34
31
|
|
|
35
32
|
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
36
33
|
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
37
|
-
const MAX_RESTART_COUNT =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
: Infinity;
|
|
34
|
+
const MAX_RESTART_COUNT = process.env.MAX_RESTART_COUNT != null && process.env.MAX_RESTART_COUNT !== ''
|
|
35
|
+
? parseInt(process.env.MAX_RESTART_COUNT, 10)
|
|
36
|
+
: Infinity;
|
|
41
37
|
const RESTART_DELAY = parseInt(process.env.RESTART_DELAY, 10) || 2;
|
|
42
38
|
const MAX_DELAY = 8;
|
|
43
39
|
const SERVER_PORT = process.env.SERVER_PORT || '3000';
|
|
44
40
|
const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8080';
|
|
45
|
-
const WORKSPACE_READY_FILE = path.resolve(
|
|
46
|
-
process.env.MIAODA_WORKSPACE_READY_FILE || '/tmp/event/WORKSPACE_READY'
|
|
47
|
-
);
|
|
48
|
-
const ACTION_PLUGIN_READY_FILE = path.resolve(
|
|
49
|
-
process.env.MIAODA_ACTION_PLUGIN_READY_FILE ||
|
|
50
|
-
'/tmp/event/MIAODA_ACTION_PLUGIN_READY.json'
|
|
51
|
-
);
|
|
52
|
-
const NEST_ORCHESTRATOR_OWNER_FILE = path.resolve(
|
|
53
|
-
process.env.MIAODA_NEST_ORCHESTRATOR_OWNER_FILE ||
|
|
54
|
-
'/tmp/event/MIAODA_NEST_ORCHESTRATOR_OWNER.json'
|
|
55
|
-
);
|
|
56
|
-
let STARTUP_EXPERIMENT_ENABLED =
|
|
57
|
-
process.env.MIAODA_STARTUP_EXPERIMENT_ENABLED === 'true';
|
|
58
|
-
const STARTUP_RUNTIME_SCRIPTS_ROOT = path.resolve(
|
|
59
|
-
process.env.MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT ||
|
|
60
|
-
path.join(PROJECT_ROOT, 'scripts')
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// When the image launched a bundle orchestrator before this project process
|
|
64
|
-
// existed, it owns the Nest lifecycle and records itself here. dev.js then
|
|
65
|
-
// follows that owner instead of competing for the public server port.
|
|
66
|
-
function readExternalNestOrchestratorOwner() {
|
|
67
|
-
try {
|
|
68
|
-
const owner = JSON.parse(
|
|
69
|
-
fs.readFileSync(NEST_ORCHESTRATOR_OWNER_FILE, 'utf8')
|
|
70
|
-
);
|
|
71
|
-
const pid = Number(owner.pid);
|
|
72
|
-
if (
|
|
73
|
-
owner.schemaVersion !== 1 ||
|
|
74
|
-
owner.mode !== (process.env.MIAODA_NEST_START_MODE || 'workspace') ||
|
|
75
|
-
!Number.isSafeInteger(pid) ||
|
|
76
|
-
pid <= 1 ||
|
|
77
|
-
pid === process.pid
|
|
78
|
-
) {
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
process.kill(pid, 0);
|
|
82
|
-
return { pid, mode: owner.mode };
|
|
83
|
-
} catch {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function workspaceDependenciesReady() {
|
|
89
|
-
try {
|
|
90
|
-
return (
|
|
91
|
-
fs.statSync(WORKSPACE_READY_FILE).isFile() &&
|
|
92
|
-
fs.statSync(path.join(PROJECT_ROOT, 'node_modules')).isDirectory()
|
|
93
|
-
);
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function waitForWorkspaceDependencies() {
|
|
100
|
-
while (!stopping && !workspaceDependenciesReady()) await sleep(100);
|
|
101
|
-
if (stopping) throw new Error('MIAODA_ACTION_PLUGIN_STARTUP_STOPPING');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function publishActionPluginReady(status, startedAtEpochMs) {
|
|
105
|
-
const atEpochMs = Date.now();
|
|
106
|
-
let restoreEpochMs = process.env.MIAODA_PLATFORM_ENV_NOT_BEFORE_MS || '';
|
|
107
|
-
if (!restoreEpochMs) {
|
|
108
|
-
try {
|
|
109
|
-
const dependencyReceipt = JSON.parse(
|
|
110
|
-
fs.readFileSync(
|
|
111
|
-
path.join(PROJECT_ROOT, '.miaoda-runtime', 'dependencies-ready.json'),
|
|
112
|
-
'utf8'
|
|
113
|
-
)
|
|
114
|
-
);
|
|
115
|
-
restoreEpochMs = String(dependencyReceipt.restoreEpochMs || '');
|
|
116
|
-
} catch {}
|
|
117
|
-
}
|
|
118
|
-
if (restoreEpochMs) {
|
|
119
|
-
process.env.MIAODA_PLATFORM_ENV_NOT_BEFORE_MS = restoreEpochMs;
|
|
120
|
-
}
|
|
121
|
-
const marker = {
|
|
122
|
-
schemaVersion: 1,
|
|
123
|
-
restoreEpochMs,
|
|
124
|
-
status,
|
|
125
|
-
atEpochMs,
|
|
126
|
-
};
|
|
127
|
-
fs.mkdirSync(path.dirname(ACTION_PLUGIN_READY_FILE), { recursive: true });
|
|
128
|
-
const temporaryFile = `${ACTION_PLUGIN_READY_FILE}.${process.pid}.tmp`;
|
|
129
|
-
fs.writeFileSync(temporaryFile, `${JSON.stringify(marker)}\n`, {
|
|
130
|
-
mode: 0o600,
|
|
131
|
-
});
|
|
132
|
-
fs.renameSync(temporaryFile, ACTION_PLUGIN_READY_FILE);
|
|
133
|
-
process.env.MIAODA_NEST_SKIP_ACTION_PLUGIN_INIT = 'true';
|
|
134
|
-
writeOutput(
|
|
135
|
-
`${JSON.stringify({
|
|
136
|
-
event: 'miaoda_workspace_runtime_phase',
|
|
137
|
-
runtime: 'shared',
|
|
138
|
-
mode: process.env.MIAODA_NEST_START_MODE || 'workspace',
|
|
139
|
-
phase: 'action_plugin_init',
|
|
140
|
-
phaseStartedAtEpochMs: startedAtEpochMs,
|
|
141
|
-
atEpochMs,
|
|
142
|
-
durationMs: atEpochMs - startedAtEpochMs,
|
|
143
|
-
status,
|
|
144
|
-
readyFile: ACTION_PLUGIN_READY_FILE,
|
|
145
|
-
})}\n`
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function initializeActionPlugins(publishReady = false) {
|
|
150
|
-
const startedAtEpochMs = Date.now();
|
|
151
|
-
let status = 'success';
|
|
152
|
-
writeOutput('\n🔌 Initializing action plugins...\n');
|
|
153
|
-
try {
|
|
154
|
-
execSync('fullstack-cli action-plugin init', {
|
|
155
|
-
cwd: PROJECT_ROOT,
|
|
156
|
-
stdio: 'inherit',
|
|
157
|
-
});
|
|
158
|
-
writeOutput('✅ Action plugins initialized\n\n');
|
|
159
|
-
} catch {
|
|
160
|
-
status = 'failed-continued';
|
|
161
|
-
writeOutput(
|
|
162
|
-
'⚠️ Action plugin initialization failed, continuing anyway...\n\n'
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
if (publishReady) publishActionPluginReady(status, startedAtEpochMs);
|
|
166
|
-
}
|
|
167
41
|
|
|
168
42
|
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
169
|
-
process.once('exit', () => {
|
|
170
|
-
try {
|
|
171
|
-
fs.rmSync(ACTION_PLUGIN_READY_FILE, { force: true });
|
|
172
|
-
} catch {}
|
|
173
|
-
});
|
|
174
43
|
|
|
175
44
|
// ── Logging infrastructure ────────────────────────────────────────────────────
|
|
176
45
|
const devStdLogPath = path.join(LOG_DIR, 'dev.std.log');
|
|
@@ -181,16 +50,11 @@ const devLogFd = fs.openSync(devLogPath, 'a');
|
|
|
181
50
|
function timestamp() {
|
|
182
51
|
const now = new Date();
|
|
183
52
|
return (
|
|
184
|
-
now.getFullYear() +
|
|
185
|
-
'-' +
|
|
186
|
-
String(now.
|
|
187
|
-
'
|
|
188
|
-
String(now.
|
|
189
|
-
' ' +
|
|
190
|
-
String(now.getHours()).padStart(2, '0') +
|
|
191
|
-
':' +
|
|
192
|
-
String(now.getMinutes()).padStart(2, '0') +
|
|
193
|
-
':' +
|
|
53
|
+
now.getFullYear() + '-' +
|
|
54
|
+
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
|
55
|
+
String(now.getDate()).padStart(2, '0') + ' ' +
|
|
56
|
+
String(now.getHours()).padStart(2, '0') + ':' +
|
|
57
|
+
String(now.getMinutes()).padStart(2, '0') + ':' +
|
|
194
58
|
String(now.getSeconds()).padStart(2, '0')
|
|
195
59
|
);
|
|
196
60
|
}
|
|
@@ -204,33 +68,21 @@ const STDOUT_MAX_INFLIGHT = 1000;
|
|
|
204
68
|
function writeOutput(msg) {
|
|
205
69
|
// File first and synchronously — read-logs reads this file; it must never be gated
|
|
206
70
|
// by the terminal consumer.
|
|
207
|
-
try {
|
|
208
|
-
fs.writeSync(devStdLogFd, msg);
|
|
209
|
-
} catch {}
|
|
71
|
+
try { fs.writeSync(devStdLogFd, msg); } catch {}
|
|
210
72
|
// stdout mirror via async fs.write: if process.stdout is a pty/pipe whose consumer
|
|
211
73
|
// stalls, the block happens on the libuv threadpool, NOT the event loop — so the
|
|
212
74
|
// synchronous log-FILE writes above (and subsequent readline callbacks) keep running.
|
|
213
75
|
// Drop overflow when too many writes are already pending (best-effort mirror).
|
|
214
|
-
if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
76
|
+
if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) { return; }
|
|
217
77
|
_stdoutInFlight++;
|
|
218
|
-
try {
|
|
219
|
-
fs.write(1, msg, () => {
|
|
220
|
-
_stdoutInFlight--;
|
|
221
|
-
});
|
|
222
|
-
} catch {
|
|
223
|
-
_stdoutInFlight--;
|
|
224
|
-
}
|
|
78
|
+
try { fs.write(1, msg, () => { _stdoutInFlight--; }); } catch { _stdoutInFlight--; }
|
|
225
79
|
}
|
|
226
80
|
|
|
227
81
|
/** Structured event log → terminal + dev.std.log + dev.log */
|
|
228
82
|
function logEvent(level, name, message) {
|
|
229
83
|
const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
|
|
230
84
|
writeOutput(msg);
|
|
231
|
-
try {
|
|
232
|
-
fs.writeSync(devLogFd, msg);
|
|
233
|
-
} catch {}
|
|
85
|
+
try { fs.writeSync(devLogFd, msg); } catch {}
|
|
234
86
|
}
|
|
235
87
|
|
|
236
88
|
// ── Process group management ──────────────────────────────────────────────────
|
|
@@ -242,16 +94,11 @@ function killProcessGroup(pid, signal) {
|
|
|
242
94
|
|
|
243
95
|
function killOrphansByPort(port) {
|
|
244
96
|
try {
|
|
245
|
-
const pids = execSync(`lsof -ti :${port}`, {
|
|
246
|
-
encoding: 'utf8',
|
|
247
|
-
timeout: 5000,
|
|
248
|
-
}).trim();
|
|
97
|
+
const pids = execSync(`lsof -ti :${port}`, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
249
98
|
if (pids) {
|
|
250
99
|
const pidList = pids.split('\n').filter(Boolean);
|
|
251
100
|
for (const p of pidList) {
|
|
252
|
-
try {
|
|
253
|
-
process.kill(parseInt(p, 10), 'SIGKILL');
|
|
254
|
-
} catch {}
|
|
101
|
+
try { process.kill(parseInt(p, 10), 'SIGKILL'); } catch {}
|
|
255
102
|
}
|
|
256
103
|
return pidList;
|
|
257
104
|
}
|
|
@@ -264,7 +111,7 @@ let stopping = false;
|
|
|
264
111
|
const managedProcesses = []; // { name, pid, child }
|
|
265
112
|
|
|
266
113
|
function sleep(ms) {
|
|
267
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
114
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
268
115
|
}
|
|
269
116
|
|
|
270
117
|
/**
|
|
@@ -294,23 +141,14 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
294
141
|
entry.child = child;
|
|
295
142
|
|
|
296
143
|
const startTime = Date.now();
|
|
297
|
-
logEvent(
|
|
298
|
-
'INFO',
|
|
299
|
-
name,
|
|
300
|
-
`Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`
|
|
301
|
-
);
|
|
144
|
+
logEvent('INFO', name, `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`);
|
|
302
145
|
|
|
303
146
|
// Pipe stdout and stderr through readline for timestamped logging
|
|
304
|
-
const pipeLines = stream => {
|
|
305
|
-
const rl = readline.createInterface({
|
|
306
|
-
|
|
307
|
-
crlfDelay: Infinity,
|
|
308
|
-
});
|
|
309
|
-
rl.on('line', line => {
|
|
147
|
+
const pipeLines = (stream) => {
|
|
148
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
149
|
+
rl.on('line', (line) => {
|
|
310
150
|
const msg = `[${timestamp()}] [${name}] ${line}\n`;
|
|
311
|
-
try {
|
|
312
|
-
fs.writeSync(logFd, msg);
|
|
313
|
-
} catch {}
|
|
151
|
+
try { fs.writeSync(logFd, msg); } catch {}
|
|
314
152
|
writeOutput(msg);
|
|
315
153
|
});
|
|
316
154
|
};
|
|
@@ -321,8 +159,8 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
321
159
|
// NOTE: must use 'exit', not 'close'. With shell:true, grandchild processes
|
|
322
160
|
// (e.g. nest's server) inherit stdout pipes. 'close' won't fire until ALL
|
|
323
161
|
// pipe holders exit, causing dev.js to hang when npm/nest dies but server survives.
|
|
324
|
-
const exitCode = await new Promise(resolve => {
|
|
325
|
-
child.on('exit', code => resolve(code ?? 1));
|
|
162
|
+
const exitCode = await new Promise((resolve) => {
|
|
163
|
+
child.on('exit', (code) => resolve(code ?? 1));
|
|
326
164
|
child.on('error', () => resolve(1));
|
|
327
165
|
});
|
|
328
166
|
|
|
@@ -339,11 +177,7 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
339
177
|
if (cleanupPort) {
|
|
340
178
|
const orphans = killOrphansByPort(cleanupPort);
|
|
341
179
|
if (orphans.length > 0) {
|
|
342
|
-
logEvent(
|
|
343
|
-
'WARN',
|
|
344
|
-
name,
|
|
345
|
-
`Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`
|
|
346
|
-
);
|
|
180
|
+
logEvent('WARN', name, `Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`);
|
|
347
181
|
await sleep(500);
|
|
348
182
|
}
|
|
349
183
|
}
|
|
@@ -353,38 +187,21 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
353
187
|
const runDuration = (Date.now() - startTime) / 1000;
|
|
354
188
|
if (runDuration >= 60) {
|
|
355
189
|
restartCount = 0;
|
|
356
|
-
logEvent(
|
|
357
|
-
'INFO',
|
|
358
|
-
name,
|
|
359
|
-
`Ran for ${Math.round(runDuration)}s, resetting restart counter`
|
|
360
|
-
);
|
|
190
|
+
logEvent('INFO', name, `Ran for ${Math.round(runDuration)}s, resetting restart counter`);
|
|
361
191
|
} else {
|
|
362
192
|
restartCount++;
|
|
363
193
|
}
|
|
364
194
|
if (restartCount >= MAX_RESTART_COUNT) {
|
|
365
|
-
logEvent(
|
|
366
|
-
'ERROR',
|
|
367
|
-
name,
|
|
368
|
-
`Max restart count (${MAX_RESTART_COUNT}) reached, giving up`
|
|
369
|
-
);
|
|
195
|
+
logEvent('ERROR', name, `Max restart count (${MAX_RESTART_COUNT}) reached, giving up`);
|
|
370
196
|
break;
|
|
371
197
|
}
|
|
372
198
|
|
|
373
|
-
const delay = Math.min(
|
|
374
|
-
|
|
375
|
-
MAX_DELAY
|
|
376
|
-
);
|
|
377
|
-
logEvent(
|
|
378
|
-
'WARN',
|
|
379
|
-
name,
|
|
380
|
-
`Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`
|
|
381
|
-
);
|
|
199
|
+
const delay = Math.min(RESTART_DELAY * (1 << Math.max(0, restartCount - 1)), MAX_DELAY);
|
|
200
|
+
logEvent('WARN', name, `Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`);
|
|
382
201
|
await sleep(delay * 1000);
|
|
383
202
|
}
|
|
384
203
|
|
|
385
|
-
try {
|
|
386
|
-
fs.closeSync(logFd);
|
|
387
|
-
} catch {}
|
|
204
|
+
try { fs.closeSync(logFd); } catch {}
|
|
388
205
|
};
|
|
389
206
|
|
|
390
207
|
return run();
|
|
@@ -414,11 +231,7 @@ async function cleanup() {
|
|
|
414
231
|
// Force kill any remaining
|
|
415
232
|
for (const entry of managedProcesses) {
|
|
416
233
|
if (entry.pid) {
|
|
417
|
-
logEvent(
|
|
418
|
-
'WARN',
|
|
419
|
-
'main',
|
|
420
|
-
`Force killing process group (PGID: ${entry.pid})`
|
|
421
|
-
);
|
|
234
|
+
logEvent('WARN', 'main', `Force killing process group (PGID: ${entry.pid})`);
|
|
422
235
|
killProcessGroup(entry.pid, 'SIGKILL');
|
|
423
236
|
}
|
|
424
237
|
}
|
|
@@ -429,12 +242,8 @@ async function cleanup() {
|
|
|
429
242
|
|
|
430
243
|
logEvent('INFO', 'main', 'All processes stopped');
|
|
431
244
|
|
|
432
|
-
try {
|
|
433
|
-
|
|
434
|
-
} catch {}
|
|
435
|
-
try {
|
|
436
|
-
fs.closeSync(devLogFd);
|
|
437
|
-
} catch {}
|
|
245
|
+
try { fs.closeSync(devStdLogFd); } catch {}
|
|
246
|
+
try { fs.closeSync(devLogFd); } catch {}
|
|
438
247
|
|
|
439
248
|
process.exit(0);
|
|
440
249
|
}
|
|
@@ -443,99 +252,16 @@ process.on('SIGTERM', cleanup);
|
|
|
443
252
|
process.on('SIGINT', cleanup);
|
|
444
253
|
process.on('SIGHUP', cleanup);
|
|
445
254
|
|
|
446
|
-
// dist
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
255
|
+
// 定向保留 dist/ 里能复用的部分, 替代之前无脑 rm -rf dist/。
|
|
256
|
+
// 收益: 保留 dist/server 让 nest --watch 从增量编译起点起 (省 8-13s 冷启动),
|
|
257
|
+
// routes JSON 也保留 (build 会覆盖, 无 stale 风险)。dist/client/index.html 按
|
|
258
|
+
// preset 打的 <meta name="miaoda-html-mode"> marker 判定:
|
|
259
|
+
// marker=dev → 保留 dist/client/ 整个 (dev cache hit)
|
|
260
|
+
// marker=prod → agent 跑过 build:client 污染, 定向清 index.html + assets/
|
|
261
|
+
// 具体逻辑抽到 lib/preserve-dev-cache.cjs 便于单测。
|
|
262
|
+
// 降级 escape hatch: FORCE_CLEAN_DIST=true 走原全清路径 (marker 逻辑出 bug 时用)。
|
|
453
263
|
const { preserveDevCache } = require('./lib/preserve-dev-cache.cjs');
|
|
454
264
|
|
|
455
|
-
function prepareTypeScriptBuildState() {
|
|
456
|
-
const startedAtEpochMs = Date.now();
|
|
457
|
-
const enabled =
|
|
458
|
-
STARTUP_EXPERIMENT_ENABLED &&
|
|
459
|
-
process.env.MIAODA_TYPESCRIPT_INCREMENTAL_CACHE_ENABLED === 'true';
|
|
460
|
-
const distPath = path.join(PROJECT_ROOT, 'dist');
|
|
461
|
-
const rootBuildInfoPath = path.join(
|
|
462
|
-
PROJECT_ROOT,
|
|
463
|
-
'tsconfig.node.tsbuildinfo'
|
|
464
|
-
);
|
|
465
|
-
const distBuildInfoPath = path.join(distPath, 'tsconfig.node.tsbuildinfo');
|
|
466
|
-
const candidateDistExists = fs.existsSync(distPath);
|
|
467
|
-
const candidateBuildInfoPath = fs.existsSync(distBuildInfoPath)
|
|
468
|
-
? distBuildInfoPath
|
|
469
|
-
: fs.existsSync(rootBuildInfoPath)
|
|
470
|
-
? rootBuildInfoPath
|
|
471
|
-
: '';
|
|
472
|
-
const candidateAvailable =
|
|
473
|
-
enabled && candidateDistExists && Boolean(candidateBuildInfoPath);
|
|
474
|
-
if (!candidateAvailable) {
|
|
475
|
-
fs.rmSync(distPath, { recursive: true, force: true });
|
|
476
|
-
fs.rmSync(rootBuildInfoPath, { force: true });
|
|
477
|
-
}
|
|
478
|
-
const atEpochMs = Date.now();
|
|
479
|
-
writeOutput(
|
|
480
|
-
`${JSON.stringify({
|
|
481
|
-
event: 'miaoda_typescript_incremental_cache',
|
|
482
|
-
phase: 'consume',
|
|
483
|
-
enabled,
|
|
484
|
-
status: candidateAvailable ? 'preserved' : enabled ? 'miss' : 'disabled',
|
|
485
|
-
candidateDistExists,
|
|
486
|
-
candidateTsbuildinfoExists: Boolean(candidateBuildInfoPath),
|
|
487
|
-
tsbuildinfoPath: candidateBuildInfoPath || null,
|
|
488
|
-
distExists: fs.existsSync(distPath),
|
|
489
|
-
tsbuildinfoExists:
|
|
490
|
-
fs.existsSync(distBuildInfoPath) || fs.existsSync(rootBuildInfoPath),
|
|
491
|
-
phaseStartedAtEpochMs: startedAtEpochMs,
|
|
492
|
-
atEpochMs,
|
|
493
|
-
durationMs: atEpochMs - startedAtEpochMs,
|
|
494
|
-
})}\n`
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
async function followExternalNestOrchestratorOrStartLocal(
|
|
499
|
-
serverStartupRuntimeScript
|
|
500
|
-
) {
|
|
501
|
-
const owner = readExternalNestOrchestratorOwner();
|
|
502
|
-
if (!owner) {
|
|
503
|
-
return startProcess({
|
|
504
|
-
name: 'server',
|
|
505
|
-
command: process.execPath,
|
|
506
|
-
args: [serverStartupRuntimeScript],
|
|
507
|
-
cleanupPort: SERVER_PORT,
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
logEvent(
|
|
512
|
-
'INFO',
|
|
513
|
-
'server',
|
|
514
|
-
`Following image-owned Nest orchestrator pid=${owner.pid} mode=${owner.mode}`
|
|
515
|
-
);
|
|
516
|
-
while (!stopping) {
|
|
517
|
-
try {
|
|
518
|
-
process.kill(owner.pid, 0);
|
|
519
|
-
await sleep(200);
|
|
520
|
-
} catch {
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (stopping) return 0;
|
|
525
|
-
|
|
526
|
-
logEvent(
|
|
527
|
-
'WARN',
|
|
528
|
-
'server',
|
|
529
|
-
`Image-owned Nest orchestrator pid=${owner.pid} exited; starting local supervisor`
|
|
530
|
-
);
|
|
531
|
-
return startProcess({
|
|
532
|
-
name: 'server',
|
|
533
|
-
command: process.execPath,
|
|
534
|
-
args: [serverStartupRuntimeScript],
|
|
535
|
-
cleanupPort: SERVER_PORT,
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
265
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
540
266
|
async function main() {
|
|
541
267
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
@@ -544,97 +270,29 @@ async function main() {
|
|
|
544
270
|
log: (level, msg) => logEvent(level, 'main', msg),
|
|
545
271
|
});
|
|
546
272
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const serverStartupRuntimeScript = path.join(
|
|
557
|
-
STARTUP_RUNTIME_SCRIPTS_ROOT,
|
|
558
|
-
'server-startup-runtime.mjs'
|
|
559
|
-
);
|
|
560
|
-
const workspaceClientRuntimeScript = path.join(
|
|
561
|
-
STARTUP_RUNTIME_SCRIPTS_ROOT,
|
|
562
|
-
'workspace-client-runtime.mjs'
|
|
563
|
-
);
|
|
564
|
-
const viteRuntimeConfigScript = path.join(
|
|
565
|
-
PROJECT_ROOT,
|
|
566
|
-
'scripts',
|
|
567
|
-
'vite-runtime.config.mjs'
|
|
568
|
-
);
|
|
569
|
-
if (STARTUP_EXPERIMENT_ENABLED) {
|
|
570
|
-
try {
|
|
571
|
-
for (const requiredPath of [
|
|
572
|
-
serverStartupRuntimeScript,
|
|
573
|
-
workspaceClientRuntimeScript,
|
|
574
|
-
viteRuntimeConfigScript,
|
|
575
|
-
]) {
|
|
576
|
-
if (!fs.existsSync(requiredPath)) {
|
|
577
|
-
throw new Error(
|
|
578
|
-
`MIAODA_STARTUP_EXPERIMENT_INPUT_MISSING: ${requiredPath}`
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
} catch (error) {
|
|
583
|
-
STARTUP_EXPERIMENT_ENABLED = false;
|
|
584
|
-
process.env.MIAODA_STARTUP_EXPERIMENT_ENABLED = 'false';
|
|
585
|
-
logEvent(
|
|
586
|
-
'WARN',
|
|
587
|
-
'main',
|
|
588
|
-
`Startup experiment unavailable; using original services: ${error.message}`
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (STARTUP_EXPERIMENT_ENABLED) {
|
|
594
|
-
fs.rmSync(ACTION_PLUGIN_READY_FILE, { force: true });
|
|
595
|
-
await waitForWorkspaceDependencies();
|
|
596
|
-
prepareTypeScriptBuildState();
|
|
597
|
-
initializeActionPlugins(true);
|
|
598
|
-
} else {
|
|
599
|
-
prepareTypeScriptBuildState();
|
|
600
|
-
initializeActionPlugins();
|
|
273
|
+
// Initialize action plugins
|
|
274
|
+
writeOutput('\n🔌 Initializing action plugins...\n');
|
|
275
|
+
try {
|
|
276
|
+
execSync('fullstack-cli action-plugin init', { cwd: PROJECT_ROOT, stdio: 'inherit' });
|
|
277
|
+
writeOutput('✅ Action plugins initialized\n\n');
|
|
278
|
+
} catch {
|
|
279
|
+
writeOutput('⚠️ Action plugin initialization failed, continuing anyway...\n\n');
|
|
601
280
|
}
|
|
602
281
|
|
|
603
282
|
// Start server and client
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
clientPromise = startProcess({
|
|
618
|
-
name: 'client',
|
|
619
|
-
command: process.execPath,
|
|
620
|
-
args: [workspaceClientRuntimeScript],
|
|
621
|
-
cleanupPort: CLIENT_DEV_PORT,
|
|
622
|
-
});
|
|
623
|
-
} else {
|
|
624
|
-
serverPromise = startProcess({
|
|
625
|
-
name: 'server',
|
|
626
|
-
command: 'npm',
|
|
627
|
-
args: ['run', 'dev:server'],
|
|
628
|
-
cleanupPort: SERVER_PORT,
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
clientPromise = startProcess({
|
|
632
|
-
name: 'client',
|
|
633
|
-
command: 'npm',
|
|
634
|
-
args: ['run', 'dev:client'],
|
|
635
|
-
cleanupPort: CLIENT_DEV_PORT,
|
|
636
|
-
});
|
|
637
|
-
}
|
|
283
|
+
const serverPromise = startProcess({
|
|
284
|
+
name: 'server',
|
|
285
|
+
command: 'npm',
|
|
286
|
+
args: ['run', 'dev:server'],
|
|
287
|
+
cleanupPort: SERVER_PORT,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const clientPromise = startProcess({
|
|
291
|
+
name: 'client',
|
|
292
|
+
command: 'npm',
|
|
293
|
+
args: ['run', 'dev:client'],
|
|
294
|
+
cleanupPort: CLIENT_DEV_PORT,
|
|
295
|
+
});
|
|
638
296
|
|
|
639
297
|
writeOutput(`📋 Dev processes running. Press Ctrl+C to stop.\n`);
|
|
640
298
|
writeOutput(`📄 Logs: ${devStdLogPath}\n\n`);
|
|
@@ -647,7 +305,7 @@ async function main() {
|
|
|
647
305
|
}
|
|
648
306
|
}
|
|
649
307
|
|
|
650
|
-
main().catch(err => {
|
|
308
|
+
main().catch((err) => {
|
|
651
309
|
console.error('Fatal error:', err);
|
|
652
310
|
process.exit(1);
|
|
653
311
|
});
|
package/template/scripts/dev.sh
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# `npm run dev` 入口;按
|
|
3
|
-
#
|
|
4
|
-
# - 非空(沙箱平台注入的依赖缓存目录)→ 直接跑 dev.js
|
|
2
|
+
# `npm run dev` 入口;按 SANDBOX_ID 是否非空判断运行环境:
|
|
3
|
+
# - SANDBOX_ID 非空(沙箱平台注入应用所属沙箱 ID)→ 直接跑 dev.js
|
|
5
4
|
# (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
|
|
6
5
|
# dev 入口不再额外 `npm run upgrade`。
|
|
7
|
-
# - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.
|
|
8
|
-
# 显式想跑本地路径可用 `npm run dev:local
|
|
9
|
-
#
|
|
10
|
-
# 不用 SANDBOX_ID:它由 sandbox_console 在绑定沙箱时才写入 .force/environment/env,预热池实例
|
|
11
|
-
# 在绑定前拿不到,会被误判成本地。保留为附加条件只为兼容,不影响判定结果。
|
|
6
|
+
# - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.sh:纯 stdout、崩了就崩、Agent 友好。
|
|
7
|
+
# 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
|
|
12
8
|
set -euo pipefail
|
|
13
9
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
-
export MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT="${MIAODA_CACHE_RUNTIME_SCRIPTS_ROOT:-$SCRIPT_DIR}"
|
|
15
|
-
export MIAODA_WORKSPACE_ROOT="${MIAODA_WORKSPACE_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
16
10
|
|
|
17
|
-
if [ -n "${
|
|
11
|
+
if [ -n "${SANDBOX_ID:-}" ]; then
|
|
18
12
|
exec node "$SCRIPT_DIR/dev.js" "$@"
|
|
19
13
|
fi
|
|
20
14
|
|
|
@@ -24,7 +18,7 @@ if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
|
|
|
24
18
|
fi
|
|
25
19
|
|
|
26
20
|
# 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
|
|
27
|
-
# latest + 迁移老 npm scripts
|
|
21
|
+
# latest + 迁移老 npm scripts。沙箱不走这里(SANDBOX_ID 分支已经 exec return)。
|
|
28
22
|
npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
|
|
29
23
|
|
|
30
24
|
exec node "$SCRIPT_DIR/dev-local.js" "$@"
|