@lark-apaas/coding-template-nestjs-react-fullstack 0.1.27-alpha.20260824122815 → 0.1.27
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-lock.json +4385 -4385
- package/template/package.json +5 -5
- package/template/scripts/build.sh +0 -12
- package/template/scripts/dev.js +57 -415
- package/template/scripts/dev.sh +0 -2
- package/template/vite.config.ts +1 -1
- 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/lib/preserve-dev-cache.cjs +0 -123
- 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/template/package.json
CHANGED
|
@@ -30,13 +30,13 @@
|
|
|
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
|
|
33
|
+
"upgrade": "npx -y @lark-apaas/fullstack-cli sync --disable-gen-openapi"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@lark-apaas/fullstack-nestjs-core": "^1.1.59",
|
|
37
37
|
"@nestjs/axios": "^4.0.1",
|
|
38
|
-
"@nestjs/cache-manager": "^3.1.3",
|
|
39
38
|
"@nestjs/common": "^10.4.20",
|
|
39
|
+
"@nestjs/cache-manager": "^3.1.3",
|
|
40
40
|
"@nestjs/config": "^3.3.0",
|
|
41
41
|
"@nestjs/core": "^10.4.20",
|
|
42
42
|
"@nestjs/platform-express": "^10.4.20",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"@headlessui/react": "^2.2.9",
|
|
63
63
|
"@heroicons/react": "^2.2.0",
|
|
64
64
|
"@hookform/resolvers": "^5.2.2",
|
|
65
|
-
"@lark-apaas/client-toolkit": "^1.2.
|
|
66
|
-
"@lark-apaas/coding-preset-vite-react": "1.0.19-alpha.20260824121536",
|
|
65
|
+
"@lark-apaas/client-toolkit": "^1.2.65",
|
|
67
66
|
"@lark-apaas/fullstack-presets": "^1.1.0",
|
|
67
|
+
"@lark-apaas/fullstack-vite-preset": "^1.0.35",
|
|
68
68
|
"@nestjs/cli": "^10.4.9",
|
|
69
69
|
"@nestjs/testing": "^10.4.20",
|
|
70
70
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"typescript-eslint": "^8.44.0",
|
|
182
182
|
"uuid": "11.0.5",
|
|
183
183
|
"vaul": "^1.1.2",
|
|
184
|
-
"vite": "^
|
|
184
|
+
"vite": "^7.3.1",
|
|
185
185
|
"xlsx": "^0.18.5",
|
|
186
186
|
"xlsx-js-style": "^1.2.0",
|
|
187
187
|
"zod": "^3.25.76",
|
|
@@ -89,18 +89,6 @@ cp "$ROOT_DIR/dist/client/"*.html "$OUT_DIR/dist/client/" || true
|
|
|
89
89
|
# 拷贝 run.sh 文件
|
|
90
90
|
cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
|
|
91
91
|
|
|
92
|
-
# 拷贝 shared/static → OUT_DIR/shared/static
|
|
93
|
-
# Nest server 生产从 process.cwd() + shared/static 读 (nestjs-core static.controller.ts:100),
|
|
94
|
-
# 生产 cwd = $OUT_DIR (dist/server), 所以目标是 dist/server/shared/static。
|
|
95
|
-
# coding-preset 的 static-assets plugin 之前 closeBundle 拷贝, 现在删除后由 build.sh 显式做。
|
|
96
|
-
# 排除 .ts/.tsx/.js/.jsx: 避免 shared/static/ 里若有开发脚本被误传到生产。
|
|
97
|
-
if [ -d "$ROOT_DIR/shared/static" ]; then
|
|
98
|
-
mkdir -p "$OUT_DIR/shared/static"
|
|
99
|
-
rsync -a --exclude='*.ts' --exclude='*.tsx' --exclude='*.js' --exclude='*.jsx' \
|
|
100
|
-
"$ROOT_DIR/shared/static/" "$OUT_DIR/shared/static/"
|
|
101
|
-
echo " Static → dist/server/shared/static/"
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
92
|
# 清理无用文件
|
|
105
93
|
rm -rf "$ROOT_DIR/dist/scripts"
|
|
106
94
|
rm -rf "$ROOT_DIR/dist/tsconfig.node.tsbuildinfo"
|
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,56 +50,26 @@ 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
|
}
|
|
197
61
|
|
|
198
|
-
|
|
199
|
-
// Each pending fs.write retains ~1.5 KB (msg + libuv/V8 wrappers); 1000 ≈ ~1.5 MB ceiling.
|
|
200
|
-
let _stdoutInFlight = 0;
|
|
201
|
-
const STDOUT_MAX_INFLIGHT = 1000;
|
|
202
|
-
|
|
203
|
-
/** Write to dev.std.log (sync, guaranteed) + mirror to terminal (async, non-blocking) */
|
|
62
|
+
/** Write to terminal + dev.std.log */
|
|
204
63
|
function writeOutput(msg) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
fs.writeSync(devStdLogFd, msg);
|
|
209
|
-
} catch {}
|
|
210
|
-
// stdout mirror via async fs.write: if process.stdout is a pty/pipe whose consumer
|
|
211
|
-
// stalls, the block happens on the libuv threadpool, NOT the event loop — so the
|
|
212
|
-
// synchronous log-FILE writes above (and subsequent readline callbacks) keep running.
|
|
213
|
-
// Drop overflow when too many writes are already pending (best-effort mirror).
|
|
214
|
-
if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
_stdoutInFlight++;
|
|
218
|
-
try {
|
|
219
|
-
fs.write(1, msg, () => {
|
|
220
|
-
_stdoutInFlight--;
|
|
221
|
-
});
|
|
222
|
-
} catch {
|
|
223
|
-
_stdoutInFlight--;
|
|
224
|
-
}
|
|
64
|
+
try { process.stdout.write(msg); } catch {}
|
|
65
|
+
try { fs.writeSync(devStdLogFd, msg); } catch {}
|
|
225
66
|
}
|
|
226
67
|
|
|
227
68
|
/** Structured event log → terminal + dev.std.log + dev.log */
|
|
228
69
|
function logEvent(level, name, message) {
|
|
229
70
|
const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
|
|
230
71
|
writeOutput(msg);
|
|
231
|
-
try {
|
|
232
|
-
fs.writeSync(devLogFd, msg);
|
|
233
|
-
} catch {}
|
|
72
|
+
try { fs.writeSync(devLogFd, msg); } catch {}
|
|
234
73
|
}
|
|
235
74
|
|
|
236
75
|
// ── Process group management ──────────────────────────────────────────────────
|
|
@@ -242,16 +81,11 @@ function killProcessGroup(pid, signal) {
|
|
|
242
81
|
|
|
243
82
|
function killOrphansByPort(port) {
|
|
244
83
|
try {
|
|
245
|
-
const pids = execSync(`lsof -ti :${port}`, {
|
|
246
|
-
encoding: 'utf8',
|
|
247
|
-
timeout: 5000,
|
|
248
|
-
}).trim();
|
|
84
|
+
const pids = execSync(`lsof -ti :${port}`, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
249
85
|
if (pids) {
|
|
250
86
|
const pidList = pids.split('\n').filter(Boolean);
|
|
251
87
|
for (const p of pidList) {
|
|
252
|
-
try {
|
|
253
|
-
process.kill(parseInt(p, 10), 'SIGKILL');
|
|
254
|
-
} catch {}
|
|
88
|
+
try { process.kill(parseInt(p, 10), 'SIGKILL'); } catch {}
|
|
255
89
|
}
|
|
256
90
|
return pidList;
|
|
257
91
|
}
|
|
@@ -264,7 +98,7 @@ let stopping = false;
|
|
|
264
98
|
const managedProcesses = []; // { name, pid, child }
|
|
265
99
|
|
|
266
100
|
function sleep(ms) {
|
|
267
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
101
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
268
102
|
}
|
|
269
103
|
|
|
270
104
|
/**
|
|
@@ -294,23 +128,14 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
294
128
|
entry.child = child;
|
|
295
129
|
|
|
296
130
|
const startTime = Date.now();
|
|
297
|
-
logEvent(
|
|
298
|
-
'INFO',
|
|
299
|
-
name,
|
|
300
|
-
`Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`
|
|
301
|
-
);
|
|
131
|
+
logEvent('INFO', name, `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`);
|
|
302
132
|
|
|
303
133
|
// 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 => {
|
|
134
|
+
const pipeLines = (stream) => {
|
|
135
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
136
|
+
rl.on('line', (line) => {
|
|
310
137
|
const msg = `[${timestamp()}] [${name}] ${line}\n`;
|
|
311
|
-
try {
|
|
312
|
-
fs.writeSync(logFd, msg);
|
|
313
|
-
} catch {}
|
|
138
|
+
try { fs.writeSync(logFd, msg); } catch {}
|
|
314
139
|
writeOutput(msg);
|
|
315
140
|
});
|
|
316
141
|
};
|
|
@@ -321,8 +146,8 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
321
146
|
// NOTE: must use 'exit', not 'close'. With shell:true, grandchild processes
|
|
322
147
|
// (e.g. nest's server) inherit stdout pipes. 'close' won't fire until ALL
|
|
323
148
|
// 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));
|
|
149
|
+
const exitCode = await new Promise((resolve) => {
|
|
150
|
+
child.on('exit', (code) => resolve(code ?? 1));
|
|
326
151
|
child.on('error', () => resolve(1));
|
|
327
152
|
});
|
|
328
153
|
|
|
@@ -339,11 +164,7 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
339
164
|
if (cleanupPort) {
|
|
340
165
|
const orphans = killOrphansByPort(cleanupPort);
|
|
341
166
|
if (orphans.length > 0) {
|
|
342
|
-
logEvent(
|
|
343
|
-
'WARN',
|
|
344
|
-
name,
|
|
345
|
-
`Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`
|
|
346
|
-
);
|
|
167
|
+
logEvent('WARN', name, `Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`);
|
|
347
168
|
await sleep(500);
|
|
348
169
|
}
|
|
349
170
|
}
|
|
@@ -353,38 +174,21 @@ function startProcess({ name, command, args, cleanupPort }) {
|
|
|
353
174
|
const runDuration = (Date.now() - startTime) / 1000;
|
|
354
175
|
if (runDuration >= 60) {
|
|
355
176
|
restartCount = 0;
|
|
356
|
-
logEvent(
|
|
357
|
-
'INFO',
|
|
358
|
-
name,
|
|
359
|
-
`Ran for ${Math.round(runDuration)}s, resetting restart counter`
|
|
360
|
-
);
|
|
177
|
+
logEvent('INFO', name, `Ran for ${Math.round(runDuration)}s, resetting restart counter`);
|
|
361
178
|
} else {
|
|
362
179
|
restartCount++;
|
|
363
180
|
}
|
|
364
181
|
if (restartCount >= MAX_RESTART_COUNT) {
|
|
365
|
-
logEvent(
|
|
366
|
-
'ERROR',
|
|
367
|
-
name,
|
|
368
|
-
`Max restart count (${MAX_RESTART_COUNT}) reached, giving up`
|
|
369
|
-
);
|
|
182
|
+
logEvent('ERROR', name, `Max restart count (${MAX_RESTART_COUNT}) reached, giving up`);
|
|
370
183
|
break;
|
|
371
184
|
}
|
|
372
185
|
|
|
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
|
-
);
|
|
186
|
+
const delay = Math.min(RESTART_DELAY * (1 << Math.max(0, restartCount - 1)), MAX_DELAY);
|
|
187
|
+
logEvent('WARN', name, `Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`);
|
|
382
188
|
await sleep(delay * 1000);
|
|
383
189
|
}
|
|
384
190
|
|
|
385
|
-
try {
|
|
386
|
-
fs.closeSync(logFd);
|
|
387
|
-
} catch {}
|
|
191
|
+
try { fs.closeSync(logFd); } catch {}
|
|
388
192
|
};
|
|
389
193
|
|
|
390
194
|
return run();
|
|
@@ -414,11 +218,7 @@ async function cleanup() {
|
|
|
414
218
|
// Force kill any remaining
|
|
415
219
|
for (const entry of managedProcesses) {
|
|
416
220
|
if (entry.pid) {
|
|
417
|
-
logEvent(
|
|
418
|
-
'WARN',
|
|
419
|
-
'main',
|
|
420
|
-
`Force killing process group (PGID: ${entry.pid})`
|
|
421
|
-
);
|
|
221
|
+
logEvent('WARN', 'main', `Force killing process group (PGID: ${entry.pid})`);
|
|
422
222
|
killProcessGroup(entry.pid, 'SIGKILL');
|
|
423
223
|
}
|
|
424
224
|
}
|
|
@@ -429,12 +229,8 @@ async function cleanup() {
|
|
|
429
229
|
|
|
430
230
|
logEvent('INFO', 'main', 'All processes stopped');
|
|
431
231
|
|
|
432
|
-
try {
|
|
433
|
-
|
|
434
|
-
} catch {}
|
|
435
|
-
try {
|
|
436
|
-
fs.closeSync(devLogFd);
|
|
437
|
-
} catch {}
|
|
232
|
+
try { fs.closeSync(devStdLogFd); } catch {}
|
|
233
|
+
try { fs.closeSync(devLogFd); } catch {}
|
|
438
234
|
|
|
439
235
|
process.exit(0);
|
|
440
236
|
}
|
|
@@ -443,198 +239,44 @@ process.on('SIGTERM', cleanup);
|
|
|
443
239
|
process.on('SIGINT', cleanup);
|
|
444
240
|
process.on('SIGHUP', cleanup);
|
|
445
241
|
|
|
446
|
-
// dist
|
|
447
|
-
|
|
448
|
-
// 后续启动 (agent 主动触发 npm run dev) → 全清 dist/, 让模型面对新鲜产物避免 stale
|
|
449
|
-
// 依赖升级 (lockfile hash 变) → 同后续启动 (清 dist), log 里点出 "deps changed"
|
|
450
|
-
// FORCE_CLEAN_DIST=true → escape hatch, 强清
|
|
451
|
-
// marker 放 os.tmpdir()/.miaoda-dev-boot (跟沙箱容器 lifecycle 同, 沙箱重启会清空,
|
|
452
|
-
// 语义对齐"沙箱首次启动"); 具体逻辑抽到 lib/preserve-dev-cache.cjs 便于单测。
|
|
453
|
-
const { preserveDevCache } = require('./lib/preserve-dev-cache.cjs');
|
|
454
|
-
|
|
455
|
-
function prepareTypeScriptBuildState() {
|
|
456
|
-
const startedAtEpochMs = Date.now();
|
|
457
|
-
const enabled =
|
|
458
|
-
STARTUP_EXPERIMENT_ENABLED &&
|
|
459
|
-
process.env.MIAODA_TYPESCRIPT_INCREMENTAL_CACHE_ENABLED === 'true';
|
|
242
|
+
// Stale dist makes nest --watch skip missing files; watcher won't self-heal.
|
|
243
|
+
function cleanStaleDist() {
|
|
460
244
|
const distPath = path.join(PROJECT_ROOT, 'dist');
|
|
461
|
-
|
|
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) {
|
|
245
|
+
if (fs.existsSync(distPath)) {
|
|
475
246
|
fs.rmSync(distPath, { recursive: true, force: true });
|
|
476
|
-
|
|
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
|
-
}
|
|
247
|
+
logEvent('INFO', 'main', 'Cleaned dist/ to force full rebuild');
|
|
523
248
|
}
|
|
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
249
|
}
|
|
538
250
|
|
|
539
251
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
540
252
|
async function main() {
|
|
541
253
|
logEvent('INFO', 'main', '========== Dev session started ==========');
|
|
542
254
|
|
|
543
|
-
|
|
544
|
-
log: (level, msg) => logEvent(level, 'main', msg),
|
|
545
|
-
});
|
|
255
|
+
cleanStaleDist();
|
|
546
256
|
|
|
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();
|
|
257
|
+
// Initialize action plugins
|
|
258
|
+
writeOutput('\n🔌 Initializing action plugins...\n');
|
|
259
|
+
try {
|
|
260
|
+
execSync('fullstack-cli action-plugin init', { cwd: PROJECT_ROOT, stdio: 'inherit' });
|
|
261
|
+
writeOutput('✅ Action plugins initialized\n\n');
|
|
262
|
+
} catch {
|
|
263
|
+
writeOutput('⚠️ Action plugin initialization failed, continuing anyway...\n\n');
|
|
601
264
|
}
|
|
602
265
|
|
|
603
266
|
// 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
|
-
}
|
|
267
|
+
const serverPromise = startProcess({
|
|
268
|
+
name: 'server',
|
|
269
|
+
command: 'npm',
|
|
270
|
+
args: ['run', 'dev:server'],
|
|
271
|
+
cleanupPort: SERVER_PORT,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const clientPromise = startProcess({
|
|
275
|
+
name: 'client',
|
|
276
|
+
command: 'npm',
|
|
277
|
+
args: ['run', 'dev:client'],
|
|
278
|
+
cleanupPort: CLIENT_DEV_PORT,
|
|
279
|
+
});
|
|
638
280
|
|
|
639
281
|
writeOutput(`📋 Dev processes running. Press Ctrl+C to stop.\n`);
|
|
640
282
|
writeOutput(`📄 Logs: ${devStdLogPath}\n\n`);
|
|
@@ -647,7 +289,7 @@ async function main() {
|
|
|
647
289
|
}
|
|
648
290
|
}
|
|
649
291
|
|
|
650
|
-
main().catch(err => {
|
|
292
|
+
main().catch((err) => {
|
|
651
293
|
console.error('Fatal error:', err);
|
|
652
294
|
process.exit(1);
|
|
653
295
|
});
|
package/template/scripts/dev.sh
CHANGED
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
# 在绑定前拿不到,会被误判成本地。保留为附加条件只为兼容,不影响判定结果。
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
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
14
|
|
|
17
15
|
if [ -n "${MIAODA_DEP_CACHE_DIR:-}" ] || [ -n "${SANDBOX_ID:-}" ]; then
|
|
18
16
|
exec node "$SCRIPT_DIR/dev.js" "$@"
|
package/template/vite.config.ts
CHANGED