@lark-apaas/fullstack-cli 1.1.59-alpha.20260719124023 → 1.1.59-alpha.3

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.59-alpha.20260719124023",
3
+ "version": "1.1.59-alpha.3",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,15 +34,12 @@
34
34
  "@lark-apaas/http-client": "^0.1.7",
35
35
  "@lydell/node-pty": "1.1.0",
36
36
  "@vercel/nft": "^0.30.3",
37
- "acorn": "^8.16.0",
38
- "acorn-walk": "^8.3.5",
39
37
  "commander": "^13.0.0",
40
38
  "debug": "^4.4.3",
41
39
  "dotenv": "^16.0.0",
42
40
  "drizzle-kit": "0.31.5",
43
41
  "drizzle-orm": "0.44.6",
44
42
  "es-toolkit": "^1.44.0",
45
- "esbuild": "^0.27.0",
46
43
  "inflection": "^3.0.2",
47
44
  "pinyin-pro": "^3.27.0",
48
45
  "postgres": "^3.4.3",
@@ -6,13 +6,10 @@ const path = require('path');
6
6
  const { spawn, execFileSync, execSync } = require('child_process');
7
7
  const readline = require('readline');
8
8
  const {
9
- applyPreviewArtifactHostOverrides,
10
9
  buildListeningPortLookupArgs,
11
- createDeferredTypecheckGate,
12
10
  createPreviewPhaseReporter,
13
11
  normalizeTcpPort,
14
12
  parseTscWatchSummary,
15
- resolvePreviewArtifactPorts,
16
13
  resolvePreviewServerCommand,
17
14
  waitForTcpReady,
18
15
  } = require('./preview-startup-timing.cjs');
@@ -42,34 +39,17 @@ loadEnv();
42
39
 
43
40
  // ── Configuration ─────────────────────────────────────────────────────────────
44
41
  const LOG_DIR = process.env.LOG_DIR || 'logs';
45
- const MAX_RESTART_COUNT =
46
- process.env.MAX_RESTART_COUNT != null && process.env.MAX_RESTART_COUNT !== ''
47
- ? parseInt(process.env.MAX_RESTART_COUNT, 10)
48
- : Infinity;
42
+ const MAX_RESTART_COUNT = process.env.MAX_RESTART_COUNT != null && process.env.MAX_RESTART_COUNT !== ''
43
+ ? parseInt(process.env.MAX_RESTART_COUNT, 10)
44
+ : Infinity;
49
45
  const RESTART_DELAY = parseInt(process.env.RESTART_DELAY, 10) || 2;
50
46
  const MAX_DELAY = 8;
