@lark-apaas/fullstack-cli 1.1.59-alpha.20260722050835 → 1.1.59-alpha.20260722071909
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
CHANGED
package/templates/scripts/dev.js
CHANGED
|
@@ -8,9 +8,7 @@ const readline = require('readline');
|
|
|
8
8
|
const {
|
|
9
9
|
applyPreviewArtifactHostOverrides,
|
|
10
10
|
buildListeningPortLookupArgs,
|
|
11
|
-
closePreviewPhaseReporterWithRetry,
|
|
12
11
|
createDeferredTypecheckGate,
|
|
13
|
-
createPreviewPhaseFrameDecoder,
|
|
14
12
|
createPreviewPhaseReporter,
|
|
15
13
|
normalizeTcpPort,
|
|
16
14
|
parseTscWatchSummary,
|
|
@@ -201,7 +199,6 @@ function startProcess({
|
|
|
201
199
|
args,
|
|
202
200
|
cleanupPort,
|
|
203
201
|
phaseDetail = {},
|
|
204
|
-
phaseChannel = false,
|
|
205
202
|
onReady,
|
|
206
203
|
onOutputLine,
|
|
207
204
|
}) {
|
|
@@ -239,23 +236,12 @@ function startProcess({
|
|
|
239
236
|
}
|
|
240
237
|
|
|
241
238
|
while (!stopping) {
|
|
242
|
-
const childEnv = { ...process.env };
|
|
243
|
-
// The parent is the only outbox writer. Child processes receive a narrow
|
|
244
|
-
// producer channel, never the filesystem destination.
|
|
245
|
-
delete childEnv.MIAODA_PREVIEW_PHASE_OUTBOX_ROOT;
|
|
246
|
-
if (phaseChannel) {
|
|
247
|
-
childEnv.MIAODA_PREVIEW_PHASE_FD = '3';
|
|
248
|
-
} else {
|
|
249
|
-
delete childEnv.MIAODA_PREVIEW_PHASE_FD;
|
|
250
|
-
}
|
|
251
239
|
const child = spawn(command, args, {
|
|
252
240
|
detached: true,
|
|
253
|
-
stdio:
|
|
254
|
-
? ['ignore', 'pipe', 'pipe', 'pipe']
|
|
255
|
-
: ['ignore', 'pipe', 'pipe'],
|
|
241
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
256
242
|
shell: true,
|
|
257
243
|
cwd: PROJECT_ROOT,
|
|
258
|
-
env:
|
|
244
|
+
env: { ...process.env },
|
|
259
245
|
});
|
|
260
246
|
|
|
261
247
|
entry.pid = child.pid;
|
|
@@ -321,24 +307,13 @@ function startProcess({
|
|
|
321
307
|
|
|
322
308
|
// Pipe stdout and stderr through readline for timestamped logging
|
|
323
309
|
let typecheckSummaryReported = false;
|
|
324
|
-
|
|
325
|
-
const pipeLines = (stream, allowPhaseFallback = false) => {
|
|
310
|
+
const pipeLines = stream => {
|
|
326
311
|
const rl = readline.createInterface({
|
|
327
312
|
input: stream,
|
|
328
313
|
crlfDelay: Infinity,
|
|
329
314
|
});
|
|
330
315
|
rl.on('line', line => {
|
|
331
316
|
if (onOutputLine) onOutputLine(line);
|
|
332
|
-
const legacyPhaseCandidate =
|
|
333
|
-
allowPhaseFallback && line.startsWith('[MiaodaPreviewPhase] ');
|
|
334
|
-
if (legacyPhaseCandidate && !phaseReported) {
|
|
335
|
-
phaseReported = previewPhaseReporter.ingest(line, {
|
|
336
|
-
producer: 'vite_plugin',
|
|
337
|
-
allowed_phases: ['vite_http_listening'],
|
|
338
|
-
attempt,
|
|
339
|
-
allow_legacy_stdout: true,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
317
|
if (name === 'server-typecheck' && !typecheckSummaryReported) {
|
|
343
318
|
const summary = parseTscWatchSummary(line);
|
|
344
319
|
if (summary) {
|
|
@@ -359,38 +334,11 @@ function startProcess({
|
|
|
359
334
|
try {
|
|
360
335
|
fs.writeSync(logFd, msg);
|
|
361
336
|
} catch {}
|
|
362
|
-
|
|
363
|
-
// enter the shared log. Keep the raw child claim quarantined in the
|
|
364
|
-
// component log so collectors never observe two representations.
|
|
365
|
-
if (!legacyPhaseCandidate) writeOutput(msg);
|
|
337
|
+
writeOutput(msg);
|
|
366
338
|
});
|
|
367
339
|
};
|
|
368
|
-
if (child.stdout) pipeLines(child.stdout
|
|
340
|
+
if (child.stdout) pipeLines(child.stdout);
|
|
369
341
|
if (child.stderr) pipeLines(child.stderr);
|
|
370
|
-
if (phaseChannel && child.stdio[3]) {
|
|
371
|
-
let oversizedPhaseFrameReported = false;
|
|
372
|
-
const phaseDecoder = createPreviewPhaseFrameDecoder({
|
|
373
|
-
max_bytes: 16 * 1024,
|
|
374
|
-
on_oversized_frame: () => {
|
|
375
|
-
if (oversizedPhaseFrameReported) return;
|
|
376
|
-
oversizedPhaseFrameReported = true;
|
|
377
|
-
logEvent(
|
|
378
|
-
'WARN',
|
|
379
|
-
name,
|
|
380
|
-
'Dropped an oversized preview phase producer frame'
|
|
381
|
-
);
|
|
382
|
-
},
|
|
383
|
-
on_frame: line => {
|
|
384
|
-
if (phaseReported) return;
|
|
385
|
-
phaseReported = previewPhaseReporter.ingest(line, {
|
|
386
|
-
producer: 'vite_plugin',
|
|
387
|
-
allowed_phases: ['vite_http_listening'],
|
|
388
|
-
attempt,
|
|
389
|
-
});
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
child.stdio[3].on('data', chunk => phaseDecoder.push(chunk));
|
|
393
|
-
}
|
|
394
342
|
|
|
395
343
|
// Wait for the direct child to exit.
|
|
396
344
|
// NOTE: must use 'exit', not 'close'. With shell:true, grandchild processes
|
|
@@ -520,16 +468,6 @@ async function cleanup() {
|
|
|
520
468
|
|
|
521
469
|
logEvent('INFO', 'main', 'All processes stopped');
|
|
522
470
|
|
|
523
|
-
const previewPhaseSealed =
|
|
524
|
-
await closePreviewPhaseReporterWithRetry(previewPhaseReporter);
|
|
525
|
-
if (!previewPhaseSealed) {
|
|
526
|
-
logEvent(
|
|
527
|
-
'ERROR',
|
|
528
|
-
'main',
|
|
529
|
-
'Preview phase outbox did not seal after bounded shutdown retries'
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
471
|
try {
|
|
534
472
|
fs.closeSync(devStdLogFd);
|
|
535
473
|
} catch {}
|
|
@@ -537,7 +475,7 @@ async function cleanup() {
|
|
|
537
475
|
fs.closeSync(devLogFd);
|
|
538
476
|
} catch {}
|
|
539
477
|
|
|
540
|
-
process.exit(
|
|
478
|
+
process.exit(0);
|
|
541
479
|
}
|
|
542
480
|
|
|
543
481
|
process.on('SIGTERM', cleanup);
|
|
@@ -706,7 +644,6 @@ async function main() {
|
|
|
706
644
|
command: 'npm',
|
|
707
645
|
args: ['run', 'dev:client'],
|
|
708
646
|
cleanupPort: CLIENT_DEV_PORT,
|
|
709
|
-
phaseChannel: true,
|
|
710
647
|
});
|
|
711
648
|
|
|
712
649
|
writeOutput(`📋 Dev processes running. Press Ctrl+C to stop.\n`);
|
|
@@ -720,14 +657,7 @@ async function main() {
|
|
|
720
657
|
}
|
|
721
658
|
}
|
|
722
659
|
|
|
723
|
-
main().catch(
|
|
660
|
+
main().catch(err => {
|
|
724
661
|
console.error('Fatal error:', err);
|
|
725
|
-
const previewPhaseSealed =
|
|
726
|
-
await closePreviewPhaseReporterWithRetry(previewPhaseReporter);
|
|
727
|
-
if (!previewPhaseSealed) {
|
|
728
|
-
console.error(
|
|
729
|
-
'Preview phase outbox did not seal after bounded fatal-exit retries'
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
662
|
process.exit(1);
|
|
733
663
|
});
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
4
|
const net = require('net');
|
|
6
5
|
const path = require('path');
|
|
7
|
-
const {
|
|
8
|
-
MAX_EVENT_BYTES,
|
|
9
|
-
createFallbackSessionID,
|
|
10
|
-
createPreviewPhaseOutbox,
|
|
11
|
-
} = require('./preview-phase-outbox.cjs');
|
|
12
6
|
|
|
13
7
|
const PREFIX = '[MiaodaPreviewPhase] ';
|
|
14
|
-
const RUN_ID_PATTERN = /^prv_[A-Za-z0-9_-]{1,
|
|
8
|
+
const RUN_ID_PATTERN = /^prv_[A-Za-z0-9_-]{1,128}$/;
|
|
15
9
|
|
|
16
10
|
function resolveAppId(env) {
|
|
17
11
|
const basePath = env.CLIENT_BASE_PATH || '';
|
|
@@ -19,457 +13,30 @@ function resolveAppId(env) {
|
|
|
19
13
|
return match ? match[1] : env.MIAODA_APP_ID || '';
|
|
20
14
|
}
|
|
21
15
|
|
|
22
|
-
function createPreviewPhaseFrameDecoder(options = {}) {
|
|
23
|
-
const maxBytes = options.max_bytes || 16 * 1024;
|
|
24
|
-
const onFrame = options.on_frame;
|
|
25
|
-
const onOversizedFrame = options.on_oversized_frame || (() => {});
|
|
26
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
27
|
-
throw new Error('preview phase frame max_bytes must be a positive integer');
|
|
28
|
-
}
|
|
29
|
-
if (typeof onFrame !== 'function') {
|
|
30
|
-
throw new Error('preview phase frame on_frame callback is required');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let pending = [];
|
|
34
|
-
let pendingBytes = 0;
|
|
35
|
-
let droppingOversizedFrame = false;
|
|
36
|
-
|
|
37
|
-
function reset() {
|
|
38
|
-
pending = [];
|
|
39
|
-
pendingBytes = 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function push(chunk) {
|
|
43
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
44
|
-
let offset = 0;
|
|
45
|
-
while (offset < buffer.length) {
|
|
46
|
-
const newline = buffer.indexOf(0x0a, offset);
|
|
47
|
-
const end = newline < 0 ? buffer.length : newline;
|
|
48
|
-
const segment = buffer.subarray(offset, end);
|
|
49
|
-
|
|
50
|
-
if (!droppingOversizedFrame) {
|
|
51
|
-
if (pendingBytes + segment.length > maxBytes) {
|
|
52
|
-
reset();
|
|
53
|
-
droppingOversizedFrame = true;
|
|
54
|
-
onOversizedFrame();
|
|
55
|
-
} else if (segment.length > 0) {
|
|
56
|
-
pending.push(segment);
|
|
57
|
-
pendingBytes += segment.length;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (newline < 0) return;
|
|
62
|
-
if (!droppingOversizedFrame) {
|
|
63
|
-
onFrame(Buffer.concat(pending, pendingBytes).toString('utf8'));
|
|
64
|
-
}
|
|
65
|
-
reset();
|
|
66
|
-
droppingOversizedFrame = false;
|
|
67
|
-
offset = newline + 1;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { push };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
16
|
function createPreviewPhaseReporter(options = {}) {
|
|
75
17
|
const env = options.env || process.env;
|
|
76
18
|
const write = options.write || (line => process.stdout.write(`${line}\n`));
|
|
77
19
|
const now = options.now || Date.now;
|
|
78
|
-
const randomUUID = options.random_uuid || crypto.randomUUID;
|
|
79
|
-
const producer = options.producer || 'dev_orchestrator';
|
|
80
|
-
const onDiagnostic =
|
|
81
|
-
options.on_diagnostic ||
|
|
82
|
-
(diagnostic => {
|
|
83
|
-
try {
|
|
84
|
-
process.stderr.write(
|
|
85
|
-
`[MiaodaPreviewOutbox] ${JSON.stringify(diagnostic)}\n`
|
|
86
|
-
);
|
|
87
|
-
} catch {
|
|
88
|
-
// Diagnostics must never block preview startup.
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
const producerInstanceID = randomUUID();
|
|
92
|
-
const fallbackSessionID =
|
|
93
|
-
options.fallback_session_id || createFallbackSessionID(randomUUID);
|
|
94
|
-
const runId = env.MIAODA_PREVIEW_RUN_ID || '';
|
|
95
|
-
let outbox = null;
|
|
96
|
-
let outboxInitializationError = null;
|
|
97
|
-
if (RUN_ID_PATTERN.test(runId)) {
|
|
98
|
-
try {
|
|
99
|
-
outbox = createPreviewPhaseOutbox({
|
|
100
|
-
root: options.outbox_root || env.MIAODA_PREVIEW_PHASE_OUTBOX_ROOT,
|
|
101
|
-
run_id: runId,
|
|
102
|
-
producer_epoch: env.MIAODA_PREVIEW_PHASE_PRODUCER_EPOCH,
|
|
103
|
-
expected_instance_id: env.MIAODA_PREVIEW_PHASE_INSTANCE_ID,
|
|
104
|
-
writer_instance_id: producerInstanceID,
|
|
105
|
-
producer_instance_id: producerInstanceID,
|
|
106
|
-
fallback_session_id: fallbackSessionID,
|
|
107
|
-
process_start_ticks: options.process_start_ticks,
|
|
108
|
-
get_process_start_ticks: options.get_process_start_ticks,
|
|
109
|
-
fs: options.fs,
|
|
110
|
-
register_producer: options.register_producer,
|
|
111
|
-
spawn_sync: options.spawn_sync,
|
|
112
|
-
random_uuid: randomUUID,
|
|
113
|
-
now,
|
|
114
|
-
});
|
|
115
|
-
} catch (error) {
|
|
116
|
-
outboxInitializationError = error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const producerSequences = new Map();
|
|
120
|
-
let fallbackSequence = 0;
|
|
121
|
-
const startupTerminalPhases = new Set();
|
|
122
|
-
let reporterSealed = false;
|
|
123
|
-
let quiesceAcknowledged = false;
|
|
124
|
-
let terminalCloseScheduled = false;
|
|
125
|
-
let quiescePollTimer = null;
|
|
126
|
-
|
|
127
|
-
function reportDiagnostic(diagnostic) {
|
|
128
|
-
try {
|
|
129
|
-
onDiagnostic(diagnostic);
|
|
130
|
-
} catch {
|
|
131
|
-
// Diagnostics must never become a preview-startup failure mode.
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
20
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
queueMicrotask(() => {
|
|
139
|
-
terminalCloseScheduled = false;
|
|
140
|
-
if (reporterSealed) return;
|
|
141
|
-
// backend_tcp_ready's synchronous onReady callback emits the initial
|
|
142
|
-
// deferred-typecheck decision immediately after the readiness event.
|
|
143
|
-
// Closing in a microtask keeps that terminal startup decision inside the
|
|
144
|
-
// same durable generation without extending the outbox into later source
|
|
145
|
-
// changes or daemon restarts.
|
|
146
|
-
flush();
|
|
147
|
-
close();
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (outboxInitializationError) {
|
|
152
|
-
reportDiagnostic({
|
|
153
|
-
code: 'outbox_initialization_failed',
|
|
154
|
-
run_id: runId,
|
|
155
|
-
error:
|
|
156
|
-
outboxInitializationError instanceof Error
|
|
157
|
-
? outboxInitializationError.message
|
|
158
|
-
: String(outboxInitializationError),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function emitForProducer(eventProducer, phase, status, detail = {}) {
|
|
163
|
-
if (!RUN_ID_PATTERN.test(runId) || reporterSealed) return false;
|
|
21
|
+
function emit(phase, status, detail = {}) {
|
|
22
|
+
const runId = env.MIAODA_PREVIEW_RUN_ID || '';
|
|
23
|
+
if (!RUN_ID_PATTERN.test(runId)) return false;
|
|
164
24
|
const atMs = detail.at_ms == null ? now() : detail.at_ms;
|
|
165
|
-
const producerSeq = (producerSequences.get(eventProducer) || 0) + 1;
|
|
166
|
-
producerSequences.set(eventProducer, producerSeq);
|
|
167
25
|
const event = {
|
|
168
26
|
...detail,
|
|
169
27
|
schema_version: 1,
|
|
170
|
-
event_id: `${runId}:${randomUUID()}`,
|
|
171
28
|
run_id: runId,
|
|
172
29
|
app_id: resolveAppId(env),
|
|
173
30
|
sandbox_id: env.SANDBOX_ID || '',
|
|
174
31
|
phase,
|
|
175
32
|
status,
|
|
176
33
|
at_ms: atMs,
|
|
177
|
-
producer: eventProducer,
|
|
178
|
-
producer_instance_id:
|
|
179
|
-
eventProducer === producer
|
|
180
|
-
? producerInstanceID
|
|
181
|
-
: `${producerInstanceID}:${eventProducer}`,
|
|
182
|
-
producer_seq: producerSeq,
|
|
183
|
-
recorded_at_ms: now(),
|
|
184
|
-
transport: 'outbox',
|
|
185
34
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
reportDiagnostic({
|
|
189
|
-
code: 'event_rejected',
|
|
190
|
-
reason: 'event_too_large',
|
|
191
|
-
run_id: runId,
|
|
192
|
-
phase,
|
|
193
|
-
event_id: event.event_id,
|
|
194
|
-
});
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
let persisted = false;
|
|
198
|
-
// Stdout remains a diagnostic mirror. Only canonical append success or a
|
|
199
|
-
// fallback event fdatasync is delivery proof.
|
|
200
|
-
let fallbackSpoolPersisted = false;
|
|
201
|
-
if (!outbox) {
|
|
202
|
-
event.transport = 'stdout_fallback';
|
|
203
|
-
reportDiagnostic({
|
|
204
|
-
code: 'stdout_fallback_untracked',
|
|
205
|
-
run_id: runId,
|
|
206
|
-
phase,
|
|
207
|
-
event_id: event.event_id,
|
|
208
|
-
});
|
|
209
|
-
} else {
|
|
210
|
-
try {
|
|
211
|
-
persisted = outbox.append(event);
|
|
212
|
-
} catch (error) {
|
|
213
|
-
if (error?.code === 'PREVIEW_PHASE_EVENT_TOO_LARGE') {
|
|
214
|
-
reportDiagnostic({
|
|
215
|
-
code: 'event_rejected',
|
|
216
|
-
reason: 'event_too_large',
|
|
217
|
-
run_id: runId,
|
|
218
|
-
phase,
|
|
219
|
-
event_id: event.event_id,
|
|
220
|
-
});
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
const nextFallbackSequence = fallbackSequence + 1;
|
|
224
|
-
event.transport = 'fallback_spool';
|
|
225
|
-
event.fallback_session_id = fallbackSessionID;
|
|
226
|
-
event.fallback_seq = nextFallbackSequence;
|
|
227
|
-
try {
|
|
228
|
-
fallbackSpoolPersisted = outbox.append_fallback(event);
|
|
229
|
-
if (fallbackSpoolPersisted) {
|
|
230
|
-
fallbackSequence = nextFallbackSequence;
|
|
231
|
-
}
|
|
232
|
-
} catch (fallbackError) {
|
|
233
|
-
if (fallbackError?.code !== 'PREVIEW_PHASE_EVENT_TOO_LARGE') {
|
|
234
|
-
reportDiagnostic({
|
|
235
|
-
code: 'fallback_spool_failed',
|
|
236
|
-
run_id: runId,
|
|
237
|
-
phase,
|
|
238
|
-
event_id: event.event_id,
|
|
239
|
-
error:
|
|
240
|
-
fallbackError instanceof Error
|
|
241
|
-
? fallbackError.message
|
|
242
|
-
: String(fallbackError),
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
if (!fallbackSpoolPersisted) {
|
|
247
|
-
event.transport = 'stdout_fallback';
|
|
248
|
-
delete event.fallback_session_id;
|
|
249
|
-
delete event.fallback_seq;
|
|
250
|
-
reportDiagnostic({
|
|
251
|
-
code: 'stdout_fallback_untracked',
|
|
252
|
-
run_id: runId,
|
|
253
|
-
phase,
|
|
254
|
-
event_id: event.event_id,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
if (error?.code !== 'PREVIEW_PHASE_OUTBOX_DEGRADED') {
|
|
258
|
-
reportDiagnostic({
|
|
259
|
-
code: 'outbox_append_failed',
|
|
260
|
-
run_id: runId,
|
|
261
|
-
phase,
|
|
262
|
-
event_id: event.event_id,
|
|
263
|
-
error: error instanceof Error ? error.message : String(error),
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
serializedEvent = JSON.stringify(event);
|
|
269
|
-
if (Buffer.byteLength(serializedEvent, 'utf8') > MAX_EVENT_BYTES) {
|
|
270
|
-
reportDiagnostic({
|
|
271
|
-
code: 'event_rejected',
|
|
272
|
-
reason: 'event_too_large',
|
|
273
|
-
run_id: runId,
|
|
274
|
-
phase,
|
|
275
|
-
event_id: event.event_id,
|
|
276
|
-
});
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
try {
|
|
280
|
-
write(`${PREFIX}${serializedEvent}`);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
reportDiagnostic({
|
|
283
|
-
code: 'stdout_mirror_failed',
|
|
284
|
-
run_id: runId,
|
|
285
|
-
phase,
|
|
286
|
-
event_id: event.event_id,
|
|
287
|
-
error: error instanceof Error ? error.message : String(error),
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
const delivered = persisted || fallbackSpoolPersisted;
|
|
291
|
-
if (delivered && outbox?.is_local_sealed?.()) {
|
|
292
|
-
reporterSealed = true;
|
|
293
|
-
stopQuiescePolling();
|
|
294
|
-
}
|
|
295
|
-
if (
|
|
296
|
-
delivered &&
|
|
297
|
-
status !== 'start' &&
|
|
298
|
-
(phase === 'backend_tcp_ready' || phase === 'vite_http_listening')
|
|
299
|
-
) {
|
|
300
|
-
startupTerminalPhases.add(phase);
|
|
301
|
-
if (startupTerminalPhases.size === 2) {
|
|
302
|
-
scheduleTerminalClose();
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return delivered;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function emit(phase, status, detail = {}) {
|
|
309
|
-
return emitForProducer(producer, phase, status, detail);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function ingest(rawFrame, context = {}) {
|
|
313
|
-
if (
|
|
314
|
-
typeof rawFrame !== 'string' ||
|
|
315
|
-
Buffer.byteLength(rawFrame, 'utf8') > 16 * 1024
|
|
316
|
-
) {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
const isLegacyStdoutFrame =
|
|
320
|
-
context.allow_legacy_stdout === true && rawFrame.startsWith(PREFIX);
|
|
321
|
-
const serializedFrame = isLegacyStdoutFrame
|
|
322
|
-
? rawFrame.slice(PREFIX.length).trim()
|
|
323
|
-
: rawFrame;
|
|
324
|
-
let frame;
|
|
325
|
-
try {
|
|
326
|
-
frame = JSON.parse(serializedFrame);
|
|
327
|
-
} catch {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
const allowedPhases = Array.isArray(context.allowed_phases)
|
|
331
|
-
? context.allowed_phases
|
|
332
|
-
: [];
|
|
333
|
-
if (
|
|
334
|
-
(!isLegacyStdoutFrame &&
|
|
335
|
-
frame?.protocol !== 'preview_phase_producer_v1') ||
|
|
336
|
-
frame.schema_version !== 1 ||
|
|
337
|
-
frame.run_id !== runId ||
|
|
338
|
-
!allowedPhases.includes(frame.phase) ||
|
|
339
|
-
!/^(?:start|success|error|fallback|skipped|cancelled)$/.test(
|
|
340
|
-
frame.status || ''
|
|
341
|
-
) ||
|
|
342
|
-
!Number.isFinite(frame.at_ms) ||
|
|
343
|
-
frame.at_ms <= 0
|
|
344
|
-
) {
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
const detail = {
|
|
348
|
-
at_ms: frame.at_ms,
|
|
349
|
-
...(Number.isFinite(frame.duration_ms) && frame.duration_ms >= 0
|
|
350
|
-
? { duration_ms: frame.duration_ms }
|
|
351
|
-
: {}),
|
|
352
|
-
...(Number.isFinite(frame.precision_ms) && frame.precision_ms >= 0
|
|
353
|
-
? { precision_ms: frame.precision_ms }
|
|
354
|
-
: {}),
|
|
355
|
-
...(typeof frame.exact === 'boolean' ? { exact: frame.exact } : {}),
|
|
356
|
-
...(Number.isSafeInteger(context.attempt) && context.attempt > 0
|
|
357
|
-
? { attempt: context.attempt }
|
|
358
|
-
: {}),
|
|
359
|
-
};
|
|
360
|
-
return emitForProducer(
|
|
361
|
-
context.producer || 'child_process',
|
|
362
|
-
frame.phase,
|
|
363
|
-
frame.status,
|
|
364
|
-
detail
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function flush() {
|
|
369
|
-
if (reporterSealed) return true;
|
|
370
|
-
if (!outbox) return outboxInitializationError == null;
|
|
371
|
-
try {
|
|
372
|
-
return outbox.flush();
|
|
373
|
-
} catch (error) {
|
|
374
|
-
reportDiagnostic({
|
|
375
|
-
code: 'outbox_flush_failed',
|
|
376
|
-
run_id: runId,
|
|
377
|
-
error: error instanceof Error ? error.message : String(error),
|
|
378
|
-
});
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function stopQuiescePolling() {
|
|
384
|
-
if (quiescePollTimer == null) return;
|
|
385
|
-
clearInterval(quiescePollTimer);
|
|
386
|
-
quiescePollTimer = null;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function pollQuiesce() {
|
|
390
|
-
if (reporterSealed) return quiesceAcknowledged;
|
|
391
|
-
if (!outbox) return false;
|
|
392
|
-
try {
|
|
393
|
-
const quiesce = outbox.poll_quiesce();
|
|
394
|
-
if (quiesce.local_sealed) {
|
|
395
|
-
reporterSealed = true;
|
|
396
|
-
quiesceAcknowledged = quiesce.canonical_acknowledged;
|
|
397
|
-
stopQuiescePolling();
|
|
398
|
-
}
|
|
399
|
-
return quiesce.canonical_acknowledged;
|
|
400
|
-
} catch (error) {
|
|
401
|
-
reportDiagnostic({
|
|
402
|
-
code: 'outbox_quiesce_failed',
|
|
403
|
-
run_id: runId,
|
|
404
|
-
error: error instanceof Error ? error.message : String(error),
|
|
405
|
-
});
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function close() {
|
|
411
|
-
if (reporterSealed) return true;
|
|
412
|
-
if (!outbox) return outboxInitializationError == null;
|
|
413
|
-
try {
|
|
414
|
-
const sealed = outbox.close();
|
|
415
|
-
if (sealed) {
|
|
416
|
-
reporterSealed = true;
|
|
417
|
-
stopQuiescePolling();
|
|
418
|
-
}
|
|
419
|
-
return sealed;
|
|
420
|
-
} catch (error) {
|
|
421
|
-
reportDiagnostic({
|
|
422
|
-
code: 'outbox_close_failed',
|
|
423
|
-
run_id: runId,
|
|
424
|
-
error: error instanceof Error ? error.message : String(error),
|
|
425
|
-
});
|
|
426
|
-
return false;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (outbox) {
|
|
431
|
-
const intervalMs = options.quiesce_poll_interval_ms ?? 100;
|
|
432
|
-
if (Number.isSafeInteger(intervalMs) && intervalMs > 0) {
|
|
433
|
-
quiescePollTimer = setInterval(pollQuiesce, intervalMs);
|
|
434
|
-
if (typeof quiescePollTimer.unref === 'function') {
|
|
435
|
-
quiescePollTimer.unref();
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return { emit, ingest, flush, close, pollQuiesce };
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async function closePreviewPhaseReporterWithRetry(reporter, options = {}) {
|
|
444
|
-
const retryDelaysMs = options.retry_delays_ms || [0, 25, 100, 250, 500, 1000];
|
|
445
|
-
const sleep =
|
|
446
|
-
options.sleep ||
|
|
447
|
-
(delayMs => new Promise(resolve => setTimeout(resolve, delayMs)));
|
|
448
|
-
if (
|
|
449
|
-
!reporter ||
|
|
450
|
-
typeof reporter.flush !== 'function' ||
|
|
451
|
-
typeof reporter.close !== 'function' ||
|
|
452
|
-
!Array.isArray(retryDelaysMs) ||
|
|
453
|
-
retryDelaysMs.length === 0 ||
|
|
454
|
-
retryDelaysMs.some(delayMs => !Number.isSafeInteger(delayMs) || delayMs < 0)
|
|
455
|
-
) {
|
|
456
|
-
throw new Error('invalid preview phase reporter close retry configuration');
|
|
35
|
+
write(`${PREFIX}${JSON.stringify(event)}`);
|
|
36
|
+
return true;
|
|
457
37
|
}
|
|
458
38
|
|
|
459
|
-
|
|
460
|
-
if (delayMs > 0) await sleep(delayMs);
|
|
461
|
-
try {
|
|
462
|
-
reporter.flush();
|
|
463
|
-
} catch {
|
|
464
|
-
// close() repeats the durable flush and reports the authoritative result.
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
if (reporter.close()) return true;
|
|
468
|
-
} catch {
|
|
469
|
-
// A later bounded attempt may succeed after a transient filesystem fault.
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return false;
|
|
39
|
+
return { emit };
|
|
473
40
|
}
|
|
474
41
|
|
|
475
42
|
function createTscServerCommand(reason, missingModules = []) {
|
|
@@ -882,9 +449,7 @@ async function waitForTcpReady(options) {
|
|
|
882
449
|
module.exports = {
|
|
883
450
|
applyPreviewArtifactHostOverrides,
|
|
884
451
|
buildListeningPortLookupArgs,
|
|
885
|
-
closePreviewPhaseReporterWithRetry,
|
|
886
452
|
createDeferredTypecheckGate,
|
|
887
|
-
createPreviewPhaseFrameDecoder,
|
|
888
453
|
createPreviewPhaseReporter,
|
|
889
454
|
normalizeTcpPort,
|
|
890
455
|
parseTscWatchSummary,
|