specline 1.3.1 → 1.3.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/cli.mjs +86 -32
- package/package.json +1 -1
- package/templates/.specline-config.yaml +0 -1
package/cli.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import { join, dirname, resolve, relative, basename } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
7
|
import { get } from 'https';
|
|
8
|
+
import { execSync, spawnSync } from 'child_process';
|
|
9
|
+
import { createInterface } from 'readline/promises';
|
|
8
10
|
|
|
9
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
12
|
const TEMPLATES_DIR = join(__dirname, 'templates');
|
|
@@ -347,10 +349,10 @@ function cmd_init(targetPath) {
|
|
|
347
349
|
process.exit(1);
|
|
348
350
|
}
|
|
349
351
|
|
|
350
|
-
const
|
|
352
|
+
const lockFile = join(target, 'specline', '.specline-lock.yaml');
|
|
351
353
|
const forceMode = process.argv.includes('--force') || process.argv.includes('-f');
|
|
352
354
|
|
|
353
|
-
if (existsSync(
|
|
355
|
+
if (existsSync(lockFile) && !forceMode) {
|
|
354
356
|
warn('Specline 已在此项目中初始化。使用 --force 强制覆盖。');
|
|
355
357
|
process.exit(0);
|
|
356
358
|
}
|
|
@@ -406,13 +408,6 @@ function cmd_init(targetPath) {
|
|
|
406
408
|
const skillsCount = countFiles(join(target, '.cursor', 'skills'));
|
|
407
409
|
const hooksCount = countFiles(join(target, '.cursor', 'hooks'));
|
|
408
410
|
|
|
409
|
-
// 写入初始化配置
|
|
410
|
-
const initConfig = `# Specline 项目配置
|
|
411
|
-
version: "${VERSION}"
|
|
412
|
-
initialized_at: "${new Date().toISOString()}"
|
|
413
|
-
`;
|
|
414
|
-
writeFileSync(configFile, initConfig, 'utf-8');
|
|
415
|
-
|
|
416
411
|
success('Specline 初始化完成');
|
|
417
412
|
log(`📁 文件: ${commandsCount} commands, ${skillsCount} skills, ${agentsCount} agents, ${hooksCount} hooks`);
|
|
418
413
|
log('');
|
|
@@ -462,7 +457,26 @@ function fetchLatestVersion() {
|
|
|
462
457
|
});
|
|
463
458
|
}
|
|
464
459
|
|
|
460
|
+
/**
|
|
461
|
+
* 交互式确认提问:回车/Y/y/Yes/yes → true, N/n/No/no → false
|
|
462
|
+
* 非 TTY 环境直接返回 true(无人值守模式)
|
|
463
|
+
*/
|
|
464
|
+
async function askConfirm(question) {
|
|
465
|
+
if (!process.stdin.isTTY) return true;
|
|
466
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
467
|
+
try {
|
|
468
|
+
const answer = await rl.question(question + ' ');
|
|
469
|
+
const trimmed = answer.trim().toLowerCase();
|
|
470
|
+
return trimmed === '' || trimmed === 'y' || trimmed === 'yes';
|
|
471
|
+
} catch {
|
|
472
|
+
return false;
|
|
473
|
+
} finally {
|
|
474
|
+
rl.close();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
465
478
|
async function cmd_update() {
|
|
479
|
+
// 1. 从 npm registry 获取最新版本
|
|
466
480
|
let latest;
|
|
467
481
|
try {
|
|
468
482
|
latest = await fetchLatestVersion();
|
|
@@ -480,25 +494,60 @@ async function cmd_update() {
|
|
|
480
494
|
process.exit(0);
|
|
481
495
|
}
|
|
482
496
|
|
|
483
|
-
|
|
484
|
-
|
|
497
|
+
// 2. 版本比较
|
|
498
|
+
if (compareVersions(VERSION, latest) >= 0) {
|
|
499
|
+
success('已是最新版本 (v' + VERSION + ')');
|
|
500
|
+
process.exit(0);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 3. 交互确认
|
|
504
|
+
log('✨ 新版本可用: v' + latest + '(当前: v' + VERSION + ')');
|
|
485
505
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
506
|
+
if (!process.stdin.isTTY) {
|
|
507
|
+
log('在非交互环境中无法自动升级,请手动执行: npm install -g specline@latest');
|
|
508
|
+
process.exit(0);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const proceed = await askConfirm('是否升级到 v' + latest + '?[Y/n]');
|
|
512
|
+
if (!proceed) {
|
|
513
|
+
log('已取消升级');
|
|
514
|
+
process.exit(0);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 4. 执行 npm install -g specline@latest
|
|
518
|
+
log('正在升级 specline...');
|
|
519
|
+
try {
|
|
520
|
+
execSync('npm install -g specline@latest', { stdio: 'inherit' });
|
|
521
|
+
} catch (err) {
|
|
522
|
+
const stderr = (err.stderr || '').toString();
|
|
523
|
+
if (stderr.includes('EACCES') || stderr.includes('permission denied')) {
|
|
524
|
+
error('权限不足。请尝试:');
|
|
525
|
+
log(' sudo npm install -g specline@latest');
|
|
526
|
+
log(' 或使用 Node 版本管理器(nvm / fnm / n)');
|
|
527
|
+
} else {
|
|
528
|
+
error('升级失败:' + (stderr || err.message));
|
|
495
529
|
}
|
|
530
|
+
process.exit(1);
|
|
496
531
|
}
|
|
497
532
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
533
|
+
success('已升级至 v' + latest);
|
|
534
|
+
|
|
535
|
+
// 5. 检测是否为 specline 项目,询问是否同步模板
|
|
536
|
+
const cwd = process.cwd();
|
|
537
|
+
const lockFile = join(cwd, 'specline', '.specline-lock.yaml');
|
|
538
|
+
if (existsSync(lockFile)) {
|
|
539
|
+
const doSync = await askConfirm('检测到 specline 项目,是否同步最新模板?[Y/n]');
|
|
540
|
+
if (doSync) {
|
|
541
|
+
log('正在同步模板文件...');
|
|
542
|
+
try {
|
|
543
|
+
const result = spawnSync('specline', ['sync'], { stdio: 'inherit' });
|
|
544
|
+
if (result.status !== 0) {
|
|
545
|
+
warn('模板同步失败(退出码: ' + result.status + '),请手动运行 specline sync');
|
|
546
|
+
}
|
|
547
|
+
} catch (err) {
|
|
548
|
+
warn('无法运行 specline sync:' + err.message);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
502
551
|
}
|
|
503
552
|
|
|
504
553
|
process.exit(0);
|
|
@@ -509,10 +558,19 @@ function cmd_sync({ dryRun, targetPath }) {
|
|
|
509
558
|
const target = resolve(cwd, targetPath || '.');
|
|
510
559
|
|
|
511
560
|
// 1. 检查项目是否已初始化
|
|
512
|
-
const
|
|
513
|
-
if (!existsSync(
|
|
514
|
-
|
|
515
|
-
|
|
561
|
+
const lockFile = join(target, 'specline', '.specline-lock.yaml');
|
|
562
|
+
if (!existsSync(lockFile)) {
|
|
563
|
+
// 向后兼容:检查旧版 .specline-config.yaml
|
|
564
|
+
const oldMarker = join(target, '.specline-config.yaml');
|
|
565
|
+
if (existsSync(oldMarker)) {
|
|
566
|
+
warn('检测到旧版项目,正在自动迁移...');
|
|
567
|
+
const lockData = buildLockData(target, target);
|
|
568
|
+
writeLockFile(target, lockData);
|
|
569
|
+
success('已从旧版项目迁移,生成了锁文件');
|
|
570
|
+
} else {
|
|
571
|
+
error('未检测到 Specline 项目,请先运行 specline init');
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
516
574
|
}
|
|
517
575
|
|
|
518
576
|
// 2. 构建上游模板哈希映射
|
|
@@ -545,10 +603,6 @@ function cmd_sync({ dryRun, targetPath }) {
|
|
|
545
603
|
// 6. 分类
|
|
546
604
|
const results = [];
|
|
547
605
|
for (const path of allPaths) {
|
|
548
|
-
if (path === '.specline-config.yaml') {
|
|
549
|
-
// 项目标识文件,由 specline init 生成(含时间戳),sync 不覆盖
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
606
|
const templateHash = upstreamFiles.get(path) || null;
|
|
553
607
|
const lockEntry = lockData ? (lockData.files.get(path) || null) : null;
|
|
554
608
|
const projectPath = join(target, path);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version: "1.0.0"
|