create-ccc-tutor 0.2.0 → 0.3.0

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.
Files changed (3) hide show
  1. package/README.md +14 -1
  2. package/bin/cli.js +70 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -23,12 +23,25 @@ course/
23
23
 
24
24
  `course/course_code(example)/` 是带两个示例课件的样板,复制改名即可开始。
25
25
 
26
+ ## 更新已安装的环境
27
+
28
+ 已经装过、想升级到新版(拿到新功能/修复,如 PDF 看图与检索引擎)时,在项目目录里跑:
29
+
30
+ ```
31
+ npx create-ccc-tutor@latest --update
32
+ ```
33
+
34
+ `--update` 会更新框架、技能与功能文件,但**保护你的用户数据**:`course/` 课件、`.harness/state/install.json` 配置、`constitution.md` 项目身份都不会被覆盖。
35
+
36
+ > 不加 `--update` 直接重装会跳过所有已存在文件(改过的技能不会更新,等于半更新);检测到老环境时安装器会提示你改用 `--update`。
37
+
26
38
  ## 选项
27
39
 
28
40
  | 选项 | 作用 |
29
41
  |---|---|
30
42
  | `--dry-run` | 只预览要写哪些文件,不实际写入 |
31
- | `--force` | 覆盖已存在的同名文件 |
43
+ | `--update` | 更新已装环境:覆盖框架/技能/功能,但保护 `course/`、`install.json`、`constitution.md` |
44
+ | `--force` | 全量覆盖所有已存在文件(含用户配置,慎用) |
32
45
 
33
46
  ## 要求
34
47
 
package/bin/cli.js CHANGED
@@ -6,6 +6,7 @@ const path = require("path");
6
6
  const argv = process.argv.slice(2);
7
7
  const dryRun = argv.includes("--dry-run");
8
8
  const force = argv.includes("--force");
9
+ const update = argv.includes("--update");
9
10
 
10
11
  const SRC = path.join(__dirname, "..", "template");
11
12
  const DEST = process.cwd();
@@ -15,8 +16,28 @@ function destName(name) {
15
16
  return name === "gitignore" ? ".gitignore" : name;
16
17
  }
17
18
 
19
+ // --update 模式下「保护」的用户数据/配置:即使已存在也绝不覆盖(除非 --force 全量重置)。
20
+ // 这样老环境能更新框架(技能/脚本/文档/引擎),又不会冲掉用户的课件和项目身份。
21
+ const PROTECTED_FILES = [
22
+ ".harness/state/install.json", // 用户的科目/配置(/init 结果)
23
+ "constitution.md", // 项目身份(/init 填的槽位)
24
+ ];
25
+ const PROTECTED_DIRS = [
26
+ "course", // 用户的课件与题目,整棵子树都不动
27
+ ];
28
+
29
+ function relPosix(p) {
30
+ return path.relative(DEST, p).split(path.sep).join("/");
31
+ }
32
+ function isProtected(absDest) {
33
+ const rel = relPosix(absDest);
34
+ if (PROTECTED_FILES.includes(rel)) return true;
35
+ return PROTECTED_DIRS.some((d) => rel === d || rel.startsWith(d + "/"));
36
+ }
37
+
18
38
  let written = 0;
19
39
  let skipped = 0;
40
+ let protectedCount = 0;
20
41
 
21
42
  function copyDir(src, dest) {
22
43
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -26,21 +47,38 @@ function copyDir(src, dest) {
26
47
  if (!dryRun) fs.mkdirSync(d, { recursive: true });
27
48
  copyDir(s, d);
28
49
  } else {
29
- const rel = path.relative(DEST, d);
30
- if (fs.existsSync(d) && !force) {
31
- console.log(` 跳过(已存在,用 --force 覆盖): ${rel}`);
32
- skipped++;
33
- continue;
50
+ const rel = relPosix(d);
51
+ const exists = fs.existsSync(d);
52
+ if (exists && !force) {
53
+ if (update) {
54
+ // 更新模式:保护用户数据,其余框架文件覆盖
55
+ if (isProtected(d)) {
56
+ console.log(` 保护(不覆盖用户文件): ${rel}`);
57
+ protectedCount++;
58
+ continue;
59
+ }
60
+ console.log(` ${dryRun ? "[预览] " : ""}更新: ${rel}`);
61
+ } else {
62
+ // 默认模式(全新安装):已存在就跳过,避免误覆盖
63
+ console.log(` 跳过(已存在;更新用 --update,全量覆盖用 --force): ${rel}`);
64
+ skipped++;
65
+ continue;
66
+ }
67
+ } else {
68
+ console.log(` ${dryRun ? "[预览] " : ""}写入: ${rel}`);
34
69
  }
35
- console.log(` ${dryRun ? "[预览] " : ""}写入: ${rel}`);
36
70
  if (!dryRun) fs.copyFileSync(s, d);
37
71
  written++;
38
72
  }
39
73
  }
40
74
  }