51
- const configuredServerPort = normalizeTcpPort(
52
- process.env.SERVER_PORT,
53
- 3000,
54
- 'SERVER_PORT'
55
- );
56
- const configuredClientDevPort = normalizeTcpPort(
47
+ const SERVER_PORT = normalizeTcpPort(process.env.SERVER_PORT, 3000, 'SERVER_PORT');
48
+ const CLIENT_DEV_PORT = normalizeTcpPort(
57
49
  process.env.CLIENT_DEV_PORT,
58
50
  8080,
59
51
  'CLIENT_DEV_PORT'
60
52
  );
61
- const previewArtifactPorts = resolvePreviewArtifactPorts({
62
- marker_file:
63
- process.env.PREVIEW_ARTIFACT_RUNTIME_MARKER ||
64
- '/tmp/preview-artifact/runtime.json',
65
- server_port: configuredServerPort,
66
- client_port: configuredClientDevPort,
67
- });
68
- applyPreviewArtifactHostOverrides(previewArtifactPorts, process.env);
69
- const SERVER_PORT = previewArtifactPorts.server_port;
70
- const CLIENT_DEV_PORT = previewArtifactPorts.client_port;
71
- process.env.SERVER_PORT = String(SERVER_PORT);
72
- process.env.CLIENT_DEV_PORT = String(CLIENT_DEV_PORT);
73
53
 
74
54
  fs.mkdirSync(LOG_DIR, { recursive: true });
75
55
 
@@ -82,16 +62,11 @@ const devLogFd = fs.openSync(devLogPath, 'a');
82
62
  function timestamp() {
83
63
  const now = new Date();
84
64
  return (
85
- now.getFullYear() +
86
- '-' +
87
- String(now.getMonth() + 1).padStart(2, '0') +
88
- '-' +
89
- String(now.getDate()).padStart(2, '0') +
90
- ' ' +
91
- String(now.getHours()).padStart(2, '0') +
92
- ':' +
93
- String(now.getMinutes()).padStart(2, '0') +
94
- ':' +
65
+ now.getFullYear() + '-' +
66
+ String(now.getMonth() + 1).padStart(2, '0') + '-' +
67
+ String(now.getDate()).padStart(2, '0') + ' ' +
68
+ String(now.getHours()).padStart(2, '0') + ':' +
69
+ String(now.getMinutes()).padStart(2, '0') + ':' +
95
70
  String(now.getSeconds()).padStart(2, '0')
96
71
  );
97
72
  }
@@ -105,37 +80,25 @@ const STDOUT_MAX_INFLIGHT = 1000;
105
80
  function writeOutput(msg) {
106
81
  // File first and synchronously — read-logs reads this file; it must never be gated
107
82
  // by the terminal consumer.
108
- try {
109
- fs.writeSync(devStdLogFd, msg);
110
- } catch {}
83
+ try { fs.writeSync(devStdLogFd, msg); } catch {}
111
84
  // stdout mirror via async fs.write: if process.stdout is a pty/pipe whose consumer
112
85
  // stalls, the block happens on the libuv threadpool, NOT the event loop — so the
113
86
  // synchronous log-FILE writes above (and subsequent readline callbacks) keep running.
114
87
  // Drop overflow when too many writes are already pending (best-effort mirror).
115
- if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) {
116
- return;
117
- }
88
+ if (_stdoutInFlight >= STDOUT_MAX_INFLIGHT) { return; }
118
89
  _stdoutInFlight++;
119
- try {
120
- fs.write(1, msg, () => {
121
- _stdoutInFlight--;
122
- });
123
- } catch {
124
- _stdoutInFlight--;
125
- }
90
+ try { fs.write(1, msg, () => { _stdoutInFlight--; }); } catch { _stdoutInFlight--; }
126
91
  }
127
92
 
128
93
  const previewPhaseReporter = createPreviewPhaseReporter({
129
- write: line => writeOutput(`${line}\n`),
94
+ write: (line) => writeOutput(`${line}\n`),
130
95
  });
131
96
 
132
97
  /** Structured event log → terminal + dev.std.log + dev.log */
133
98
  function logEvent(level, name, message) {
134
99
  const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
135
100
  writeOutput(msg);
136
- try {
137
- fs.writeSync(devLogFd, msg);
138
- } catch {}
101
+ try { fs.writeSync(devLogFd, msg); } catch {}
139
102
  }
140
103
 
141
104
  // ── Process group management ──────────────────────────────────────────────────
@@ -186,22 +149,14 @@ let stopping = false;
186
149
  const managedProcesses = []; // { name, pid, child }
187
150
 
188
151
  function sleep(ms) {
189
- return new Promise(resolve => setTimeout(resolve, ms));
152
+ return new Promise((resolve) => setTimeout(resolve, ms));
190
153
  }
191
154
 
192
155
  /**
193
156
  * Start and supervise a process with auto-restart and log piping.
194
157
  * Returns a promise that resolves when the process loop ends.
195
158
  */
