@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
|
@@ -1,865 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import net from 'node:net';
|
|
6
|
-
import http from 'node:http';
|
|
7
|
-
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { spawn } from 'node:child_process';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import {
|
|
11
|
-
validateCacheGeneration,
|
|
12
|
-
validateCacheGenerationReceipt,
|
|
13
|
-
writeCacheGenerationValidationReceipt,
|
|
14
|
-
} from './cache-generation-preflight.mjs';
|
|
15
|
-
|
|
16
|
-
const projectRoot = path.resolve(
|
|
17
|
-
process.env.MIAODA_WORKSPACE_ROOT || process.cwd()
|
|
18
|
-
);
|
|
19
|
-
const defaultCachePointer = path.join(projectRoot, '.miaoda-cache', 'current');
|
|
20
|
-
const requestedCacheRoot = path.resolve(
|
|
21
|
-
process.env.MIAODA_CACHE_ROOT || defaultCachePointer
|
|
22
|
-
);
|
|
23
|
-
const cacheRoot = fs.realpathSync(requestedCacheRoot);
|
|
24
|
-
if (!process.env.MIAODA_CACHE_ROOT) {
|
|
25
|
-
const generationStore = fs.realpathSync(
|
|
26
|
-
path.join(projectRoot, '.miaoda-cache', 'generations')
|
|
27
|
-
);
|
|
28
|
-
const generationRelative = path.relative(generationStore, cacheRoot);
|
|
29
|
-
if (!/^[a-f0-9]{64}$/.test(generationRelative)) {
|
|
30
|
-
throw new Error('MIAODA_CACHE_GENERATION_POINTER_REJECTED');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const runtimeManifestFile = path.resolve(
|
|
34
|
-
process.env.MIAODA_PLATFORM_RUNTIME_MANIFEST ||
|
|
35
|
-
'/opt/miaoda/preview-runtime/runtime-manifest.json'
|
|
36
|
-
);
|
|
37
|
-
const validateOnly = process.argv.includes('--validate-only');
|
|
38
|
-
const artifactScopeOptionIndex = process.argv.indexOf('--artifact-scope');
|
|
39
|
-
const validationArtifactScope =
|
|
40
|
-
artifactScopeOptionIndex >= 0
|
|
41
|
-
? process.argv[artifactScopeOptionIndex + 1]
|
|
42
|
-
: 'all';
|
|
43
|
-
const skipWorkspaceInputs = process.argv.includes('--skip-workspace-inputs');
|
|
44
|
-
const servingReadyMarker = '/tmp/event/MIAODA_NEST_CACHE_SERVING_READY';
|
|
45
|
-
const skipConfigEnvironment = process.argv.includes(
|
|
46
|
-
'--skip-config-environment'
|
|
47
|
-
);
|
|
48
|
-
const runtimeStartedAt = process.hrtime.bigint();
|
|
49
|
-
const durationMs = startedAt =>
|
|
50
|
-
Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
|
51
|
-
function emitRuntimePhase(phase, startedAt, details = {}) {
|
|
52
|
-
if (validateOnly) return;
|
|
53
|
-
process.stdout.write(
|
|
54
|
-
`${JSON.stringify({
|
|
55
|
-
event: 'miaoda_cache_runtime_phase',
|
|
56
|
-
runtime: 'nest',
|
|
57
|
-
phase,
|
|
58
|
-
durationMs: Number(durationMs(startedAt).toFixed(3)),
|
|
59
|
-
...details,
|
|
60
|
-
})}\n`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
function publishServingReadyMarker(file) {
|
|
64
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
65
|
-
const temporaryFile = `${file}.${process.pid}.tmp`;
|
|
66
|
-
fs.writeFileSync(
|
|
67
|
-
temporaryFile,
|
|
68
|
-
`${JSON.stringify({ schemaVersion: 1, readyAt: new Date().toISOString() })}\n`,
|
|
69
|
-
{ mode: 0o600 }
|
|
70
|
-
);
|
|
71
|
-
fs.renameSync(temporaryFile, file);
|
|
72
|
-
}
|
|
73
|
-
if (!validateOnly) {
|
|
74
|
-
fs.rmSync(servingReadyMarker, { force: true });
|
|
75
|
-
process.once('exit', () => {
|
|
76
|
-
try {
|
|
77
|
-
fs.rmSync(servingReadyMarker, { force: true });
|
|
78
|
-
} catch {}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (skipConfigEnvironment && !validateOnly) {
|
|
82
|
-
process.stderr.write(
|
|
83
|
-
'MIAODA_CACHE_CONFIG_ENVIRONMENT_SKIP_REQUIRES_VALIDATE_ONLY\n'
|
|
84
|
-
);
|
|
85
|
-
process.exit(2);
|
|
86
|
-
}
|
|
87
|
-
if (
|
|
88
|
-
(!validateOnly && artifactScopeOptionIndex >= 0) ||
|
|
89
|
-
(!validateOnly && skipWorkspaceInputs) ||
|
|
90
|
-
!['all', 'server', 'vite'].includes(validationArtifactScope) ||
|
|
91
|
-
(skipWorkspaceInputs && validationArtifactScope !== 'vite')
|
|
92
|
-
) {
|
|
93
|
-
process.stderr.write('MIAODA_CACHE_VALIDATION_OPTIONS_INVALID\n');
|
|
94
|
-
process.exit(2);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let validated;
|
|
98
|
-
let validatedBy = 'full-sha';
|
|
99
|
-
let validationReceiptMiss;
|
|
100
|
-
const validationStartedAt = process.hrtime.bigint();
|
|
101
|
-
try {
|
|
102
|
-
const validationReceiptFile =
|
|
103
|
-
process.env.MIAODA_CACHE_VALIDATION_RECEIPT_FILE;
|
|
104
|
-
if (validationReceiptFile && !skipWorkspaceInputs) {
|
|
105
|
-
try {
|
|
106
|
-
validated = validateCacheGenerationReceipt({
|
|
107
|
-
projectRoot,
|
|
108
|
-
cacheRoot,
|
|
109
|
-
runtimeManifestFile,
|
|
110
|
-
receiptFile: validationReceiptFile,
|
|
111
|
-
validateConfigEnvironment: !skipConfigEnvironment,
|
|
112
|
-
artifactScope: validateOnly ? validationArtifactScope : 'server',
|
|
113
|
-
});
|
|
114
|
-
validatedBy = 'platform-receipt';
|
|
115
|
-
} catch (error) {
|
|
116
|
-
validationReceiptMiss = String(
|
|
117
|
-
error instanceof Error ? error.message : error
|
|
118
|
-
).split(':')[0];
|
|
119
|
-
// A missing/stale receipt is only a fast-path miss. Preserve the legacy
|
|
120
|
-
// full preflight and dependency-backed fallback semantics below.
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!validated) {
|
|
124
|
-
validated = validateCacheGeneration({
|
|
125
|
-
projectRoot,
|
|
126
|
-
cacheRoot,
|
|
127
|
-
runtimeManifestFile,
|
|
128
|
-
validateConfigEnvironment: !skipConfigEnvironment,
|
|
129
|
-
artifactScope: validateOnly ? validationArtifactScope : 'server',
|
|
130
|
-
validateWorkspaceInputs: !skipWorkspaceInputs,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
if (
|
|
134
|
-
!process.env.MIAODA_CACHE_ROOT &&
|
|
135
|
-
path.basename(cacheRoot) !== validated.generation.generationId
|
|
136
|
-
) {
|
|
137
|
-
throw new Error('MIAODA_CACHE_GENERATION_POINTER_ID_MISMATCH');
|
|
138
|
-
}
|
|
139
|
-
if (validateOnly) {
|
|
140
|
-
if (
|
|
141
|
-
validationReceiptFile &&
|
|
142
|
-
validatedBy === 'full-sha' &&
|
|
143
|
-
!skipWorkspaceInputs
|
|
144
|
-
) {
|
|
145
|
-
writeCacheGenerationValidationReceipt({
|
|
146
|
-
validated,
|
|
147
|
-
receiptFile: validationReceiptFile,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
process.stdout.write(
|
|
151
|
-
`${JSON.stringify({
|
|
152
|
-
valid: true,
|
|
153
|
-
generationId: validated.generation.generationId,
|
|
154
|
-
artifactScope: validationArtifactScope,
|
|
155
|
-
workspaceInputsValidated: !skipWorkspaceInputs,
|
|
156
|
-
validatedBy,
|
|
157
|
-
receiptWritten: Boolean(
|
|
158
|
-
validationReceiptFile &&
|
|
159
|
-
validatedBy === 'full-sha' &&
|
|
160
|
-
!skipWorkspaceInputs
|
|
161
|
-
),
|
|
162
|
-
})}\n`
|
|
163
|
-
);
|
|
164
|
-
process.exit(0);
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
|
|
168
|
-
process.exit(2);
|
|
169
|
-
}
|
|
170
|
-
emitRuntimePhase('generation_validation', validationStartedAt, {
|
|
171
|
-
artifactScope: 'server',
|
|
172
|
-
validatedBy,
|
|
173
|
-
...(validationReceiptMiss ? { validationReceiptMiss } : {}),
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const publicPort = Number(process.env.SERVER_PORT || 3000);
|
|
177
|
-
const publicHost = process.env.SERVER_HOST || '0.0.0.0';
|
|
178
|
-
const dependencyReadyFile = path.resolve(
|
|
179
|
-
process.env.MIAODA_DEPENDENCY_READY_FILE ||
|
|
180
|
-
path.join(projectRoot, '.miaoda-runtime', 'dependencies-ready.json')
|
|
181
|
-
);
|
|
182
|
-
const actionPluginReadyFile = path.resolve(
|
|
183
|
-
process.env.MIAODA_ACTION_PLUGIN_READY_FILE ||
|
|
184
|
-
'/tmp/event/MIAODA_ACTION_PLUGIN_READY.json'
|
|
185
|
-
);
|
|
186
|
-
const expectedRestoreEpochMs =
|
|
187
|
-
process.env.MIAODA_PLATFORM_ENV_NOT_BEFORE_MS || '';
|
|
188
|
-
const skipActionPluginInit =
|
|
189
|
-
process.env.MIAODA_NEST_SKIP_ACTION_PLUGIN_INIT === 'true';
|
|
190
|
-
const sourceHandoffMode =
|
|
191
|
-
process.env.MIAODA_NEST_SOURCE_HANDOFF_MODE || 'immediate';
|
|
192
|
-
if (!['immediate', 'on-source-change'].includes(sourceHandoffMode)) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`MIAODA_NEST_SOURCE_HANDOFF_MODE_INVALID: ${sourceHandoffMode}`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
function positiveDuration(name, fallback) {
|
|
198
|
-
const value = Number(process.env[name] || fallback);
|
|
199
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
200
|
-
}
|
|
201
|
-
const sourceReadyTimeoutMs = positiveDuration(
|
|
202
|
-
'MIAODA_NEST_SOURCE_READY_TIMEOUT_MS',
|
|
203
|
-
60_000
|
|
204
|
-
);
|
|
205
|
-
const cacheDrainTimeoutMs = positiveDuration(
|
|
206
|
-
'MIAODA_NEST_CACHE_DRAIN_TIMEOUT_MS',
|
|
207
|
-
5_000
|
|
208
|
-
);
|
|
209
|
-
const serviceId = randomUUID();
|
|
210
|
-
let stopping = false;
|
|
211
|
-
let sourceHandoffWatcher;
|
|
212
|
-
let sourceHandoffTimer;
|
|
213
|
-
|
|
214
|
-
function resolveProbeHost(host) {
|
|
215
|
-
const normalized = String(host || '').trim();
|
|
216
|
-
const lower = normalized.toLowerCase();
|
|
217
|
-
if (!normalized || lower === '0.0.0.0') return '127.0.0.1';
|
|
218
|
-
if (lower === '::' || lower === '[::]') return '::1';
|
|
219
|
-
if (normalized.startsWith('[') && normalized.endsWith(']')) {
|
|
220
|
-
return normalized.slice(1, -1);
|
|
221
|
-
}
|
|
222
|
-
return normalized;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function waitForPort(host, port, timeoutMs = 60_000) {
|
|
226
|
-
const deadline = Date.now() + timeoutMs;
|
|
227
|
-
const probeHost = resolveProbeHost(host);
|
|
228
|
-
return new Promise((resolve, reject) => {
|
|
229
|
-
const attempt = () => {
|
|
230
|
-
if (stopping) return reject(new Error('MIAODA_SERVER_RUNTIME_STOPPING'));
|
|
231
|
-
const socket = net.createConnection({ host: probeHost, port });
|
|
232
|
-
const failed = () => {
|
|
233
|
-
socket.destroy();
|
|
234
|
-
if (Date.now() >= deadline) {
|
|
235
|
-
reject(new Error(`MIAODA_SERVER_UPSTREAM_TIMEOUT: ${port}`));
|
|
236
|
-
} else {
|
|
237
|
-
setTimeout(attempt, 100);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
socket.once('connect', () => {
|
|
241
|
-
socket.destroy();
|
|
242
|
-
resolve();
|
|
243
|
-
});
|
|
244
|
-
socket.once('error', failed);
|
|
245
|
-
};
|
|
246
|
-
attempt();
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function reservePort(host) {
|
|
251
|
-
return new Promise((resolve, reject) => {
|
|
252
|
-
const server = net.createServer();
|
|
253
|
-
server.once('error', reject);
|
|
254
|
-
server.listen(0, host, () => {
|
|
255
|
-
const address = server.address();
|
|
256
|
-
if (!address || typeof address === 'string') {
|
|
257
|
-
server.close(() =>
|
|
258
|
-
reject(new Error('MIAODA_SERVER_UPSTREAM_PORT_RESERVATION_FAILED'))
|
|
259
|
-
);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
server.close(error => (error ? reject(error) : resolve(address.port)));
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function delay(ms) {
|
|
268
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function childIsRunning(child) {
|
|
272
|
-
return Boolean(
|
|
273
|
-
child?.pid && child.exitCode === null && child.signalCode === null
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function terminate(child) {
|
|
278
|
-
if (!child?.pid) return;
|
|
279
|
-
try {
|
|
280
|
-
process.kill(-child.pid, 'SIGTERM');
|
|
281
|
-
} catch {
|
|
282
|
-
try {
|
|
283
|
-
child.kill('SIGTERM');
|
|
284
|
-
} catch {}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function waitForChildExit(child, timeoutMs) {
|
|
289
|
-
if (!childIsRunning(child)) return Promise.resolve();
|
|
290
|
-
return Promise.race([
|
|
291
|
-
new Promise(resolve => child.once('exit', resolve)),
|
|
292
|
-
delay(timeoutMs),
|
|
293
|
-
]);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function pruneSupersededGenerations() {
|
|
297
|
-
if (process.env.MIAODA_CACHE_ROOT) return;
|
|
298
|
-
const pruneScript = fileURLToPath(
|
|
299
|
-
new URL('./cache-generation-prune.mjs', import.meta.url)
|
|
300
|
-
);
|
|
301
|
-
if (!fs.existsSync(pruneScript)) {
|
|
302
|
-
process.stderr.write(
|
|
303
|
-
`${JSON.stringify({ event: 'miaoda_cache_generation_prune_failed', error: 'MIAODA_CACHE_GENERATION_PRUNE_SCRIPT_MISSING' })}\n`
|
|
304
|
-
);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const pruneChild = spawn(process.execPath, [pruneScript, '--wait-ms=60000'], {
|
|
308
|
-
cwd: projectRoot,
|
|
309
|
-
stdio: 'inherit',
|
|
310
|
-
env: { ...process.env, MIAODA_WORKSPACE_ROOT: projectRoot },
|
|
311
|
-
});
|
|
312
|
-
pruneChild.once('error', error => {
|
|
313
|
-
process.stderr.write(
|
|
314
|
-
`${JSON.stringify({ event: 'miaoda_cache_generation_prune_failed', error: error.message })}\n`
|
|
315
|
-
);
|
|
316
|
-
});
|
|
317
|
-
pruneChild.once('exit', (code, signal) => {
|
|
318
|
-
if (code === 0) return;
|
|
319
|
-
process.stderr.write(
|
|
320
|
-
`${JSON.stringify({
|
|
321
|
-
event: 'miaoda_cache_generation_prune_failed',
|
|
322
|
-
error: signal ? `signal-${signal}` : `exit-${code}`,
|
|
323
|
-
})}\n`
|
|
324
|
-
);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const internalHost = resolveProbeHost(publicHost);
|
|
329
|
-
const cacheUpstreamPort = await reservePort(internalHost);
|
|
330
|
-
let sourceUpstreamPort = await reservePort(internalHost);
|
|
331
|
-
while (sourceUpstreamPort === cacheUpstreamPort) {
|
|
332
|
-
sourceUpstreamPort = await reservePort(internalHost);
|
|
333
|
-
}
|
|
334
|
-
const cacheUpstream = {
|
|
335
|
-
name: 'cache',
|
|
336
|
-
host: internalHost,
|
|
337
|
-
port: cacheUpstreamPort,
|
|
338
|
-
inflight: 0,
|
|
339
|
-
};
|
|
340
|
-
const sourceUpstream = {
|
|
341
|
-
name: 'source',
|
|
342
|
-
host: internalHost,
|
|
343
|
-
port: sourceUpstreamPort,
|
|
344
|
-
inflight: 0,
|
|
345
|
-
};
|
|
346
|
-
let activeUpstream = cacheUpstream;
|
|
347
|
-
let cacheRetired = false;
|
|
348
|
-
let sourceChild;
|
|
349
|
-
let actionPluginInitChild;
|
|
350
|
-
let actionPluginsInitialized = false;
|
|
351
|
-
|
|
352
|
-
function retain(upstream) {
|
|
353
|
-
upstream.inflight += 1;
|
|
354
|
-
let released = false;
|
|
355
|
-
return () => {
|
|
356
|
-
if (released) return;
|
|
357
|
-
released = true;
|
|
358
|
-
upstream.inflight = Math.max(0, upstream.inflight - 1);
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const proxy = http.createServer((request, response) => {
|
|
363
|
-
const upstream = activeUpstream;
|
|
364
|
-
const release = retain(upstream);
|
|
365
|
-
const upstreamRequest = http.request(
|
|
366
|
-
{
|
|
367
|
-
hostname: upstream.host,
|
|
368
|
-
port: upstream.port,
|
|
369
|
-
method: request.method,
|
|
370
|
-
path: request.url,
|
|
371
|
-
headers: request.headers,
|
|
372
|
-
},
|
|
373
|
-
upstreamResponse => {
|
|
374
|
-
if (upstreamResponse.statusMessage) {
|
|
375
|
-
response.writeHead(
|
|
376
|
-
upstreamResponse.statusCode || 502,
|
|
377
|
-
upstreamResponse.statusMessage,
|
|
378
|
-
upstreamResponse.headers
|
|
379
|
-
);
|
|
380
|
-
} else {
|
|
381
|
-
response.writeHead(
|
|
382
|
-
upstreamResponse.statusCode || 502,
|
|
383
|
-
upstreamResponse.headers
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
upstreamResponse.pipe(response);
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
upstreamRequest.once('error', error => {
|
|
390
|
-
if (!response.headersSent) {
|
|
391
|
-
response.writeHead(502, { 'content-type': 'application/json' });
|
|
392
|
-
response.end(
|
|
393
|
-
`${JSON.stringify({
|
|
394
|
-
error: 'MIAODA_NEST_UPSTREAM_UNAVAILABLE',
|
|
395
|
-
upstream: upstream.name,
|
|
396
|
-
})}\n`
|
|
397
|
-
);
|
|
398
|
-
} else {
|
|
399
|
-
response.destroy(error);
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
request.once('aborted', () => upstreamRequest.destroy());
|
|
403
|
-
response.once('finish', release);
|
|
404
|
-
response.once('close', release);
|
|
405
|
-
request.pipe(upstreamRequest);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
proxy.on('upgrade', (request, clientSocket, head) => {
|
|
409
|
-
const upstream = activeUpstream;
|
|
410
|
-
const release = retain(upstream);
|
|
411
|
-
const upstreamSocket = net.createConnection({
|
|
412
|
-
host: upstream.host,
|
|
413
|
-
port: upstream.port,
|
|
414
|
-
});
|
|
415
|
-
const close = () => {
|
|
416
|
-
release();
|
|
417
|
-
clientSocket.destroy();
|
|
418
|
-
upstreamSocket.destroy();
|
|
419
|
-
};
|
|
420
|
-
upstreamSocket.once('connect', () => {
|
|
421
|
-
const requestLine = `${request.method} ${request.url} HTTP/${request.httpVersion}\r\n`;
|
|
422
|
-
const headers = [];
|
|
423
|
-
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
424
|
-
headers.push(
|
|
425
|
-
`${request.rawHeaders[index]}: ${request.rawHeaders[index + 1]}`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
upstreamSocket.write(`${requestLine}${headers.join('\r\n')}\r\n\r\n`);
|
|
429
|
-
if (head.length > 0) upstreamSocket.write(head);
|
|
430
|
-
clientSocket.pipe(upstreamSocket).pipe(clientSocket);
|
|
431
|
-
});
|
|
432
|
-
upstreamSocket.once('error', close);
|
|
433
|
-
clientSocket.once('error', close);
|
|
434
|
-
upstreamSocket.once('close', release);
|
|
435
|
-
clientSocket.once('close', release);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
proxy.on('clientError', (_error, socket) => {
|
|
439
|
-
if (socket.writable) socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
function listen(server, host, port) {
|
|
443
|
-
return new Promise((resolve, reject) => {
|
|
444
|
-
server.once('error', reject);
|
|
445
|
-
server.listen(port, host, resolve);
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const bundleSpawnStartedAt = process.hrtime.bigint();
|
|
450
|
-
const cacheChild = spawn(process.execPath, [validated.artifacts.serverBundle], {
|
|
451
|
-
cwd: projectRoot,
|
|
452
|
-
detached: true,
|
|
453
|
-
stdio: 'inherit',
|
|
454
|
-
env: {
|
|
455
|
-
...process.env,
|
|
456
|
-
NODE_PATH: '',
|
|
457
|
-
SERVER_HOST: cacheUpstream.host,
|
|
458
|
-
SERVER_PORT: String(cacheUpstream.port),
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
emitRuntimePhase('bundle_spawn', bundleSpawnStartedAt, {
|
|
462
|
-
childPid: cacheChild.pid,
|
|
463
|
-
upstreamPort: cacheUpstream.port,
|
|
464
|
-
});
|
|
465
|
-
cacheChild.once('exit', (code, signal) => {
|
|
466
|
-
if (stopping || cacheRetired) return;
|
|
467
|
-
process.stderr.write(
|
|
468
|
-
`${JSON.stringify({ event: 'miaoda_nest_cache_runtime_exited', code, signal })}\n`
|
|
469
|
-
);
|
|
470
|
-
if (activeUpstream === cacheUpstream) process.exit(code || 1);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
let rejectedDependencyReceiptSignature = '';
|
|
474
|
-
function dependenciesAreReady() {
|
|
475
|
-
try {
|
|
476
|
-
const marker = JSON.parse(fs.readFileSync(dependencyReadyFile, 'utf8'));
|
|
477
|
-
if (
|
|
478
|
-
marker?.schemaVersion !== 1 ||
|
|
479
|
-
marker.generationId !== validated.generation.generationId
|
|
480
|
-
) {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
if (
|
|
484
|
-
expectedRestoreEpochMs &&
|
|
485
|
-
String(marker.restoreEpochMs || '') !== expectedRestoreEpochMs
|
|
486
|
-
) {
|
|
487
|
-
const signature = `${marker.generationId}:${marker.restoreEpochMs || ''}`;
|
|
488
|
-
if (signature !== rejectedDependencyReceiptSignature) {
|
|
489
|
-
rejectedDependencyReceiptSignature = signature;
|
|
490
|
-
process.stdout.write(
|
|
491
|
-
`${JSON.stringify({
|
|
492
|
-
event: 'miaoda_nest_dependency_receipt_rejected',
|
|
493
|
-
reason: 'restore-identity-mismatch',
|
|
494
|
-
expectedRestoreEpochMs,
|
|
495
|
-
})}\n`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
return fs.statSync(path.join(projectRoot, 'node_modules')).isDirectory();
|
|
501
|
-
} catch {
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function actionPluginPreconditionReady() {
|
|
507
|
-
try {
|
|
508
|
-
const marker = JSON.parse(fs.readFileSync(actionPluginReadyFile, 'utf8'));
|
|
509
|
-
return (
|
|
510
|
-
marker?.schemaVersion === 1 &&
|
|
511
|
-
(!expectedRestoreEpochMs ||
|
|
512
|
-
String(marker.restoreEpochMs || '') === expectedRestoreEpochMs)
|
|
513
|
-
);
|
|
514
|
-
} catch {
|
|
515
|
-
return false;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async function waitForActionPluginPrecondition() {
|
|
520
|
-
const waitStartedAt = process.hrtime.bigint();
|
|
521
|
-
while (!stopping && !actionPluginPreconditionReady()) await delay(100);
|
|
522
|
-
if (stopping) throw new Error('MIAODA_SERVER_RUNTIME_STOPPING');
|
|
523
|
-
const waitDurationMs = Number(durationMs(waitStartedAt).toFixed(3));
|
|
524
|
-
emitRuntimePhase('action_plugin_wait', waitStartedAt, {
|
|
525
|
-
actionPluginReadyFile,
|
|
526
|
-
ownership: 'shared-parent',
|
|
527
|
-
});
|
|
528
|
-
return waitDurationMs;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
async function waitForDependencies() {
|
|
532
|
-
while (!stopping) {
|
|
533
|
-
if (dependenciesAreReady()) return;
|
|
534
|
-
await delay(100);
|
|
535
|
-
}
|
|
536
|
-
throw new Error('MIAODA_SERVER_RUNTIME_STOPPING');
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async function waitForSourceChange() {
|
|
540
|
-
const waitStartedAt = process.hrtime.bigint();
|
|
541
|
-
await new Promise((resolve, reject) => {
|
|
542
|
-
const watchers = [];
|
|
543
|
-
const close = () => {
|
|
544
|
-
clearTimeout(sourceHandoffTimer);
|
|
545
|
-
for (const watcher of watchers) watcher.close();
|
|
546
|
-
sourceHandoffWatcher = undefined;
|
|
547
|
-
};
|
|
548
|
-
const trigger = () => {
|
|
549
|
-
clearTimeout(sourceHandoffTimer);
|
|
550
|
-
sourceHandoffTimer = setTimeout(() => {
|
|
551
|
-
close();
|
|
552
|
-
resolve();
|
|
553
|
-
}, 120);
|
|
554
|
-
};
|
|
555
|
-
try {
|
|
556
|
-
for (const relative of ['server', 'shared']) {
|
|
557
|
-
const directory = path.join(projectRoot, relative);
|
|
558
|
-
if (!fs.existsSync(directory)) continue;
|
|
559
|
-
watchers.push(
|
|
560
|
-
fs.watch(directory, { recursive: true }, (_event, filename) => {
|
|
561
|
-
if (!filename || !/\.(?:[cm]?[jt]sx?|json)$/.test(filename)) {
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
trigger();
|
|
565
|
-
})
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
if (watchers.length === 0) {
|
|
569
|
-
reject(new Error('MIAODA_NEST_SOURCE_WATCH_ROOT_MISSING'));
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
sourceHandoffWatcher = { close };
|
|
573
|
-
} catch (error) {
|
|
574
|
-
close();
|
|
575
|
-
reject(error);
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
emitRuntimePhase('source_handoff_wait', waitStartedAt, {
|
|
579
|
-
sourceHandoffMode,
|
|
580
|
-
reason: 'source-change',
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function publishActionPluginPrecondition() {
|
|
585
|
-
try {
|
|
586
|
-
const marker = JSON.parse(fs.readFileSync(actionPluginReadyFile, 'utf8'));
|
|
587
|
-
if (
|
|
588
|
-
marker?.schemaVersion === 1 &&
|
|
589
|
-
(!expectedRestoreEpochMs ||
|
|
590
|
-
String(marker.restoreEpochMs || '') === expectedRestoreEpochMs)
|
|
591
|
-
) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
} catch {}
|
|
595
|
-
fs.mkdirSync(path.dirname(actionPluginReadyFile), { recursive: true });
|
|
596
|
-
const temporaryFile = `${actionPluginReadyFile}.${process.pid}.tmp`;
|
|
597
|
-
fs.writeFileSync(
|
|
598
|
-
temporaryFile,
|
|
599
|
-
`${JSON.stringify({
|
|
600
|
-
schemaVersion: 1,
|
|
601
|
-
restoreEpochMs: expectedRestoreEpochMs,
|
|
602
|
-
status: 'completed-by-cache-runtime',
|
|
603
|
-
atEpochMs: Date.now(),
|
|
604
|
-
})}\n`,
|
|
605
|
-
{ mode: 0o600 }
|
|
606
|
-
);
|
|
607
|
-
fs.renameSync(temporaryFile, actionPluginReadyFile);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
async function initializeActionPlugins() {
|
|
611
|
-
if (actionPluginsInitialized) return;
|
|
612
|
-
if (skipActionPluginInit) {
|
|
613
|
-
actionPluginsInitialized = true;
|
|
614
|
-
process.stdout.write(
|
|
615
|
-
`${JSON.stringify({
|
|
616
|
-
event: 'miaoda_action_plugin_init_skipped',
|
|
617
|
-
reason: 'shared-parent-initialized',
|
|
618
|
-
})}\n`
|
|
619
|
-
);
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
const cli = [
|
|
623
|
-
process.env.MIAODA_FULLSTACK_CLI_BIN,
|
|
624
|
-
path.join(
|
|
625
|
-
projectRoot,
|
|
626
|
-
'node_modules',
|
|
627
|
-
'@lark-apaas',
|
|
628
|
-
'fullstack-cli',
|
|
629
|
-
'bin',
|
|
630
|
-
'cli.js'
|
|
631
|
-
),
|
|
632
|
-
'/usr/lib/node_modules/@lark-apaas/fullstack-cli/bin/cli.js',
|
|
633
|
-
].find(candidate => candidate && fs.existsSync(candidate));
|
|
634
|
-
if (!cli) {
|
|
635
|
-
process.stdout.write(
|
|
636
|
-
`${JSON.stringify({ event: 'miaoda_action_plugin_init_skipped', reason: 'project-cli-missing' })}\n`
|
|
637
|
-
);
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const startedAt = process.hrtime.bigint();
|
|
641
|
-
actionPluginInitChild = spawn(
|
|
642
|
-
process.execPath,
|
|
643
|
-
[cli, 'action-plugin', 'init'],
|
|
644
|
-
{
|
|
645
|
-
cwd: projectRoot,
|
|
646
|
-
detached: true,
|
|
647
|
-
stdio: 'inherit',
|
|
648
|
-
env: { ...process.env },
|
|
649
|
-
}
|
|
650
|
-
);
|
|
651
|
-
const code = await new Promise(resolve => {
|
|
652
|
-
actionPluginInitChild.once('exit', exitCode => resolve(exitCode ?? 1));
|
|
653
|
-
actionPluginInitChild.once('error', () => resolve(1));
|
|
654
|
-
});
|
|
655
|
-
actionPluginInitChild = undefined;
|
|
656
|
-
process.stdout.write(
|
|
657
|
-
`${JSON.stringify({
|
|
658
|
-
event: 'miaoda_action_plugin_init_finished',
|
|
659
|
-
code,
|
|
660
|
-
durationMs: Number(durationMs(startedAt).toFixed(3)),
|
|
661
|
-
})}\n`
|
|
662
|
-
);
|
|
663
|
-
if (code !== 0) {
|
|
664
|
-
process.stderr.write(
|
|
665
|
-
`${JSON.stringify({
|
|
666
|
-
event: 'miaoda_action_plugin_init_failed',
|
|
667
|
-
code,
|
|
668
|
-
continueStartup: true,
|
|
669
|
-
})}\n`
|
|
670
|
-
);
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
actionPluginsInitialized = true;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
async function drainCacheRuntime() {
|
|
677
|
-
const deadline = Date.now() + cacheDrainTimeoutMs;
|
|
678
|
-
while (!stopping && cacheUpstream.inflight > 0 && Date.now() < deadline) {
|
|
679
|
-
await delay(25);
|
|
680
|
-
}
|
|
681
|
-
cacheRetired = true;
|
|
682
|
-
terminate(cacheChild);
|
|
683
|
-
await waitForChildExit(cacheChild, 1_000);
|
|
684
|
-
process.stdout.write(
|
|
685
|
-
`${JSON.stringify({
|
|
686
|
-
event: 'miaoda_nest_cache_runtime_retired',
|
|
687
|
-
remainingInflight: cacheUpstream.inflight,
|
|
688
|
-
})}\n`
|
|
689
|
-
);
|
|
690
|
-
pruneSupersededGenerations();
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
async function promoteSourceRuntime() {
|
|
694
|
-
await waitForDependencies();
|
|
695
|
-
if (sourceHandoffMode === 'on-source-change') {
|
|
696
|
-
await waitForSourceChange();
|
|
697
|
-
}
|
|
698
|
-
if (stopping) return;
|
|
699
|
-
const startedAt = process.hrtime.bigint();
|
|
700
|
-
const sourceRuntime = fileURLToPath(
|
|
701
|
-
new URL('./server-startup-runtime.mjs', import.meta.url)
|
|
702
|
-
);
|
|
703
|
-
if (!fs.existsSync(sourceRuntime)) {
|
|
704
|
-
throw new Error('MIAODA_NEST_SOURCE_RUNTIME_MISSING');
|
|
705
|
-
}
|
|
706
|
-
process.stdout.write(
|
|
707
|
-
`${JSON.stringify({
|
|
708
|
-
event: 'miaoda_nest_source_runtime_starting',
|
|
709
|
-
dependencyReadyFile,
|
|
710
|
-
sourceMode: 'workspace',
|
|
711
|
-
})}\n`
|
|
712
|
-
);
|
|
713
|
-
await initializeActionPlugins();
|
|
714
|
-
publishActionPluginPrecondition();
|
|
715
|
-
if (stopping) return;
|
|
716
|
-
sourceChild = spawn(process.execPath, [sourceRuntime], {
|
|
717
|
-
cwd: projectRoot,
|
|
718
|
-
detached: true,
|
|
719
|
-
stdio: 'inherit',
|
|
720
|
-
env: {
|
|
721
|
-
...process.env,
|
|
722
|
-
MIAODA_CACHE_BOOTSTRAP_ENABLED: 'false',
|
|
723
|
-
MIAODA_NEST_START_MODE: 'workspace',
|
|
724
|
-
MIAODA_NEST_SKIP_ACTION_PLUGIN_INIT: 'true',
|
|
725
|
-
MIAODA_WORKSPACE_READY_FILE: dependencyReadyFile,
|
|
726
|
-
SERVER_HOST: sourceUpstream.host,
|
|
727
|
-
SERVER_PORT: String(sourceUpstream.port),
|
|
728
|
-
},
|
|
729
|
-
});
|
|
730
|
-
let sourceExit;
|
|
731
|
-
let sourcePromoted = false;
|
|
732
|
-
const sourceExitPromise = new Promise((_, reject) => {
|
|
733
|
-
sourceChild.once('exit', (code, signal) => {
|
|
734
|
-
sourceExit = { code, signal };
|
|
735
|
-
if (stopping) return;
|
|
736
|
-
process.stderr.write(
|
|
737
|
-
`${JSON.stringify({ event: 'miaoda_nest_source_runtime_exited', code, signal })}\n`
|
|
738
|
-
);
|
|
739
|
-
if (sourcePromoted) {
|
|
740
|
-
// The outer dev supervisor restarts this bridge. It first restores the
|
|
741
|
-
// already-sealed cache worker, then promotes the dependency-backed worker.
|
|
742
|
-
process.exit(code || 1);
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
reject(
|
|
746
|
-
new Error(
|
|
747
|
-
`MIAODA_NEST_SOURCE_RUNTIME_EXITED_BEFORE_READY: code=${code} signal=${signal}`
|
|
748
|
-
)
|
|
749
|
-
);
|
|
750
|
-
});
|
|
751
|
-
sourceChild.once('error', reject);
|
|
752
|
-
});
|
|
753
|
-
await Promise.race([
|
|
754
|
-
waitForPort(sourceUpstream.host, sourceUpstream.port, sourceReadyTimeoutMs),
|
|
755
|
-
sourceExitPromise,
|
|
756
|
-
]);
|
|
757
|
-
sourcePromoted = true;
|
|
758
|
-
if (sourceExit || !childIsRunning(sourceChild)) {
|
|
759
|
-
throw new Error('MIAODA_NEST_SOURCE_RUNTIME_EXITED_DURING_PROMOTION');
|
|
760
|
-
}
|
|
761
|
-
if (stopping) return;
|
|
762
|
-
activeUpstream = sourceUpstream;
|
|
763
|
-
process.stdout.write(
|
|
764
|
-
`${JSON.stringify({
|
|
765
|
-
event: 'miaoda_nest_source_runtime_ready',
|
|
766
|
-
pid: sourceChild.pid,
|
|
767
|
-
supervisorPid: process.pid,
|
|
768
|
-
serviceId,
|
|
769
|
-
port: publicPort,
|
|
770
|
-
upstreamPort: sourceUpstream.port,
|
|
771
|
-
sourceMode: 'workspace',
|
|
772
|
-
cutoverDurationMs: Number(durationMs(startedAt).toFixed(3)),
|
|
773
|
-
})}\n`
|
|
774
|
-
);
|
|
775
|
-
void drainCacheRuntime();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
async function close(signal) {
|
|
779
|
-
if (stopping) return;
|
|
780
|
-
stopping = true;
|
|
781
|
-
clearTimeout(sourceHandoffTimer);
|
|
782
|
-
sourceHandoffWatcher?.close();
|
|
783
|
-
process.stdout.write(
|
|
784
|
-
`${JSON.stringify({ event: 'miaoda_nest_runtime_stopping', signal })}\n`
|
|
785
|
-
);
|
|
786
|
-
proxy.close();
|
|
787
|
-
terminate(actionPluginInitChild);
|
|
788
|
-
terminate(sourceChild);
|
|
789
|
-
terminate(cacheChild);
|
|
790
|
-
setTimeout(() => process.exit(0), 250).unref();
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
794
|
-
process.once(signal, () => void close(signal));
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const bundleListenWaitStartedAt = process.hrtime.bigint();
|
|
798
|
-
await waitForPort(cacheUpstream.host, cacheUpstream.port);
|
|
799
|
-
emitRuntimePhase('bundle_listen_wait', bundleListenWaitStartedAt, {
|
|
800
|
-
cacheUpstreamPort: cacheUpstream.port,
|
|
801
|
-
});
|
|
802
|
-
const bundleStartupDurationMs = Number(
|
|
803
|
-
durationMs(runtimeStartedAt).toFixed(3)
|
|
804
|
-
);
|
|
805
|
-
process.stdout.write(
|
|
806
|
-
`${JSON.stringify({
|
|
807
|
-
event: 'miaoda_nest_bundle_runtime_ready',
|
|
808
|
-
pid: cacheChild.pid,
|
|
809
|
-
supervisorPid: process.pid,
|
|
810
|
-
generationId: validated.generation.generationId,
|
|
811
|
-
upstreamHost: cacheUpstream.host,
|
|
812
|
-
upstreamPort: cacheUpstream.port,
|
|
813
|
-
atEpochMs: Date.now(),
|
|
814
|
-
startupDurationMs: bundleStartupDurationMs,
|
|
815
|
-
publicReadyBlockedBy: actionPluginPreconditionReady()
|
|
816
|
-
? null
|
|
817
|
-
: 'action-plugin',
|
|
818
|
-
})}\n`
|
|
819
|
-
);
|
|
820
|
-
const actionPluginWaitDurationMs = await waitForActionPluginPrecondition();
|
|
821
|
-
const listenWaitStartedAt = process.hrtime.bigint();
|
|
822
|
-
await listen(proxy, publicHost, publicPort);
|
|
823
|
-
emitRuntimePhase('listen_wait', listenWaitStartedAt, {
|
|
824
|
-
port: publicPort,
|
|
825
|
-
cacheUpstreamPort: cacheUpstream.port,
|
|
826
|
-
});
|
|
827
|
-
publishServingReadyMarker(servingReadyMarker);
|
|
828
|
-
process.stdout.write(
|
|
829
|
-
`${JSON.stringify({
|
|
830
|
-
event: 'miaoda_nest_cache_runtime_ready',
|
|
831
|
-
pid: cacheChild.pid,
|
|
832
|
-
supervisorPid: process.pid,
|
|
833
|
-
serviceId,
|
|
834
|
-
generationId: validated.generation.generationId,
|
|
835
|
-
port: publicPort,
|
|
836
|
-
bundleStartupDurationMs,
|
|
837
|
-
actionPluginWaitDurationMs,
|
|
838
|
-
startupDurationMs: Number(durationMs(runtimeStartedAt).toFixed(3)),
|
|
839
|
-
})}\n`
|
|
840
|
-
);
|
|
841
|
-
async function promoteSourceRuntimeWithRetry() {
|
|
842
|
-
try {
|
|
843
|
-
await promoteSourceRuntime();
|
|
844
|
-
} catch (error) {
|
|
845
|
-
if (stopping) return;
|
|
846
|
-
terminate(sourceChild);
|
|
847
|
-
await waitForChildExit(sourceChild, 1_000);
|
|
848
|
-
cacheRetired = true;
|
|
849
|
-
proxy.close();
|
|
850
|
-
terminate(cacheChild);
|
|
851
|
-
process.stderr.write(
|
|
852
|
-
`${JSON.stringify({
|
|
853
|
-
event: 'miaoda_nest_source_runtime_promotion_failed',
|
|
854
|
-
cacheServicePreserved: false,
|
|
855
|
-
fallback: 'original-preview-retry',
|
|
856
|
-
error: error instanceof Error ? error.message : String(error),
|
|
857
|
-
})}\n`
|
|
858
|
-
);
|
|
859
|
-
process.exitCode = 1;
|
|
860
|
-
setTimeout(() => process.exit(1), 250).unref();
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
void promoteSourceRuntimeWithRetry();
|
|
865
|
-
await new Promise(() => {});
|