@coze-arch/cli 0.0.1-alpha.a3fb1a → 0.0.1-alpha.c49cb2

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/lib/cli.js CHANGED
@@ -5,10 +5,12 @@ var commander = require('commander');
5
5
  var path = require('path');
6
6
  var fs = require('fs');
7
7
  var shelljs = require('shelljs');
8
+ var perf_hooks = require('perf_hooks');
8
9
  var fs$1 = require('fs/promises');
9
10
  var toml = require('@iarna/toml');
10
11
  var jsYaml = require('js-yaml');
11
- var perf_hooks = require('perf_hooks');
12
+ var child_process = require('child_process');
13
+ var os = require('os');
12
14
  var addFormats = require('ajv-formats');
13
15
  var Ajv = require('ajv');
14
16
  var minimist = require('minimist');
@@ -125,7 +127,7 @@ const generateTemplatesHelpText = () => {
125
127
  return lines.join('\n');
126
128
  };
127
129
 
128
- function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$4(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var LogLevel; (function (LogLevel) {
130
+ function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$3(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var LogLevel; (function (LogLevel) {
129
131
  const ERROR = 0; LogLevel[LogLevel["ERROR"] = ERROR] = "ERROR";
130
132
  const WARN = 1; LogLevel[LogLevel["WARN"] = WARN] = "WARN";
131
133
  const SUCCESS = 2; LogLevel[LogLevel["SUCCESS"] = SUCCESS] = "SUCCESS";
@@ -172,7 +174,7 @@ class Logger {
172
174
  return level;
173
175
  }
174
176
 
175
- const envLevel = _optionalChain$4([process, 'access', _ => _.env, 'access', _2 => _2.LOG_LEVEL, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]);
177
+ const envLevel = _optionalChain$3([process, 'access', _ => _.env, 'access', _2 => _2.LOG_LEVEL, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]);
176
178
  if (envLevel && envLevel in LOG_LEVEL_MAP) {
177
179
  return LOG_LEVEL_MAP[envLevel];
178
180
  }
@@ -184,7 +186,7 @@ class Logger {
184
186
  // 简单检测:Node.js 环境且支持 TTY
185
187
  return (
186
188
  typeof process !== 'undefined' &&
187
- _optionalChain$4([process, 'access', _5 => _5.stdout, 'optionalAccess', _6 => _6.isTTY]) === true &&
189
+ _optionalChain$3([process, 'access', _5 => _5.stdout, 'optionalAccess', _6 => _6.isTTY]) === true &&
188
190
  process.env.NO_COLOR === undefined
189
191
  );
190
192
  }
@@ -270,7 +272,330 @@ const createLogger = (options = {}) =>
270
272
  // 导出默认实例
271
273
  const logger = createLogger();
272
274
 
273
- function _optionalChain$3(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* eslint-disable @typescript-eslint/no-explicit-any */
275
+ /**
276
+ * 时间统计工具
277
+ */
278
+ class TimeTracker {
279
+
280
+
281
+
282
+ constructor() {
283
+ this.startTime = perf_hooks.performance.now();
284
+ this.lastTime = this.startTime;
285
+ }
286
+
287
+ /**
288
+ * 记录阶段耗时
289
+ * @param phaseName 阶段名称
290
+ */
291
+ logPhase(phaseName) {
292
+ const now = perf_hooks.performance.now();
293
+ const phaseTime = now - this.lastTime;
294
+ this.lastTime = now;
295
+
296
+ logger.verbose(`⏱ ${phaseName}: ${phaseTime.toFixed(2)}ms`);
297
+ }
298
+
299
+ /**
300
+ * 记录总耗时
301
+ */
302
+ logTotal() {
303
+ const totalTime = perf_hooks.performance.now() - this.startTime;
304
+ logger.verbose(`⏱ Total time: ${totalTime.toFixed(2)}ms`);
305
+ }
306
+
307
+ /**
308
+ * 获取当前耗时(不输出日志)
309
+ * @returns 从开始到现在的总耗时(毫秒)
310
+ */
311
+ getElapsedTime() {
312
+ return perf_hooks.performance.now() - this.startTime;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * 获取模板配置文件路径
318
+ * @returns templates.json 的绝对路径
319
+ */
320
+ const getTemplatesConfigPath = () => {
321
+ const configPath = path.resolve(getTemplatesDir(), 'templates.json');
322
+ logger.verbose(`Templates config path: ${configPath}`);
323
+ logger.verbose(`Config file exists: ${fs.existsSync(configPath)}`);
324
+ return configPath;
325
+ };
326
+
327
+ /**
328
+ * 获取模板目录路径
329
+ * @returns __templates__ 目录的绝对路径
330
+ */
331
+ const getTemplatesDir = () => {
332
+ const templatesDir = path.resolve(__dirname, './__templates__');
333
+ logger.verbose(`Templates directory: ${templatesDir}`);
334
+ logger.verbose(`Templates directory exists: ${fs.existsSync(templatesDir)}`);
335
+ return templatesDir;
336
+ };
337
+
338
+ /**
339
+ * 加载模板配置文件
340
+ * 支持 .ts 和 .js 文件(通过 sucrase 注册)
341
+ *
342
+ * @param templatePath - 模板目录路径
343
+ * @returns 模板配置对象
344
+ */
345
+
346
+ const loadTemplateConfig = async (
347
+ templatePath,
348
+ ) => {
349
+ logger.verbose(`Loading template config from: ${templatePath}`);
350
+
351
+ const tsConfigPath = path.join(templatePath, 'template.config.ts');
352
+ const jsConfigPath = path.join(templatePath, 'template.config.js');
353
+
354
+ logger.verbose('Checking for config files:');
355
+ logger.verbose(` - TypeScript: ${tsConfigPath}`);
356
+ logger.verbose(` - JavaScript: ${jsConfigPath}`);
357
+
358
+ let configPath;
359
+
360
+ const [tsExists, jsExists] = await Promise.all([
361
+ fs$1.access(tsConfigPath).then(
362
+ () => true,
363
+ () => false,
364
+ ),
365
+ fs$1.access(jsConfigPath).then(
366
+ () => true,
367
+ () => false,
368
+ ),
369
+ ]);
370
+
371
+ logger.verbose('Config file existence check:');
372
+ logger.verbose(` - template.config.ts: ${tsExists}`);
373
+ logger.verbose(` - template.config.js: ${jsExists}`);
374
+
375
+ if (tsExists) {
376
+ configPath = tsConfigPath;
377
+ } else if (jsExists) {
378
+ configPath = jsConfigPath;
379
+ } else {
380
+ throw new Error(
381
+ `Template config not found in ${templatePath}.\n` +
382
+ 'Expected: template.config.ts or template.config.js',
383
+ );
384
+ }
385
+
386
+ logger.verbose(`Using config file: ${configPath}`);
387
+
388
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, security/detect-non-literal-require -- Sucrase handles .ts files at runtime, path is validated above
389
+ const config = require(configPath);
390
+
391
+ logger.verbose('Template config loaded successfully');
392
+
393
+ return config.default || config;
394
+ };
395
+
396
+ /**
397
+ * 加载模板列表配置
398
+ *
399
+ * @param configPath - templates.json 配置文件路径
400
+ * @returns 模板列表配置
401
+ */
402
+ const loadTemplatesConfig = async (
403
+ configPath,
404
+ ) => {
405
+ logger.verbose(`Loading templates config from: ${configPath}`);
406
+
407
+ const content = await fs$1.readFile(configPath, 'utf-8');
408
+ // eslint-disable-next-line no-restricted-syntax -- Static config file loaded at build time, safeJsonParse not needed
409
+ const config = JSON.parse(content) ;
410
+
411
+ logger.verbose(
412
+ `Found ${config.templates.length} templates: ${config.templates.map(t => t.name).join(', ')}`,
413
+ );
414
+
415
+ return config;
416
+ };
417
+
418
+ /**
419
+ * 根据模板名称查找模板元信息
420
+ *
421
+ * @param templatesConfig - 模板列表配置
422
+ * @param templateName - 模板名称
423
+ * @returns 模板元信息
424
+ */
425
+ const findTemplate = (
426
+ templatesConfig,
427
+ templateName,
428
+ ) => {
429
+ const template = templatesConfig.templates.find(t => t.name === templateName);
430
+
431
+ if (!template) {
432
+ const availableTemplates = templatesConfig.templates
433
+ .map(t => t.name)
434
+ .join(', ');
435
+ throw new Error(
436
+ `Template "${templateName}" not found.\n` +
437
+ `Available templates: ${availableTemplates}\n` +
438
+ 'Use --template <name> to specify a template.',
439
+ );
440
+ }
441
+
442
+ return template;
443
+ };
444
+
445
+ /**
446
+ * 获取模板的完整路径
447
+ *
448
+ * @param basePath - 模板目录(templates.json 所在目录)
449
+ * @param templateMetadata - 模板元信息
450
+ * @returns 模板完整路径
451
+ */
452
+ const getTemplatePath = async (
453
+ basePath,
454
+ templateMetadata,
455
+ ) => {
456
+ logger.verbose('Resolving template path:');
457
+ logger.verbose(` - Base path: ${basePath}`);
458
+ logger.verbose(` - Template location: ${templateMetadata.location}`);
459
+
460
+ // location 是相对于 templates.json 文件的路径
461
+ const templatePath = path.join(basePath, templateMetadata.location);
462
+
463
+ logger.verbose(` - Resolved path: ${templatePath}`);
464
+
465
+ try {
466
+ await fs$1.access(templatePath);
467
+ logger.verbose(' - Template directory exists: ✓');
468
+ // eslint-disable-next-line @coze-arch/use-error-in-catch -- Error handling done in throw statement
469
+ } catch (e) {
470
+ logger.error(' - Template directory does not exist: ✗');
471
+ throw new Error(`Template directory not found: ${templatePath}`);
472
+ }
473
+
474
+ return templatePath;
475
+ };
476
+
477
+ /**
478
+ * 对单个模板执行 pnpm install
479
+ */
480
+ const warmupTemplate = (templatePath, templateName) => {
481
+ logger.info(`\nWarming up template: ${templateName}`);
482
+ logger.info(` Path: ${templatePath}`);
483
+
484
+ const result = shelljs.exec('pnpm install', {
485
+ cwd: templatePath,
486
+ silent: true,
487
+ });
488
+
489
+ // 输出 stdout
490
+ if (result.stdout) {
491
+ process.stdout.write(result.stdout);
492
+ }
493
+
494
+ // 输出 stderr
495
+ if (result.stderr) {
496
+ process.stderr.write(result.stderr);
497
+ }
498
+
499
+ if (result.code === 0) {
500
+ logger.success(` ✓ ${templateName} warmed up successfully`);
501
+ } else {
502
+ const errorMessage = [
503
+ `pnpm install failed for ${templateName} with exit code ${result.code}`,
504
+ result.stderr ? `\nStderr:\n${result.stderr}` : '',
505
+ result.stdout ? `\nStdout:\n${result.stdout}` : '',
506
+ ]
507
+ .filter(Boolean)
508
+ .join('');
509
+
510
+ throw new Error(errorMessage);
511
+ }
512
+ };
513
+
514
+ /**
515
+ * 执行 warmup 命令的内部实现
516
+ */
517
+ const executeWarmup = async (
518
+ options
519
+
520
+ ,
521
+
522
+ command,
523
+ ) => {
524
+ const timer = new TimeTracker();
525
+
526
+ try {
527
+ const { template: templateFilter } = options;
528
+
529
+ logger.info('Starting template warmup...');
530
+ timer.logPhase('Initialization');
531
+
532
+ // 加载模板配置
533
+ const configPath = getTemplatesConfigPath();
534
+ const templatesConfig = await loadTemplatesConfig(configPath);
535
+
536
+ logger.verbose(
537
+ `Found ${templatesConfig.templates.length} templates in config`,
538
+ );
539
+
540
+ // 过滤模板
541
+ const templatesToWarmup = templateFilter
542
+ ? templatesConfig.templates.filter(t => t.name === templateFilter)
543
+ : templatesConfig.templates;
544
+
545
+ if (templatesToWarmup.length === 0) {
546
+ if (templateFilter) {
547
+ logger.warn(`Template "${templateFilter}" not found`);
548
+ logger.info(
549
+ `Available templates: ${templatesConfig.templates.map(t => t.name).join(', ')}`,
550
+ );
551
+ } else {
552
+ logger.warn('No templates found');
553
+ }
554
+ return;
555
+ }
556
+
557
+ logger.info(
558
+ `\nWill warm up ${templatesToWarmup.length} template(s): ${templatesToWarmup.map(t => t.name).join(', ')}`,
559
+ );
560
+
561
+ // 获取模板基础路径
562
+ const basePath = configPath.replace(/\/templates\.json$/, '');
563
+
564
+ // 对每个模板执行 pnpm install
565
+ for (const templateMetadata of templatesToWarmup) {
566
+ const templatePath = await getTemplatePath(basePath, templateMetadata);
567
+ warmupTemplate(templatePath, templateMetadata.name);
568
+ timer.logPhase(`Warmup ${templateMetadata.name}`);
569
+ }
570
+
571
+ logger.success('\n✅ All templates warmed up successfully!');
572
+ logger.info(
573
+ '\nNext time you run `coze init`, it will be much faster as node_modules are pre-installed.',
574
+ );
575
+
576
+ timer.logTotal();
577
+ } catch (error) {
578
+ timer.logTotal();
579
+ logger.error('Failed to warmup templates:');
580
+ logger.error(error instanceof Error ? error.message : String(error));
581
+ process.exit(1);
582
+ }
583
+ };
584
+
585
+ /**
586
+ * 注册 warmup 命令到 program
587
+ */
588
+ const registerCommand$2 = program => {
589
+ program
590
+ .command('warmup')
591
+ .description('Pre-install dependencies for templates to speed up init')
592
+ .option('-t, --template <name>', 'Warmup a specific template only')
593
+ .action(async (options, command) => {
594
+ await executeWarmup(options);
595
+ });
596
+ };
597
+
598
+ function _optionalChain$2(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* eslint-disable @typescript-eslint/no-explicit-any */
274
599
  // Safe JSON parsing utilities with type safety and error handling
275
600
  // Provides fallback values, validation, and error monitoring capabilities
276
601
 
@@ -361,12 +686,12 @@ function safeJsonParse(
361
686
  const parsed = JSON.parse(String(input));
362
687
 
363
688
  // Optional validation
364
- if (_optionalChain$3([options, 'optionalAccess', _ => _.validate])) {
689
+ if (_optionalChain$2([options, 'optionalAccess', _ => _.validate])) {
365
690
  if (options.validate(parsed)) {
366
691
  return parsed;
367
692
  } else {
368
693
  const validationError = new Error('JSON validation failed');
369
- _optionalChain$3([options, 'access', _2 => _2.onError, 'optionalCall', _3 => _3(validationError, input)]);
694
+ _optionalChain$2([options, 'access', _2 => _2.onError, 'optionalCall', _3 => _3(validationError, input)]);
370
695
 
371
696
  if (options.throwOnValidationError) {
372
697
  throw validationError;
@@ -378,15 +703,15 @@ function safeJsonParse(
378
703
  return parsed;
379
704
  } catch (error) {
380
705
  // Re-throw validation errors when throwOnValidationError is true
381
- if (error instanceof Error && error.message === 'JSON validation failed' && _optionalChain$3([options, 'optionalAccess', _4 => _4.throwOnValidationError])) {
706
+ if (error instanceof Error && error.message === 'JSON validation failed' && _optionalChain$2([options, 'optionalAccess', _4 => _4.throwOnValidationError])) {
382
707
  throw error;
383
708
  }
384
- _optionalChain$3([options, 'optionalAccess', _5 => _5.onError, 'optionalCall', _6 => _6(error , input)]);
709
+ _optionalChain$2([options, 'optionalAccess', _5 => _5.onError, 'optionalCall', _6 => _6(error , input)]);
385
710
  return defaultValue;
386
711
  }
387
712
  }
388
713
 
389
- function _optionalChain$2(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
714
+ function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
390
715
 
391
716
 
392
717
  /**
@@ -476,13 +801,13 @@ const getCommandConfig = (
476
801
  // 根据命令名称映射到配置路径
477
802
  switch (commandName) {
478
803
  case 'dev':
479
- commandConfig = _optionalChain$2([config, 'access', _ => _.dev, 'optionalAccess', _2 => _2.run]);
804
+ commandConfig = _optionalChain$1([config, 'access', _ => _.dev, 'optionalAccess', _2 => _2.run]);
480
805
  break;
481
806
  case 'build':
482
- commandConfig = _optionalChain$2([config, 'access', _3 => _3.deploy, 'optionalAccess', _4 => _4.build]);
807
+ commandConfig = _optionalChain$1([config, 'access', _3 => _3.deploy, 'optionalAccess', _4 => _4.build]);
483
808
  break;
484
809
  case 'start':
485
- commandConfig = _optionalChain$2([config, 'access', _5 => _5.deploy, 'optionalAccess', _6 => _6.run]);
810
+ commandConfig = _optionalChain$1([config, 'access', _5 => _5.deploy, 'optionalAccess', _6 => _6.run]);
486
811
  break;
487
812
  default:
488
813
  throw new Error(`Unknown command: ${commandName}`);
@@ -498,7 +823,7 @@ const getCommandConfig = (
498
823
  return commandConfig;
499
824
  };
500
825
 
501
- function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
826
+ function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
502
827
 
503
828
  /**
504
829
  * 创建日志管理器
@@ -556,12 +881,12 @@ const executeRun = async (
556
881
  }
557
882
 
558
883
  // 将输出同时写入控制台和日志文件
559
- _optionalChain$1([childProcess, 'access', _ => _.stdout, 'optionalAccess', _2 => _2.on, 'call', _3 => _3('data', (data) => {
884
+ _optionalChain([childProcess, 'access', _ => _.stdout, 'optionalAccess', _2 => _2.on, 'call', _3 => _3('data', (data) => {
560
885
  process.stdout.write(data);
561
886
  logStream.write(data);
562
887
  })]);
563
888
 
564
- _optionalChain$1([childProcess, 'access', _4 => _4.stderr, 'optionalAccess', _5 => _5.on, 'call', _6 => _6('data', (data) => {
889
+ _optionalChain([childProcess, 'access', _4 => _4.stderr, 'optionalAccess', _5 => _5.on, 'call', _6 => _6('data', (data) => {
565
890
  process.stderr.write(data);
566
891
  logStream.write(data);
567
892
  })]);
@@ -630,68 +955,44 @@ const registerCommand$1 = program => {
630
955
  };
631
956
 
632
957
  /**
633
- * 时间统计工具
958
+ * 在后台启动一个独立的子进程
959
+ * 类似于 `setsid command args >/dev/null 2>&1 &`
960
+ *
961
+ * @param command - 要执行的命令 (例如: 'npm', 'node', 'bash')
962
+ * @param args - 命令参数数组 (例如: ['run', 'dev'])
963
+ * @param options - 配置选项
964
+ * @returns 子进程的 PID
634
965
  */
635
- class TimeTracker {
636
-
637
-
966
+ function spawnDetached(
967
+ command,
968
+ args,
969
+ options,
970
+ ) {
971
+ const { cwd, verbose = true } = options;
972
+ const isWindows = os.platform() === 'win32';
638
973
 
639
- constructor() {
640
- this.startTime = perf_hooks.performance.now();
641
- this.lastTime = this.startTime;
974
+ if (verbose) {
975
+ console.log(`Spawning detached process: ${command} ${args.join(' ')}`);
976
+ console.log(`Working directory: ${cwd}`);
642
977
  }
643
978
 
644
- /**
645
- * 记录阶段耗时
646
- * @param phaseName 阶段名称
647
- */
648
- logPhase(phaseName) {
649
- const now = perf_hooks.performance.now();
650
- const phaseTime = now - this.lastTime;
651
- this.lastTime = now;
979
+ // 使用 spawn 创建后台子进程
980
+ const child = child_process.spawn(command, args, {
981
+ cwd,
982
+ detached: !isWindows, // Windows 不完全支持 detached,但仍可以使用
983
+ stdio: 'ignore', // 忽略所有输入输出,让进程完全独立运行
984
+ });
652
985
 
653
- logger.verbose(`⏱ ${phaseName}: ${phaseTime.toFixed(2)}ms`);
654
- }
986
+ // 分离父子进程引用,允许父进程退出而不等待子进程
987
+ child.unref();
655
988
 
656
- /**
657
- * 记录总耗时
658
- */
659
- logTotal() {
660
- const totalTime = perf_hooks.performance.now() - this.startTime;
661
- logger.verbose(`⏱ Total time: ${totalTime.toFixed(2)}ms`);
989
+ if (verbose && child.pid) {
990
+ console.log(`Process started with PID: ${child.pid}`);
662
991
  }
663
992
 
664
- /**
665
- * 获取当前耗时(不输出日志)
666
- * @returns 从开始到现在的总耗时(毫秒)
667
- */
668
- getElapsedTime() {
669
- return perf_hooks.performance.now() - this.startTime;
670
- }
993
+ return child.pid;
671
994
  }
672
995
 
673
- /**
674
- * 获取模板配置文件路径
675
- * @returns templates.json 的绝对路径
676
- */
677
- const getTemplatesConfigPath = () => {
678
- const configPath = path.resolve(getTemplatesDir(), 'templates.json');
679
- logger.verbose(`Templates config path: ${configPath}`);
680
- logger.verbose(`Config file exists: ${fs.existsSync(configPath)}`);
681
- return configPath;
682
- };
683
-
684
- /**
685
- * 获取模板目录路径
686
- * @returns __templates__ 目录的绝对路径
687
- */
688
- const getTemplatesDir = () => {
689
- const templatesDir = path.resolve(__dirname, './__templates__');
690
- logger.verbose(`Templates directory: ${templatesDir}`);
691
- logger.verbose(`Templates directory exists: ${fs.existsSync(templatesDir)}`);
692
- return templatesDir;
693
- };
694
-
695
996
  /**
696
997
  * 创建 AJV 验证器实例
697
998
  */
@@ -740,145 +1041,6 @@ const validateParams = (
740
1041
  return params ;
741
1042
  };
742
1043
 
743
- /**
744
- * 加载模板配置文件
745
- * 支持 .ts 和 .js 文件(通过 sucrase 注册)
746
- *
747
- * @param templatePath - 模板目录路径
748
- * @returns 模板配置对象
749
- */
750
-
751
- const loadTemplateConfig = async (
752
- templatePath,
753
- ) => {
754
- logger.verbose(`Loading template config from: ${templatePath}`);
755
-
756
- const tsConfigPath = path.join(templatePath, 'template.config.ts');
757
- const jsConfigPath = path.join(templatePath, 'template.config.js');
758
-
759
- logger.verbose('Checking for config files:');
760
- logger.verbose(` - TypeScript: ${tsConfigPath}`);
761
- logger.verbose(` - JavaScript: ${jsConfigPath}`);
762
-
763
- let configPath;
764
-
765
- const [tsExists, jsExists] = await Promise.all([
766
- fs$1.access(tsConfigPath).then(
767
- () => true,
768
- () => false,
769
- ),
770
- fs$1.access(jsConfigPath).then(
771
- () => true,
772
- () => false,
773
- ),
774
- ]);
775
-
776
- logger.verbose('Config file existence check:');
777
- logger.verbose(` - template.config.ts: ${tsExists}`);
778
- logger.verbose(` - template.config.js: ${jsExists}`);
779
-
780
- if (tsExists) {
781
- configPath = tsConfigPath;
782
- } else if (jsExists) {
783
- configPath = jsConfigPath;
784
- } else {
785
- throw new Error(
786
- `Template config not found in ${templatePath}.\n` +
787
- 'Expected: template.config.ts or template.config.js',
788
- );
789
- }
790
-
791
- logger.verbose(`Using config file: ${configPath}`);
792
-
793
- // eslint-disable-next-line @typescript-eslint/no-require-imports, security/detect-non-literal-require -- Sucrase handles .ts files at runtime, path is validated above
794
- const config = require(configPath);
795
-
796
- logger.verbose('Template config loaded successfully');
797
-
798
- return config.default || config;
799
- };
800
-
801
- /**
802
- * 加载模板列表配置
803
- *
804
- * @param configPath - templates.json 配置文件路径
805
- * @returns 模板列表配置
806
- */
807
- const loadTemplatesConfig = async (
808
- configPath,
809
- ) => {
810
- logger.verbose(`Loading templates config from: ${configPath}`);
811
-
812
- const content = await fs$1.readFile(configPath, 'utf-8');
813
- // eslint-disable-next-line no-restricted-syntax -- Static config file loaded at build time, safeJsonParse not needed
814
- const config = JSON.parse(content) ;
815
-
816
- logger.verbose(
817
- `Found ${config.templates.length} templates: ${config.templates.map(t => t.name).join(', ')}`,
818
- );
819
-
820
- return config;
821
- };
822
-
823
- /**
824
- * 根据模板名称查找模板元信息
825
- *
826
- * @param templatesConfig - 模板列表配置
827
- * @param templateName - 模板名称
828
- * @returns 模板元信息
829
- */
830
- const findTemplate = (
831
- templatesConfig,
832
- templateName,
833
- ) => {
834
- const template = templatesConfig.templates.find(t => t.name === templateName);
835
-
836
- if (!template) {
837
- const availableTemplates = templatesConfig.templates
838
- .map(t => t.name)
839
- .join(', ');
840
- throw new Error(
841
- `Template "${templateName}" not found.\n` +
842
- `Available templates: ${availableTemplates}\n` +
843
- 'Use --template <name> to specify a template.',
844
- );
845
- }
846
-
847
- return template;
848
- };
849
-
850
- /**
851
- * 获取模板的完整路径
852
- *
853
- * @param basePath - 模板目录(templates.json 所在目录)
854
- * @param templateMetadata - 模板元信息
855
- * @returns 模板完整路径
856
- */
857
- const getTemplatePath = async (
858
- basePath,
859
- templateMetadata,
860
- ) => {
861
- logger.verbose('Resolving template path:');
862
- logger.verbose(` - Base path: ${basePath}`);
863
- logger.verbose(` - Template location: ${templateMetadata.location}`);
864
-
865
- // location 是相对于 templates.json 文件的路径
866
- const templatePath = path.join(basePath, templateMetadata.location);
867
-
868
- logger.verbose(` - Resolved path: ${templatePath}`);
869
-
870
- try {
871
- await fs$1.access(templatePath);
872
- logger.verbose(' - Template directory exists: ✓');
873
- // eslint-disable-next-line @coze-arch/use-error-in-catch -- Error handling done in throw statement
874
- } catch (e) {
875
- logger.error(' - Template directory does not exist: ✗');
876
- throw new Error(`Template directory not found: ${templatePath}`);
877
- }
878
-
879
- return templatePath;
880
- };
881
-
882
1044
  /**
883
1045
  * 从 Commander 解析透传参数
884
1046
  * 将 kebab-case 的 CLI 参数转换为 camelCase
@@ -1344,7 +1506,7 @@ const execute = async (
1344
1506
  return absoluteOutputPath;
1345
1507
  };
1346
1508
 
1347
- function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1509
+ function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
1348
1510
  /**
1349
1511
  * 运行 pnpm install
1350
1512
  */
@@ -1447,45 +1609,26 @@ const runGitInit = (projectPath) => {
1447
1609
  };
1448
1610
 
1449
1611
  /**
1450
- * 运行开发服务器
1612
+ * 运行开发服务器(后台模式)
1613
+ * 启动后台子进程运行开发服务器,父进程可以直接退出
1451
1614
  */
1452
1615
  const runNpmDev = (projectPath) => {
1453
- logger.info('\nStarting development server...');
1616
+ logger.info('\nStarting development server in background...');
1454
1617
  logger.info(`Executing: npm run dev in ${projectPath}`);
1455
- logger.info('Press Ctrl+C to stop the server\n');
1456
1618
 
1457
- // 使用 async: true 异步执行,不阻塞进程
1458
- const child = shelljs.exec('npm run dev', {
1619
+ // 使用通用的后台执行函数启动开发服务器
1620
+ const pid = spawnDetached('npm', ['run', 'dev'], {
1459
1621
  cwd: projectPath,
1460
- async: true,
1461
- silent: true, // 手动处理输出以便显示详细信息
1622
+ verbose: false, // 不输出额外的进程信息,由 logger 统一处理
1462
1623
  });
1463
1624
 
1464
- if (child) {
1465
- // 输出 stdout
1466
- _optionalChain([child, 'access', _ => _.stdout, 'optionalAccess', _2 => _2.on, 'call', _3 => _3('data', (data) => {
1467
- process.stdout.write(data);
1468
- })]);
1469
-
1470
- // 输出 stderr
1471
- _optionalChain([child, 'access', _4 => _4.stderr, 'optionalAccess', _5 => _5.on, 'call', _6 => _6('data', (data) => {
1472
- process.stderr.write(data);
1473
- })]);
1474
-
1475
- // 监听错误
1476
- child.on('error', (error) => {
1477
- logger.error(`Failed to start dev server: ${error.message}`);
1478
- logger.error(`Error stack: ${error.stack}`);
1479
- });
1480
-
1481
- // 监听退出
1482
- child.on('exit', (code, signal) => {
1483
- if (code !== 0 && code !== null) {
1484
- logger.error(
1485
- `Dev server exited with code ${code}${signal ? ` and signal ${signal}` : ''}`,
1486
- );
1487
- }
1488
- });
1625
+ logger.success('Development server started in background!');
1626
+ if (pid) {
1627
+ logger.info(`Process ID: ${pid}`);
1628
+ logger.info(
1629
+ '\nThe dev server is running independently. You can close this terminal.',
1630
+ );
1631
+ logger.info(`To stop the server later, use: kill ${pid}`);
1489
1632
  }
1490
1633
  };
1491
1634
 
@@ -1598,14 +1741,14 @@ const registerCommand = program => {
1598
1741
  });
1599
1742
  };
1600
1743
 
1601
- var version = "0.0.1-alpha.a3fb1a";
1744
+ var version = "0.0.1-alpha.c49cb2";
1602
1745
  var packageJson = {
1603
1746
  version: version};
1604
1747
 
1605
1748
  const commands = [
1606
1749
  registerCommand,
1607
1750
  registerCommand$1,
1608
- // registerWarmupCommand,
1751
+ registerCommand$2,
1609
1752
  ];
1610
1753
 
1611
1754
  const main = () => {