41
75
 
76
+ const hasExistingInstall = fs.existsSync(
77
+ path.join(DEST, ".harness", "state", "install.json")
78
+ );
79
+ const mode = force ? "全量覆盖 (--force)" : update ? "更新 (--update)" : "全新安装";
42
80
  console.log(
43
- `\nCCC-tutor 安装到: ${DEST}${dryRun ? " (--dry-run 预览,不写文件)" : ""}\n`
81
+ `\nCCC-tutor ${mode} ${DEST}${dryRun ? " (--dry-run 预览,不写文件)" : ""}\n`
44
82
  );
45
83
 
46
84
  if (!fs.existsSync(SRC)) {
@@ -48,6 +86,16 @@ if (!fs.existsSync(SRC)) {
48
86
  process.exit(1);
49
87
  }
50
88
 
89
+ // 检测到老环境却没带 --update/--force:提示用户,避免「只新增、改过的技能被跳过」的半更新。
90
+ if (hasExistingInstall && !update && !force) {
91
+ console.log(
92
+ "⚠️ 检测到这里已经装过 CCC-tutor。直接安装会跳过所有已存在文件(改过的技能不会更新)。\n" +
93
+ " 想更新到新版请改用: npx create-ccc-tutor@latest --update\n" +
94
+ " (--update 会更新框架与功能,但保护你的 course/ 课件、install.json、constitution.md)\n" +
95
+ " 仍要按全新安装继续会跳过已存在文件。\n"
96
+ );
97
+ }
98
+
51
99
  copyDir(SRC, DEST);
52
100
 
53
101
  // 给 shell 脚本加执行权限
@@ -62,8 +110,20 @@ if (!dryRun) {
62
110
  if (fs.existsSync(codexInstall)) fs.chmodSync(codexInstall, 0o755);
63
111
  }
64
112
 
65
- console.log(`
66
- 完成!写入 ${written} 个文件${skipped ? `,跳过 ${skipped} 个已存在文件(加 --force 覆盖)` : ""}。
113
+ const tail = [];
114
+ if (skipped) tail.push(`跳过 ${skipped} 个已存在文件(加 --update 更新 / --force 全量覆盖)`);
115
+ if (protectedCount) tail.push(`保护 ${protectedCount} 个用户文件未动`);
116
+
117
+ if (update) {
118
+ console.log(`
119
+ ✓ 更新完成!更新 ${written} 个文件${tail.length ? "," + tail.join(",") : ""}。
120
+
121
+ 已保护你的 course/ 课件、install.json 配置、constitution.md 项目身份。
122
+ 框架、技能与功能(含 PDF 看图/检索引擎)已更新到本版。
123
+ `);
124
+ } else {
125
+ console.log(`
126
+ ✓ 完成!写入 ${written} 个文件${tail.length ? "," + tail.join(",") : ""}。
67
127
 
68
128
  接下来:
69
129
  1. 把课件放进 course/<科目>/slide/ ,题目放进 course/<科目>/exam/
@@ -74,3 +134,4 @@ console.log(`
74
134
 
75
135
  已预填 install.json,首次启动无需走 /init 配置。
76
136
  `);
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ccc-tutor",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "一行命令装好的多科目 AI 复习助手(基于 CCC-MAGI):slide 查课件、exam 解题目,严格依据课件、标出处、不编造。",
5
5
  "bin": {
6
6
  "create-ccc-tutor": "bin/cli.js"