@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.
@@ -1,466 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'node:fs';
4
- import http from 'node:http';
5
- import net from 'node:net';
6
- import path from 'node:path';
7
- import { spawn } from 'node:child_process';
8
-
9
- const projectRoot = path.resolve(
10
- process.env.MIAODA_WORKSPACE_ROOT || process.cwd()
11
- );
12
- const workspaceReadyFile = path.resolve(
13
- process.env.MIAODA_WORKSPACE_READY_FILE || '/tmp/event/WORKSPACE_READY'
14
- );
15
- const clientHost = process.env.CLIENT_DEV_HOST || '0.0.0.0';
16
- const clientPort = Number(process.env.CLIENT_DEV_PORT || 8080);
17
- const serverHost = process.env.SERVER_HOST || '0.0.0.0';
18
- const serverPort = Number(process.env.SERVER_PORT || 3000);
19
- const internalViteHost = process.env.MIAODA_VITE_INTERNAL_HOST || '127.0.0.1';
20
- const vite8RuntimeEnabled = process.env.MIAODA_VITE8_RUNTIME_ENABLED === 'true';
21
- const devBundleEnabled = process.env.MIAODA_VITE_DEV_BUNDLE_ENABLED === 'true';
22
- if (devBundleEnabled && !vite8RuntimeEnabled) {
23
- throw new Error('MIAODA_VITE_DEV_BUNDLE_REQUIRES_VITE8');
24
- }
25
- const viteDepsCacheEnabled =
26
- process.env.MIAODA_VITE_DEPS_CACHE_ENABLED || 'true';
27
- if (viteDepsCacheEnabled !== 'true' && viteDepsCacheEnabled !== 'false') {
28
- throw new Error(
29
- `MIAODA_VITE_DEPS_CACHE_ENABLED_INVALID: ${viteDepsCacheEnabled}`
30
- );
31
- }
32
- const viteRuntimeConfig = path.join(
33
- projectRoot,
34
- 'scripts',
35
- 'vite-runtime.config.mjs'
36
- );
37
- const viteEphemeralCacheDir =
38
- process.env.MIAODA_VITE_EPHEMERAL_CACHE_DIR ||
39
- '/tmp/miaoda-vite-deps-ephemeral';
40
- const bundledDevOutputFile = path.join(
41
- projectRoot,
42
- 'dist',
43
- 'client',
44
- 'index.html'
45
- );
46
- const vite8Bin = path.resolve(
47
- process.env.MIAODA_VITE8_BIN ||
48
- '/opt/miaoda/preview-runtime/node_modules/vite/bin/vite.js'
49
- );
50
- // pair_ready 的就绪口径由 MIAODA_PAIR_READY_GATE 控制:
51
- // 'true'(默认)= 等 Vite + Nest 双就绪('vite-and-nest');
52
- // 'false' = 只等 Vite 就绪('vite'),对齐 legacy 预览链路的放行语义。
53
- // 关闭 gate 时 Nest 就绪仍被观测并照常发 nest_listen_ready 事件,
54
- // 保证不同口径下的阶段埋点可横向对比。
55
- const pairReadyGate =
56
- (process.env.MIAODA_PAIR_READY_GATE || 'true') !== 'false';
57
- const readinessScope = pairReadyGate ? 'vite-and-nest' : 'vite';
58
- const nestStartMode = process.env.MIAODA_NEST_START_MODE || 'workspace';
59
- const runtimeStartedAtEpochMs = Date.now();
60
- const startedAt = process.hrtime.bigint();
61
- let stopping = false;
62
- let child;
63
- let proxyServer;
64
- let pairReady = false;
65
-
66
- function durationMs(since = startedAt) {
67
- return Number(process.hrtime.bigint() - since) / 1_000_000;
68
- }
69
-
70
- function emit(phase, since, details = {}) {
71
- const atEpochMs = Date.now();
72
- const phaseDurationMs = Number(durationMs(since).toFixed(3));
73
- process.stdout.write(
74
- `${JSON.stringify({
75
- event: 'miaoda_workspace_runtime_phase',
76
- runtime: 'vite',
77
- phase,
78
- atEpochMs,
79
- phaseStartedAtEpochMs: Number((atEpochMs - phaseDurationMs).toFixed(3)),
80
- runtimeStartedAtEpochMs,
81
- durationMs: phaseDurationMs,
82
- ...details,
83
- })}\n`
84
- );
85
- }
86
-
87
- function delay(ms) {
88
- return new Promise(resolve => setTimeout(resolve, ms));
89
- }
90
-
91
- function workspaceDependenciesReady() {
92
- try {
93
- return (
94
- fs.statSync(workspaceReadyFile).isFile() &&
95
- fs.statSync(path.join(projectRoot, 'node_modules')).isDirectory()
96
- );
97
- } catch {
98
- return false;
99
- }
100
- }
101
-
102
- function resolveVite8Runtime() {
103
- if (!vite8RuntimeEnabled) return null;
104
- if (!fs.existsSync(vite8Bin) || fs.lstatSync(vite8Bin).isSymbolicLink()) {
105
- throw new Error(`MIAODA_VITE8_BIN_INVALID: ${vite8Bin}`);
106
- }
107
- const packageFile = path.resolve(vite8Bin, '..', '..', 'package.json');
108
- const packageJson = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
109
- const version = String(packageJson.version || '');
110
- if (!version.startsWith('8.')) {
111
- throw new Error(`MIAODA_VITE8_VERSION_INVALID: ${version}`);
112
- }
113
- return { bin: vite8Bin, version };
114
- }
115
-
116
- async function waitForDependencies() {
117
- const waitStartedAt = process.hrtime.bigint();
118
- while (!stopping && !workspaceDependenciesReady()) await delay(100);
119
- if (stopping) throw new Error('MIAODA_WORKSPACE_CLIENT_STOPPING');
120
- emit('dependency_wait', waitStartedAt, { workspaceReadyFile });
121
- }
122
-
123
- function isBundledDevDocumentReady() {
124
- try {
125
- const html = fs.readFileSync(bundledDevOutputFile, 'utf8');
126
- const forbiddenEntries = [
127
- '/@vite/client',
128
- '/@react-refresh',
129
- '/client/src/index.tsx',
130
- '/client/src/main.tsx',
131
- '/src/index.tsx',
132
- '/src/main.tsx',
133
- '/@runtime.js',
134
- ];
135
- return (
136
- html.includes('/assets/') &&
137
- !forbiddenEntries.some(entry => html.includes(entry))
138
- );
139
- } catch {
140
- return false;
141
- }
142
- }
143
-
144
- async function waitForBundledDevDocument() {
145
- const waitStartedAt = process.hrtime.bigint();
146
- const deadline = Date.now() + 60_000;
147
- while (!stopping && Date.now() < deadline) {
148
- if (child && (child.exitCode !== null || child.signalCode !== null)) {
149
- throw new Error('MIAODA_WORKSPACE_VITE_EXITED_BEFORE_BUNDLED_DEV_READY');
150
- }
151
- if (isBundledDevDocumentReady()) {
152
- emit('bundled_dev_ready', waitStartedAt, {
153
- outputFile: bundledDevOutputFile,
154
- });
155
- return;
156
- }
157
- await delay(50);
158
- }
159
- throw new Error('MIAODA_VITE_BUNDLED_DEV_READY_TIMEOUT');
160
- }
161
-
162
- function probeHost(host) {
163
- if (!host || host === '0.0.0.0') return '127.0.0.1';
164
- if (host === '::' || host === '[::]') return '::1';
165
- return host.replace(/^\[|\]$/g, '');
166
- }
167
-
168
- async function reservePort() {
169
- const configured = Number(process.env.MIAODA_VITE_INTERNAL_PORT || 0);
170
- if (
171
- Number.isSafeInteger(configured) &&
172
- configured > 0 &&
173
- configured <= 65_535
174
- ) {
175
- return configured;
176
- }
177
- return await new Promise((resolve, reject) => {
178
- const server = net.createServer();
179
- server.once('error', reject);
180
- server.listen(0, internalViteHost, () => {
181
- const address = server.address();
182
- const port = address && typeof address !== 'string' ? address.port : 0;
183
- server.close(error => (error ? reject(error) : resolve(port)));
184
- });
185
- });
186
- }
187
-
188
- async function waitForPort(host, port, phase) {
189
- const listenStartedAt = process.hrtime.bigint();
190
- const deadline = Date.now() + 60_000;
191
- const targetHost = probeHost(host);
192
- while (!stopping && Date.now() < deadline) {
193
- if (child && (child.exitCode !== null || child.signalCode !== null)) {
194
- throw new Error('MIAODA_WORKSPACE_VITE_EXITED_BEFORE_READY');
195
- }
196
- const connected = await new Promise(resolve => {
197
- const socket = net.createConnection({ host: targetHost, port });
198
- socket.once('connect', () => {
199
- socket.destroy();
200
- resolve(true);
201
- });
202
- socket.once('error', () => resolve(false));
203
- });
204
- if (connected) {
205
- emit(phase, listenStartedAt, {
206
- host,
207
- port,
208
- });
209
- return;
210
- }
211
- await delay(100);
212
- }
213
- throw new Error(`MIAODA_WORKSPACE_SERVICE_READY_TIMEOUT: ${phase}`);
214
- }
215
-
216
- function rejectBeforePair(response) {
217
- response.writeHead(503, {
218
- 'content-type': 'text/plain; charset=utf-8',
219
- 'cache-control': 'no-store',
220
- 'retry-after': '1',
221
- connection: 'close',
222
- });
223
- response.end('preview services are starting');
224
- }
225
-
226
- function proxyHttp(request, response, internalVitePort) {
227
- if (!pairReady) {
228
- rejectBeforePair(response);
229
- return;
230
- }
231
- const upstream = http.request(
232
- {
233
- host: internalViteHost,
234
- port: internalVitePort,
235
- method: request.method,
236
- path: request.url,
237
- headers: request.headers,
238
- },
239
- upstreamResponse => {
240
- response.writeHead(
241
- upstreamResponse.statusCode || 502,
242
- upstreamResponse.statusMessage,
243
- upstreamResponse.headers
244
- );
245
- upstreamResponse.pipe(response);
246
- }
247
- );
248
- upstream.once('error', error => {
249
- if (!response.headersSent) {
250
- response.writeHead(502, {
251
- 'content-type': 'text/plain; charset=utf-8',
252
- 'cache-control': 'no-store',
253
- });
254
- }
255
- response.end(`vite upstream unavailable: ${error.code || 'unknown'}`);
256
- });
257
- request.pipe(upstream);
258
- }
259
-
260
- function proxyUpgrade(request, socket, head, internalVitePort) {
261
- if (!pairReady) {
262
- socket.destroy();
263
- return;
264
- }
265
- const upstream = net.createConnection(
266
- { host: internalViteHost, port: internalVitePort },
267
- () => {
268
- const lines = [
269
- `${request.method} ${request.url} HTTP/${request.httpVersion}`,
270
- ];
271
- for (let index = 0; index < request.rawHeaders.length; index += 2) {
272
- lines.push(
273
- `${request.rawHeaders[index]}: ${request.rawHeaders[index + 1]}`
274
- );
275
- }
276
- upstream.write(`${lines.join('\r\n')}\r\n\r\n`);
277
- if (head.length > 0) upstream.write(head);
278
- socket.pipe(upstream).pipe(socket);
279
- }
280
- );
281
- upstream.once('error', () => socket.destroy());
282
- socket.once('error', () => upstream.destroy());
283
- }
284
-
285
- async function listenPublicProxy(internalVitePort) {
286
- proxyServer = http.createServer((request, response) =>
287
- proxyHttp(request, response, internalVitePort)
288
- );
289
- proxyServer.on('upgrade', (request, socket, head) =>
290
- proxyUpgrade(request, socket, head, internalVitePort)
291
- );
292
- await new Promise((resolve, reject) => {
293
- proxyServer.once('error', reject);
294
- proxyServer.listen(clientPort, clientHost, resolve);
295
- });
296
- emit('public_gate_listen', startedAt, {
297
- host: clientHost,
298
- port: clientPort,
299
- readinessScope,
300
- });
301
- }
302
-
303
- function terminate(signal = 'SIGTERM') {
304
- if (!child?.pid) return;
305
- try {
306
- process.kill(-child.pid, signal);
307
- } catch {
308
- try {
309
- child.kill(signal);
310
- } catch {}
311
- }
312
- }
313
-
314
- async function close(signal) {
315
- if (stopping) return;
316
- stopping = true;
317
- pairReady = false;
318
- proxyServer?.close();
319
- terminate(signal);
320
- setTimeout(() => terminate('SIGKILL'), 1_000).unref();
321
- }
322
-
323
- for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
324
- process.once(signal, () => void close(signal));
325
- }
326
-
327
- if (
328
- !Number.isSafeInteger(clientPort) ||
329
- clientPort <= 0 ||
330
- clientPort > 65_535
331
- ) {
332
- throw new Error('MIAODA_WORKSPACE_VITE_PORT_INVALID');
333
- }
334
- if (
335
- !Number.isSafeInteger(serverPort) ||
336
- serverPort <= 0 ||
337
- serverPort > 65_535
338
- ) {
339
- throw new Error('MIAODA_WORKSPACE_NEST_PORT_INVALID');
340
- }
341
-
342
- await waitForDependencies();
343
- const internalVitePort = await reservePort();
344
- await listenPublicProxy(internalVitePort);
345
- if (nestStartMode === 'bundle-handoff') {
346
- await waitForPort(serverHost, serverPort, 'nest_precedes_vite');
347
- }
348
- const spawnStartedAt = process.hrtime.bigint();
349
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
350
- const vite8Runtime = resolveVite8Runtime();
351
- if (
352
- !fs.existsSync(viteRuntimeConfig) ||
353
- fs.lstatSync(viteRuntimeConfig).isSymbolicLink()
354
- ) {
355
- throw new Error(`MIAODA_VITE_RUNTIME_CONFIG_INVALID: ${viteRuntimeConfig}`);
356
- }
357
- if (viteDepsCacheEnabled === 'false') {
358
- fs.rmSync(viteEphemeralCacheDir, { recursive: true, force: true });
359
- }
360
- if (devBundleEnabled) {
361
- fs.rmSync(bundledDevOutputFile, { force: true });
362
- }
363
- const viteCliArgs = [
364
- '--config',
365
- viteRuntimeConfig,
366
- '--host',
367
- internalViteHost,
368
- '--port',
369
- String(internalVitePort),
370
- '--strictPort',
371
- ];
372
- if (devBundleEnabled) {
373
- viteCliArgs.push('--experimentalBundle');
374
- }
375
- const viteMaxOldSpaceSizeMb = devBundleEnabled ? 2048 : undefined;
376
- const viteNodeOptions = viteMaxOldSpaceSizeMb
377
- ? [process.env.NODE_OPTIONS, `--max-old-space-size=${viteMaxOldSpaceSizeMb}`]
378
- .filter(Boolean)
379
- .join(' ')
380
- : process.env.NODE_OPTIONS;
381
- const viteCommand = vite8Runtime ? process.execPath : npmCommand;
382
- const viteArgs = vite8Runtime
383
- ? [vite8Runtime.bin, ...viteCliArgs]
384
- : ['run', 'dev:client', '--', ...viteCliArgs];
385
- child = spawn(viteCommand, viteArgs, {
386
- cwd: projectRoot,
387
- detached: true,
388
- stdio: 'inherit',
389
- env: {
390
- ...process.env,
391
- NODE_OPTIONS: viteNodeOptions,
392
- MIAODA_CACHE_BOOTSTRAP_ENABLED: 'false',
393
- MIAODA_VITE8_RUNTIME_ENABLED: String(vite8RuntimeEnabled),
394
- MIAODA_VITE_DEV_BUNDLE_ENABLED: String(devBundleEnabled),
395
- MIAODA_VITE_DEPS_CACHE_ENABLED: viteDepsCacheEnabled,
396
- MIAODA_VITE_EPHEMERAL_CACHE_DIR: viteEphemeralCacheDir,
397
- MIAODA_VITE_INTERNAL_HOST: internalViteHost,
398
- MIAODA_VITE_INTERNAL_PORT: String(internalVitePort),
399
- },
400
- });
401
- emit('process_spawn', spawnStartedAt, {
402
- pid: child.pid,
403
- vite8RuntimeEnabled,
404
- vite8Version: vite8Runtime?.version,
405
- devBundleEnabled,
406
- viteMaxOldSpaceSizeMb,
407
- viteDepsCacheEnabled: viteDepsCacheEnabled === 'true',
408
- viteRuntimeConfig,
409
- viteEphemeralCacheDir:
410
- viteDepsCacheEnabled === 'false' ? viteEphemeralCacheDir : undefined,
411
- internalViteHost,
412
- internalVitePort,
413
- });
414
-
415
- const exitPromise = new Promise((resolve, reject) => {
416
- child.once('error', reject);
417
- child.once('exit', (code, signal) => resolve({ code, signal }));
418
- });
419
- const viteListenWait = waitForPort(
420
- internalViteHost,
421
- internalVitePort,
422
- 'vite_listen_ready'
423
- );
424
- const viteRuntimeReadiness = [viteListenWait];
425
- if (devBundleEnabled) {
426
- viteRuntimeReadiness.push(waitForBundledDevDocument());
427
- }
428
- let readinessWait = Promise.all(viteRuntimeReadiness);
429
- if (pairReadyGate) {
430
- readinessWait = Promise.all([
431
- ...viteRuntimeReadiness,
432
- waitForPort(serverHost, serverPort, 'nest_listen_ready'),
433
- ]);
434
- } else {
435
- // Gate 关闭时 pair_ready 只跟随 Vite;Nest 就绪在后台继续观测,
436
- // nest_listen_ready 事件照常发出,失败不阻断预览放行。
437
- waitForPort(serverHost, serverPort, 'nest_listen_ready').catch(() => {});
438
- }
439
- await Promise.race([
440
- readinessWait,
441
- exitPromise.then(exit => {
442
- throw new Error(
443
- `MIAODA_WORKSPACE_VITE_EXITED_BEFORE_READY: code=${exit.code} signal=${exit.signal}`
444
- );
445
- }),
446
- ]);
447
- pairReady = true;
448
- emit('pair_ready', startedAt, {
449
- host: clientHost,
450
- port: clientPort,
451
- internalViteHost,
452
- internalVitePort,
453
- serverHost,
454
- serverPort,
455
- readinessScope,
456
- });
457
- emit('startup', startedAt, {
458
- pid: child.pid,
459
- port: clientPort,
460
- readinessScope,
461
- });
462
-
463
- const exit = await exitPromise;
464
- const wasStopping = stopping;
465
- await close(exit.signal || 'SIGTERM');
466
- process.exit(exit.code ?? (wasStopping ? 0 : 1));