196
- function startProcess({
197
- name,
198
- command,
199
- args,
200
- cleanupPort,
201
- phaseDetail = {},
202
- onReady,
203
- onOutputLine,
204
- }) {
159
+ function startProcess({ name, command, args, cleanupPort, phaseDetail = {}, onReady }) {
205
160
  const logFilePath = path.join(LOG_DIR, `${name}.std.log`);
206
161
  const logFd = fs.openSync(logFilePath, 'a');
207
162
 
@@ -220,8 +175,8 @@ function startProcess({
220
175
  portCleanup.error
221
176
  ? `Port ${cleanupPort} preflight failed: ${portCleanup.error}`
222
177
  : stalePids.length > 0
223
- ? `Killed stale processes on port ${cleanupPort} before first spawn: ${stalePids.join(' ')}`
224
- : `Port ${cleanupPort} is clear before first spawn`
178
+ ? `Killed stale processes on port ${cleanupPort} before first spawn: ${stalePids.join(' ')}`
179
+ : `Port ${cleanupPort} is clear before first spawn`
225
180
  );
226
181
  previewPhaseReporter.emit(
227
182
  name === 'server' ? 'backend_port_preflight' : 'client_port_preflight',
@@ -249,11 +204,7 @@ function startProcess({
249
204
 
250
205
  const startTime = Date.now();
251
206
  const attempt = restartCount + 1;
252
- logEvent(
253
- 'INFO',
254
- name,
255
- `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`
256
- );
207
+ logEvent('INFO', name, `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`);
257
208
  const processPhase =
258
209
  name === 'server'
259
210
  ? 'backend_process_spawn'
@@ -307,13 +258,9 @@ function startProcess({
307
258
 
308
259
  // Pipe stdout and stderr through readline for timestamped logging
309
260
  let typecheckSummaryReported = false;
310
- const pipeLines = stream => {
311
- const rl = readline.createInterface({
312
- input: stream,
313
- crlfDelay: Infinity,
314
- });
315
- rl.on('line', line => {
316
- if (onOutputLine) onOutputLine(line);
261
+ const pipeLines = (stream) => {
262
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
263
+ rl.on('line', (line) => {
317
264
  if (name === 'server-typecheck' && !typecheckSummaryReported) {
318
265
  const summary = parseTscWatchSummary(line);
319
266
  if (summary) {
@@ -331,9 +278,7 @@ function startProcess({
331
278
  }
332
279
  }
333
280
  const msg = `[${timestamp()}] [${name}] ${line}\n`;
334
- try {
335
- fs.writeSync(logFd, msg);
336
- } catch {}
281
+ try { fs.writeSync(logFd, msg); } catch {}
337
282
  writeOutput(msg);
338
283
  });
339
284
  };
@@ -344,8 +289,8 @@ function startProcess({
344
289
  // NOTE: must use 'exit', not 'close'. With shell:true, grandchild processes
345
290
  // (e.g. nest's server) inherit stdout pipes. 'close' won't fire until ALL
346
291
  // pipe holders exit, causing dev.js to hang when npm/nest dies but server survives.
347
- const exitCode = await new Promise(resolve => {
348
- child.on('exit', code => resolve(code ?? 1));
292
+ const exitCode = await new Promise((resolve) => {
293
+ child.on('exit', (code) => resolve(code ?? 1));
349
294
  child.on('error', () => resolve(1));
350
295
  });
351
296
 
@@ -370,11 +315,7 @@ function startProcess({
370
315
  );
371
316
  }
372
317
  if (orphans.length > 0) {
373
- logEvent(
374
- 'WARN',
375
- name,
376
- `Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`
377
- );
318
+ logEvent('WARN', name, `Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`);
378
319
  await sleep(500);
379
320
  }
380
321
  }
@@ -384,38 +325,21 @@ function startProcess({
384
325
  const runDuration = (Date.now() - startTime) / 1000;
385
326
  if (runDuration >= 60) {
386
327
  restartCount = 0;
387
- logEvent(
388
- 'INFO',
389
- name,
390
- `Ran for ${Math.round(runDuration)}s, resetting restart counter`
391
- );
328
+ logEvent('INFO', name, `Ran for ${Math.round(runDuration)}s, resetting restart counter`);
392
329
  } else {
393
330
  restartCount++;
394
331
  }
395
332
  if (restartCount >= MAX_RESTART_COUNT) {
396
- logEvent(
397
- 'ERROR',
398
- name,
399
- `Max restart count (${MAX_RESTART_COUNT}) reached, giving up`
400
- );
333
+ logEvent('ERROR', name, `Max restart count (${MAX_RESTART_COUNT}) reached, giving up`);
401
334
  break;
402
335
  }
403
336
 
404
- const delay = Math.min(
405
- RESTART_DELAY * (1 << Math.max(0, restartCount - 1)),
406
- MAX_DELAY
407
- );
408
- logEvent(
409
- 'WARN',
410
- name,
411
- `Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`
412
- );
337
+ const delay = Math.min(RESTART_DELAY * (1 << Math.max(0, restartCount - 1)), MAX_DELAY);
338
+ logEvent('WARN', name, `Process exited with code ${exitCode}, restarting (${restartCount}/${MAX_RESTART_COUNT}) in ${delay}s...`);
413
339
  await sleep(delay * 1000);
414
340
  }
415
341
 
416
- try {
417
- fs.closeSync(logFd);
418
- } catch {}
342
+ try { fs.closeSync(logFd); } catch {}
419
343
  };
420
344
 
421
345
  return run();
@@ -445,11 +369,7 @@ async function cleanup() {
445
369
  // Force kill any remaining
446
370
  for (const entry of managedProcesses) {
447
371
  if (entry.pid) {
448
- logEvent(
449
- 'WARN',
450
- 'main',
451
- `Force killing process group (PGID: ${entry.pid})`
452
- );
372
+ logEvent('WARN', 'main', `Force killing process group (PGID: ${entry.pid})`);
453
373
  killProcessGroup(entry.pid, 'SIGKILL');
454
374
  }
455
375
  }
@@ -458,22 +378,14 @@ async function cleanup() {
458
378
  for (const port of [SERVER_PORT, CLIENT_DEV_PORT]) {
459
379
  const portCleanup = killOrphansByPort(port);
460
380
  if (portCleanup.error) {
461
- logEvent(
462
- 'ERROR',
463
- 'main',
464
- `Port ${port} cleanup failed: ${portCleanup.error}`
465
- );
381
+ logEvent('ERROR', 'main', `Port ${port} cleanup failed: ${portCleanup.error}`);
466
382
  }
467
383
  }
468
384
 
469
385
  logEvent('INFO', 'main', 'All processes stopped');
470
386
 
471
- try {
472
- fs.closeSync(devStdLogFd);
473
- } catch {}
474
- try {
475
- fs.closeSync(devLogFd);
476
- } catch {}
387
+ try { fs.closeSync(devStdLogFd); } catch {}
388
+ try { fs.closeSync(devLogFd); } catch {}
477
389
 
478
390
  process.exit(0);
479
391
  }
@@ -494,22 +406,7 @@ function cleanStaleDist() {
494
406
  // ── Main ──────────────────────────────────────────────────────────────────────
495
407
  async function main() {
496
408
  logEvent('INFO', 'main', '========== Dev session started ==========');
497
- previewPhaseReporter.emit('dev_orchestrator_start', 'success', {
498
- exact: true,
499
- });
500
- if (previewArtifactPorts.cache_active) {
501
- logEvent(
502
- 'INFO',
503
- 'main',
504
- `Cached generation ${previewArtifactPorts.generation_id} is serving; starting fresh services on shadow ports ${SERVER_PORT}/${CLIENT_DEV_PORT}`
505
- );
506
- previewPhaseReporter.emit('preview_artifact_shadow_start', 'success', {
507
- exact: true,
508
- generation_id: previewArtifactPorts.generation_id,
509
- fresh_server_port: SERVER_PORT,
510
- fresh_client_port: CLIENT_DEV_PORT,
511
- });
512
- }
409
+ previewPhaseReporter.emit('dev_orchestrator_start', 'success', { exact: true });
513
410
 
514
411
  cleanStaleDist();
515
412
 
@@ -521,10 +418,7 @@ async function main() {
521
418
  exact: true,
522
419
  });
523
420
  try {
524
- execSync('fullstack-cli action-plugin init', {
525
- cwd: PROJECT_ROOT,
526
- stdio: 'inherit',
527
- });
421
+ execSync('fullstack-cli action-plugin init', { cwd: PROJECT_ROOT, stdio: 'inherit' });
528
422
  previewPhaseReporter.emit('action_plugin_init', 'success', {
529
423
  duration_ms: Date.now() - actionPluginStartedAt,
530
424
  exact: true,
@@ -536,9 +430,7 @@ async function main() {
536
430
  exact: true,
537
431
  error: error instanceof Error ? error.message : String(error),
538
432
  });
539
- writeOutput(
540
- '⚠️ Action plugin initialization failed, continuing anyway...\n\n'
541
- );
433
+ writeOutput('⚠️ Action plugin initialization failed, continuing anyway...\n\n');
542
434
  }
543
435
 
544
436
  // Start server and client
@@ -558,20 +450,18 @@ async function main() {
558
450
  reason: serverCommand.reason,
559
451
  missing_modules: serverCommand.missing_modules,
560
452
  });
561
- const typecheckGate = createDeferredTypecheckGate();
562
453
  let serverTypecheckStarted = false;
563
- const startServerTypecheck = trigger => {
454
+ const startServerTypecheck = () => {
564
455
  if (serverTypecheckStarted || !serverCommand.type_check_command) return;
565
456
  try {
566
457
  logEvent(
567
458
  'INFO',
568
459
  'server-typecheck',
569
- `Starting deferred server type checker after ${trigger}`
460
+ 'Starting deferred server type checker after backend TCP ready'
570
461
  );
571
462
  previewPhaseReporter.emit('backend_typecheck_deferred_start', 'success', {
572
463
  exact: true,
573
464
  type_check_mode: serverCommand.type_check_mode,
574
- trigger,
575
465
  });
576
466
  const typecheckPromise = startProcess({
577
467
  name: 'server-typecheck',
@@ -600,30 +490,6 @@ async function main() {
600
490
  );
601
491
  }
602
492
  };
603
- const handleBackendReady = () => {
604
- if (!serverCommand.type_check_command) return;
605
- const decision = typecheckGate.onBackendReady();
606
- if (decision.should_start) {
607
- startServerTypecheck(decision.reason);
608
- return;
609
- }
610
- logEvent(
611
- 'INFO',
612
- 'server-typecheck',
613
- 'Skipped type checking during initial preview startup'
614
- );
615
- previewPhaseReporter.emit('backend_typecheck_skipped', 'success', {
616
- exact: true,
617
- type_check_mode: serverCommand.type_check_mode,
618
- reason: decision.reason,
619
- next_trigger: 'source_change_or_backend_restart',
620
- });
621
- };
622
- const handleServerOutput = line => {
623
- if (!serverCommand.type_check_command || serverTypecheckStarted) return;
624
- const decision = typecheckGate.onCompilerOutput(line);
625
- if (decision?.should_start) startServerTypecheck(decision.reason);
626
- };
627
493
  const serverPromise = startProcess({
628
494
  name: 'server',
629
495
  command: serverCommand.command,
@@ -635,8 +501,7 @@ async function main() {
635
501
  type_check_mode: serverCommand.type_check_mode,
636
502
  compiler_selection_reason: serverCommand.reason,
637
503
  },
638
- onReady: handleBackendReady,
639
- onOutputLine: handleServerOutput,
504
+ onReady: startServerTypecheck,
640
505
  });
641
506
 
642
507
  const clientPromise = startProcess({
@@ -657,7 +522,7 @@ async function main() {
657
522
  }
658
523
  }
659
524
 
660
- main().catch(err => {
525
+ main().catch((err) => {
661
526
  console.error('Fatal error:', err);
662
527
  process.exit(1);
663
528
  });
@@ -70,53 +70,6 @@ function buildListeningPortLookupArgs(port) {
70
70
  return [`-tiTCP:${validatedPort}`, '-sTCP:LISTEN'];
71
71
  }
72
72
 
73
- function resolvePreviewArtifactPorts(options) {
74
- const fallback = {
75
- cache_active: false,
76
- server_port: normalizeTcpPort(options.server_port, 3000, 'SERVER_PORT'),
77
- client_port: normalizeTcpPort(options.client_port, 8080, 'CLIENT_DEV_PORT'),
78
- generation_id: '',
79
- };
80
- try {
81
- const markerStat = fs.lstatSync(options.marker_file);
82
- if (!markerStat.isFile() || markerStat.isSymbolicLink()) return fallback;
83
- const marker = JSON.parse(fs.readFileSync(options.marker_file, 'utf8'));
84
- if (
85
- marker?.schemaVersion !== 1 ||
86
- !['cached-serving', 'fresh-serving'].includes(marker?.status) ||
87
- !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(marker?.generationId || '')
88
- ) {
89
- return fallback;
90
- }
91
- return {
92
- cache_active: true,
93
- server_port: normalizeTcpPort(
94
- marker.freshServerPort,
95
- fallback.server_port,
96
- 'freshServerPort'
97
- ),
98
- client_port: normalizeTcpPort(
99
- marker.freshClientPort,
100
- fallback.client_port,
101
- 'freshClientPort'
102
- ),
103
- generation_id: marker.generationId,
104
- };
105
- } catch {
106
- return fallback;
107
- }
108
- }
109
-
110
- function applyPreviewArtifactHostOverrides(ports, environment) {
111
- if (!ports.cache_active) return false;
112
- // Cached runtime and switcher use an explicit IPv4 loopback endpoint. Pin
113
- // both shadow services to the same address family; Linux commonly resolves
114
- // `localhost` to IPv6-only ::1, which otherwise makes cutover impossible.
115
- environment.SERVER_HOST = '127.0.0.1';
116
- environment.CLIENT_DEV_HOST = '127.0.0.1';
117
- return true;
118
- }
119
-
120
73
  function hasRuntimeCompilerPluginConsumer(projectRoot, sourceRoot) {
121
74
  const root = path.resolve(projectRoot, sourceRoot || 'src');
122
75
  const stack = [root];
@@ -316,38 +269,6 @@ function resolvePreviewServerCommand(options = {}) {
316
269
  };
317
270
  }
318
271
 
319
- function createDeferredTypecheckGate() {
320
- let initialBackendReadySeen = false;
321
-
322
- return {
323
- onBackendReady() {
324
- if (!initialBackendReadySeen) {
325
- initialBackendReadySeen = true;
326
- return {
327
- should_start: false,
328
- reason: 'initial_preview_startup',
329
- };
330
- }
331
- return {
332
- should_start: true,
333
- reason: 'backend_restart',
334
- };
335
- },
336
- onCompilerOutput(line) {
337
- if (
338
- !initialBackendReadySeen ||
339
- !/Successfully compiled:\s+\d+\s+files?\s+with\s+swc\b/i.test(line)
340
- ) {
341
- return null;
342
- }
343
- return {
344
- should_start: true,
345
- reason: 'source_change',
346
- };
347
- },
348
- };
349
- }
350
-
351
272
  function parseTscWatchSummary(line) {
352
273
  const match = /Found\s+(\d+)\s+errors?\.\s+Watching for file changes\./i.exec(
353
274
  line
@@ -447,13 +368,10 @@ async function waitForTcpReady(options) {
447
368
  }
448
369
 
449
370
  module.exports = {
450
- applyPreviewArtifactHostOverrides,
451
371
  buildListeningPortLookupArgs,
452
- createDeferredTypecheckGate,
453
372
  createPreviewPhaseReporter,
454
373
  normalizeTcpPort,
455
374
  parseTscWatchSummary,
456
- resolvePreviewArtifactPorts,
457
375
  resolvePreviewServerCommand,
458
376
  waitForTcpReady,
459
377
  };