@coze-arch/cli 0.0.1-alpha.a3fb1a → 0.0.1-alpha.d5effa
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/__templates__/expo/.cozeproj/scripts/deploy_build.sh +4 -3
- package/lib/__templates__/expo/.cozeproj/scripts/deploy_run.sh +8 -40
- package/lib/__templates__/expo/_npmrc +1 -1
- package/lib/__templates__/expo/client/contexts/AuthContext.tsx +14 -107
- package/lib/__templates__/expo/client/screens/home/index.tsx +1 -4
- package/lib/__templates__/expo/client/screens/home/styles.ts +1 -273
- package/lib/__templates__/expo/client/utils/index.ts +1 -2
- package/lib/__templates__/expo/package.json +1 -1
- package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
- package/lib/__templates__/expo/src/index.ts +2 -2
- package/lib/__templates__/expo/template.config.js +1 -1
- package/lib/__templates__/nextjs/_npmrc +1 -1
- package/lib/__templates__/nextjs/package.json +1 -4
- package/lib/__templates__/nextjs/pnpm-lock.yaml +5 -1025
- package/lib/__templates__/nextjs/scripts/dev.sh +8 -27
- package/lib/__templates__/nextjs/src/app/layout.tsx +18 -22
- package/lib/__templates__/nextjs/template.config.js +1 -1
- package/lib/__templates__/templates.json +7 -0
- package/lib/__templates__/vite/_npmrc +1 -1
- package/lib/__templates__/vite/scripts/dev.sh +7 -26
- package/lib/__templates__/vite/template.config.js +11 -2
- package/lib/__templates__/vite/vite.config.ts +4 -3
- package/lib/cli.js +385 -242
- package/package.json +1 -2
- package/lib/__templates__/nextjs/.babelrc +0 -15
- package/lib/__templates__/nextjs/server.mjs +0 -50
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
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
804
|
+
commandConfig = _optionalChain$1([config, 'access', _ => _.dev, 'optionalAccess', _2 => _2.run]);
|
|
480
805
|
break;
|
|
481
806
|
case 'build':
|
|
482
|
-
commandConfig = _optionalChain$
|
|
807
|
+
commandConfig = _optionalChain$1([config, 'access', _3 => _3.deploy, 'optionalAccess', _4 => _4.build]);
|
|
483
808
|
break;
|
|
484
809
|
case 'start':
|
|
485
|
-
commandConfig = _optionalChain$
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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(); } }
|
|
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
|
-
//
|
|
1458
|
-
const
|
|
1619
|
+
// 使用通用的后台执行函数启动开发服务器
|
|
1620
|
+
const pid = spawnDetached('npm', ['run', 'dev'], {
|
|
1459
1621
|
cwd: projectPath,
|
|
1460
|
-
|
|
1461
|
-
silent: true, // 手动处理输出以便显示详细信息
|
|
1622
|
+
verbose: false, // 不输出额外的进程信息,由 logger 统一处理
|
|
1462
1623
|
});
|
|
1463
1624
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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.
|
|
1744
|
+
var version = "0.0.1-alpha.d5effa";
|
|
1602
1745
|
var packageJson = {
|
|
1603
1746
|
version: version};
|
|
1604
1747
|
|
|
1605
1748
|
const commands = [
|
|
1606
1749
|
registerCommand,
|
|
1607
1750
|
registerCommand$1,
|
|
1608
|
-
|
|
1751
|
+
registerCommand$2,
|
|
1609
1752
|
];
|
|
1610
1753
|
|
|
1611
1754
|
const main = () => {
|