@tnotesjs/core 0.2.0 → 0.2.2

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 (67) hide show
  1. package/config/ConfigManager.ts +137 -0
  2. package/config/constants.ts +121 -0
  3. package/config/index.ts +25 -0
  4. package/config/templates.ts +49 -0
  5. package/core/GitManager.ts +513 -0
  6. package/core/NoteIndexCache.ts +194 -0
  7. package/core/NoteManager.ts +407 -0
  8. package/core/ProcessManager.ts +180 -0
  9. package/core/ReadmeGenerator.ts +215 -0
  10. package/core/TocGenerator.ts +212 -0
  11. package/core/index.ts +11 -0
  12. package/dist/chunk-5QN44ES5.js +6050 -0
  13. package/dist/chunk-5Y5IYS6C.js +200 -0
  14. package/dist/cli/index.js +986 -0
  15. package/dist/index.js +8 -0
  16. package/dist/vitepress/config/index.js +1686 -0
  17. package/package.json +6 -2
  18. package/services/file-watcher/configChangeHandler.ts +64 -0
  19. package/services/file-watcher/eventScheduler.ts +179 -0
  20. package/services/file-watcher/folderChangeHandler.ts +325 -0
  21. package/services/file-watcher/fsWatcherAdapter.ts +128 -0
  22. package/services/file-watcher/globalUpdateCoordinator.ts +60 -0
  23. package/services/file-watcher/index.ts +7 -0
  24. package/services/file-watcher/internal.ts +79 -0
  25. package/services/file-watcher/readmeChangeHandler.ts +28 -0
  26. package/services/file-watcher/renameDetector.ts +120 -0
  27. package/services/file-watcher/service.ts +352 -0
  28. package/services/file-watcher/watchState.ts +194 -0
  29. package/services/git/index.ts +7 -0
  30. package/services/git/service.ts +114 -0
  31. package/services/index.ts +15 -0
  32. package/services/init-sub-repo/index.ts +2 -0
  33. package/services/init-sub-repo/initSubRepoLogic.test.ts +162 -0
  34. package/services/init-sub-repo/initSubRepoLogic.ts +304 -0
  35. package/services/init-sub-repo/service.ts +101 -0
  36. package/services/note/index.ts +7 -0
  37. package/services/note/service.ts +362 -0
  38. package/services/readme/index.ts +7 -0
  39. package/services/readme/service.ts +761 -0
  40. package/services/timestamp/index.ts +7 -0
  41. package/services/timestamp/service.ts +465 -0
  42. package/services/toc/index.ts +5 -0
  43. package/services/toc/moveTocInside.test.ts +73 -0
  44. package/services/toc/service.ts +759 -0
  45. package/services/vitepress/index.ts +7 -0
  46. package/services/vitepress/service.ts +339 -0
  47. package/utils/errorHandler.ts +174 -0
  48. package/utils/file.ts +17 -0
  49. package/utils/genHierarchicalSidebar.ts +69 -0
  50. package/utils/generateAnchor.ts +24 -0
  51. package/utils/getChangedIds.ts +35 -0
  52. package/utils/index.ts +71 -0
  53. package/utils/logger.ts +231 -0
  54. package/utils/markdown.ts +75 -0
  55. package/utils/migrateReadmeToToc.test.ts +111 -0
  56. package/utils/migrateReadmeToToc.ts +135 -0
  57. package/utils/parseArgs.ts +90 -0
  58. package/utils/parseReadmeCompletedNotes.test.ts +90 -0
  59. package/utils/parseReadmeCompletedNotes.ts +108 -0
  60. package/utils/portUtils.ts +113 -0
  61. package/utils/readmeHelpers.ts +190 -0
  62. package/utils/runCommand.ts +29 -0
  63. package/utils/tocHelpers.test.ts +278 -0
  64. package/utils/tocHelpers.ts +855 -0
  65. package/utils/tocNodeId.test.ts +60 -0
  66. package/utils/tocNodeId.ts +97 -0
  67. package/utils/validators.ts +102 -0
@@ -0,0 +1,986 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BaseCommand,
4
+ COMMAND_DESCRIPTIONS,
5
+ COMMAND_NAMES,
6
+ COMMAND_OPTIONS,
7
+ DEFAULT_DESCRIPTION,
8
+ DEFAULT_PORT,
9
+ FileWatcherService,
10
+ GitManager,
11
+ GitService,
12
+ InitSubRepoService,
13
+ NoteIndexCache,
14
+ NoteManager,
15
+ NoteService,
16
+ ROOT_CONFIG_PATH,
17
+ ROOT_DIR_PATH,
18
+ ROOT_README_PATH,
19
+ ROOT_TOC_PATH,
20
+ ReadmeService,
21
+ RenameNoteCommand,
22
+ TimestampService,
23
+ TocService,
24
+ UpdateNoteConfigCommand,
25
+ VitepressService,
26
+ buildFolderTocLine,
27
+ buildInitContext,
28
+ buildManualSteps,
29
+ buildTocLine,
30
+ createLogger,
31
+ defaultDisplayName,
32
+ getInitializedConfigPath,
33
+ handleError,
34
+ isAlreadyInitialized,
35
+ logger,
36
+ migrateReadmeToToc,
37
+ parseArgs,
38
+ parseReadmeCompletedNotes,
39
+ parseTocCompletedNotes,
40
+ processTocEmptyLines,
41
+ resolveNoteFromIndex,
42
+ runCommand,
43
+ validatePort,
44
+ validateTopic
45
+ } from "../chunk-5QN44ES5.js";
46
+ import {
47
+ ConfigManager,
48
+ getConfigManager
49
+ } from "../chunk-5Y5IYS6C.js";
50
+
51
+ // commands/update/UpdateCommand.ts
52
+ import { existsSync, readFileSync, writeFileSync } from "fs";
53
+ var UpdateCommand = class extends BaseCommand {
54
+ constructor() {
55
+ super("update");
56
+ this.quiet = false;
57
+ this.readmeService = ReadmeService.getInstance();
58
+ this.noteService = NoteService.getInstance();
59
+ }
60
+ /**
61
+ * 设置 quiet 模式
62
+ *
63
+ * 在 quiet 模式下,只显示 WARN 级别以上的日志
64
+ */
65
+ setQuiet(quiet) {
66
+ this.quiet = quiet;
67
+ if (quiet) {
68
+ logger.setLevel(2 /* WARN */);
69
+ } else {
70
+ logger.setLevel(1 /* INFO */);
71
+ }
72
+ }
73
+ async run() {
74
+ await this.updateCurrentRepo();
75
+ }
76
+ /**
77
+ * 更新当前知识库
78
+ */
79
+ async updateCurrentRepo() {
80
+ const startTime = Date.now();
81
+ const notes = this.noteService.getAllNotes();
82
+ if (!this.quiet) {
83
+ this.logger.info("\u6B63\u5728\u4FEE\u6B63\u7B14\u8BB0\u6807\u9898...");
84
+ }
85
+ const fixedCount = await this.noteService.fixAllNoteTitles(notes);
86
+ if (!this.quiet && fixedCount > 0) {
87
+ this.logger.success(`\u4FEE\u6B63\u4E86 ${fixedCount} \u4E2A\u7B14\u8BB0\u6807\u9898`);
88
+ }
89
+ await this.readmeService.updateAllReadmes({ notes });
90
+ await this.updateRootItem();
91
+ const duration = Date.now() - startTime;
92
+ if (this.quiet) {
93
+ this.logger.success(`\u77E5\u8BC6\u5E93\u66F4\u65B0\u5B8C\u6210 (${duration}ms)`);
94
+ } else {
95
+ this.logger.success("\u77E5\u8BC6\u5E93\u66F4\u65B0\u5B8C\u6210");
96
+ }
97
+ }
98
+ /**
99
+ * 更新 root_item 配置
100
+ * 更新当前月份的完成笔记数量,并自动补齐从最后已有月份到当前月份之间的所有缺失月份。
101
+ */
102
+ async updateRootItem() {
103
+ try {
104
+ const configContent = readFileSync(ROOT_CONFIG_PATH, "utf-8");
105
+ const config = JSON.parse(configContent);
106
+ if (!existsSync(ROOT_TOC_PATH)) {
107
+ throw new Error("\u6839\u76EE\u5F55 TOC.md \u4E0D\u5B58\u5728");
108
+ }
109
+ const tocContent = readFileSync(ROOT_TOC_PATH, "utf-8");
110
+ const { completedCount } = parseTocCompletedNotes(tocContent);
111
+ const now = /* @__PURE__ */ new Date();
112
+ const yearShort = String(now.getFullYear()).slice(-2);
113
+ const monthStr = String(now.getMonth() + 1).padStart(2, "0");
114
+ const currentKey = `${yearShort}.${monthStr}`;
115
+ const existing = { ...config.root_item.completed_notes_count || {} };
116
+ const _countsBeforeFix = Object.keys(existing).length;
117
+ existing[currentKey] = completedCount;
118
+ const fixed = fillMissingMonthGaps(existing, currentKey);
119
+ config.root_item = {
120
+ ...config.root_item,
121
+ completed_notes_count: fixed
122
+ };
123
+ delete config.root_item.completed_notes_count_last_month;
124
+ writeFileSync(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
125
+ if (!this.quiet) {
126
+ const filledCount = Object.keys(fixed).length - _countsBeforeFix;
127
+ const filledMsg = filledCount > 0 ? `\uFF0C\u8865\u9F50 ${filledCount} \u4E2A\u7F3A\u5931\u6708\u4EFD` : "";
128
+ this.logger.success(
129
+ `root_item \u914D\u7F6E\u5DF2\u66F4\u65B0: ${currentKey} \u6708\u5B8C\u6210 ${completedCount} \u7BC7\u7B14\u8BB0${filledMsg}`
130
+ );
131
+ }
132
+ } catch (error) {
133
+ if (!this.quiet) {
134
+ this.logger.error(
135
+ `\u66F4\u65B0 root_item \u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
136
+ );
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+ };
142
+ function fillMissingMonthGaps(counts, currentKey) {
143
+ const existingKeys = Object.keys(counts).sort();
144
+ const result = {};
145
+ const keySet = new Set(existingKeys);
146
+ keySet.add(currentKey);
147
+ const allKeys = Array.from(keySet).sort();
148
+ let prevValue = 0;
149
+ let lastKey = null;
150
+ for (const key of allKeys) {
151
+ if (lastKey !== null) {
152
+ const missing = generateMissingMonthKeys(lastKey, key);
153
+ for (const midKey of missing) {
154
+ result[midKey] = prevValue;
155
+ }
156
+ }
157
+ result[key] = counts[key] ?? prevValue;
158
+ prevValue = result[key];
159
+ lastKey = key;
160
+ }
161
+ return result;
162
+ }
163
+ function generateMissingMonthKeys(fromKey, toKey) {
164
+ const parseKey = (key) => {
165
+ const [yy, mm] = key.split(".").map(Number);
166
+ return { year: 2e3 + yy, month: mm };
167
+ };
168
+ const from = parseKey(fromKey);
169
+ const to = parseKey(toKey);
170
+ const missing = [];
171
+ let year = from.year;
172
+ let month = from.month;
173
+ while (true) {
174
+ month++;
175
+ if (month > 12) {
176
+ month = 1;
177
+ year++;
178
+ }
179
+ const shortYear = String(year).slice(-2);
180
+ const paddedMonth = String(month).padStart(2, "0");
181
+ const candidateKey = `${shortYear}.${paddedMonth}`;
182
+ if (candidateKey === toKey) break;
183
+ if (year > to.year || year === to.year && month > to.month) break;
184
+ missing.push(candidateKey);
185
+ }
186
+ return missing;
187
+ }
188
+
189
+ // commands/git/PushCommand.ts
190
+ var PushCommand = class extends BaseCommand {
191
+ constructor() {
192
+ super("push");
193
+ this.gitManager = new GitManager(ROOT_DIR_PATH);
194
+ this.gitService = new GitService();
195
+ this.timestampService = new TimestampService();
196
+ }
197
+ async run() {
198
+ try {
199
+ this.logger.info("\u68C0\u67E5\u662F\u5426\u6709\u66F4\u6539...");
200
+ const status = await this.gitManager.getStatus();
201
+ const hasPendingCommits = (status.ahead ?? 0) > 0;
202
+ if (!status.hasChanges && !hasPendingCommits) {
203
+ this.logger.info("\u6CA1\u6709\u66F4\u6539\u9700\u8981\u63A8\u9001");
204
+ return;
205
+ }
206
+ const force = this.options.force === true;
207
+ if (force) {
208
+ this.logger.warn("\u4F7F\u7528\u5F3A\u5236\u63A8\u9001\u6A21\u5F0F (--force)");
209
+ }
210
+ if (status.hasChanges) {
211
+ this.logger.info(
212
+ `\u68C0\u6D4B\u5230 ${status.files.length} \u4E2A\u53D8\u66F4\u6587\u4EF6\uFF0C\u6B63\u5728\u63D0\u4EA4...`
213
+ );
214
+ const commitMessage = this.gitService.generateCommitMessage();
215
+ await runCommand("git add -A", ROOT_DIR_PATH);
216
+ await runCommand(`git commit -m "${commitMessage}"`, ROOT_DIR_PATH);
217
+ this.logger.info("\u4FEE\u590D\u7B14\u8BB0\u65F6\u95F4\u6233...");
218
+ await this.timestampService.fixAllTimestamps(false);
219
+ const afterFixStatus = await runCommand(
220
+ "git -c core.quotePath=false status --porcelain",
221
+ ROOT_DIR_PATH
222
+ );
223
+ const hasTimestampChanges = afterFixStatus.split(/\r?\n/).filter(Boolean).some((line) => line.includes(".tnotes.json"));
224
+ if (hasTimestampChanges) {
225
+ this.logger.info("\u5C06\u65F6\u95F4\u6233\u53D8\u66F4\u5408\u5E76\u5230 commit...");
226
+ await runCommand("git add -u", ROOT_DIR_PATH);
227
+ await runCommand("git commit --amend --no-edit", ROOT_DIR_PATH);
228
+ }
229
+ } else {
230
+ this.logger.info(`\u68C0\u6D4B\u5230 ${status.ahead} \u4E2A\u672A\u63A8\u9001\u7684\u63D0\u4EA4\uFF0C\u76F4\u63A5\u63A8\u9001...`);
231
+ }
232
+ this.logger.info("\u6B63\u5728\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93...");
233
+ const pushCmd = force ? "git push --force" : "git push";
234
+ await runCommand(pushCmd, ROOT_DIR_PATH);
235
+ this.logger.success("\u63A8\u9001\u5B8C\u6210");
236
+ } catch (error) {
237
+ this.logger.error("\u63A8\u9001\u5931\u8D25:", error);
238
+ throw error;
239
+ }
240
+ }
241
+ };
242
+
243
+ // commands/git/PullCommand.ts
244
+ var PullCommand = class extends BaseCommand {
245
+ constructor() {
246
+ super("pull");
247
+ this.gitService = new GitService();
248
+ }
249
+ async run() {
250
+ this.logger.info("\u6B63\u5728\u4ECE\u8FDC\u7A0B\u4ED3\u5E93\u62C9\u53D6...");
251
+ await this.gitService.pull();
252
+ this.logger.success("\u62C9\u53D6\u5B8C\u6210");
253
+ }
254
+ };
255
+
256
+ // commands/build/BuildCommand.ts
257
+ var BuildCommand = class extends BaseCommand {
258
+ constructor() {
259
+ super("build");
260
+ this.vitepressService = new VitepressService();
261
+ }
262
+ async run() {
263
+ this.logger.info("\u5F00\u59CB\u6784\u5EFA\u77E5\u8BC6\u5E93...");
264
+ await this.vitepressService.build();
265
+ this.logger.success("\u77E5\u8BC6\u5E93\u6784\u5EFA\u5B8C\u6210");
266
+ }
267
+ };
268
+
269
+ // commands/build/PreviewCommand.ts
270
+ var PreviewCommand = class extends BaseCommand {
271
+ constructor() {
272
+ super("preview");
273
+ this.vitepressService = new VitepressService();
274
+ }
275
+ async run() {
276
+ this.logger.info("\u542F\u52A8\u9884\u89C8\u670D\u52A1\u5668...");
277
+ const pid = await this.vitepressService.preview();
278
+ if (pid) {
279
+ this.logger.success(`\u9884\u89C8\u670D\u52A1\u5668\u5DF2\u542F\u52A8 (PID: ${pid})`);
280
+ } else {
281
+ this.logger.error("\u542F\u52A8\u9884\u89C8\u670D\u52A1\u5668\u5931\u8D25");
282
+ }
283
+ }
284
+ };
285
+
286
+ // commands/dev/DevCommand.ts
287
+ var DevCommand = class extends BaseCommand {
288
+ constructor() {
289
+ super("dev");
290
+ this.configManager = ConfigManager.getInstance();
291
+ this.fileWatcherService = new FileWatcherService();
292
+ this.noteIndexCache = NoteIndexCache.getInstance();
293
+ this.noteManager = NoteManager.getInstance();
294
+ this.tocService = TocService.getInstance();
295
+ this.vitepressService = new VitepressService();
296
+ }
297
+ async run() {
298
+ const notes = this.noteManager.scanNotes();
299
+ this.logger.info(`\u626B\u63CF\u5230 ${notes.length} \u7BC7\u7B14\u8BB0`);
300
+ this.noteIndexCache.initialize(notes);
301
+ this.tocService.ensureTocExists();
302
+ await this.tocService.regenerateSidebar(notes);
303
+ const result = await this.vitepressService.startServer();
304
+ if (result) {
305
+ const versionInfo = result.version ? `\uFF08v${result.version}\uFF09` : "";
306
+ this.logger.success(
307
+ `VitePress \u670D\u52A1${versionInfo}\u5DF2\u5C31\u7EEA\uFF0C\u8017\u65F6\uFF1A${result.elapsed} ms`
308
+ );
309
+ const watcherStart = Date.now();
310
+ this.fileWatcherService.start();
311
+ const watcherElapsed = Date.now() - watcherStart;
312
+ this.logger.success(`\u6587\u4EF6\u76D1\u542C\u670D\u52A1\u5DF2\u5C31\u7EEA\uFF0C\u8017\u65F6\uFF1A${watcherElapsed} ms`);
313
+ const port2 = this.configManager.get("port") || VitepressService.DEFAULT_DEV_PORT;
314
+ const repoName2 = this.configManager.get("repoName");
315
+ this.logger.info(
316
+ `\u672C\u5730\u5F00\u53D1\u670D\u52A1\u5730\u5740\uFF1Ahttp://localhost:${port2}/${repoName2}/`
317
+ );
318
+ } else {
319
+ this.logger.error("\u542F\u52A8\u670D\u52A1\u5668\u5931\u8D25");
320
+ }
321
+ }
322
+ };
323
+
324
+ // commands/init-sub-repo/InitSubRepoCommand.ts
325
+ import { readFileSync as readFileSync2 } from "fs";
326
+ import { join, dirname } from "path";
327
+ import { createInterface } from "readline";
328
+ import { fileURLToPath } from "url";
329
+ import { v4 as uuidv4 } from "uuid";
330
+ function readCoreVersion() {
331
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
332
+ let dir = moduleDir;
333
+ while (dir !== dirname(dir)) {
334
+ const pkgPath = join(dir, "package.json");
335
+ try {
336
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
337
+ if (pkg.name === "@tnotesjs/core" && pkg.version) return pkg.version;
338
+ } catch {
339
+ }
340
+ dir = dirname(dir);
341
+ }
342
+ return "0.1.28";
343
+ }
344
+ var InitSubRepoCommand = class extends BaseCommand {
345
+ constructor() {
346
+ super("init-sub-repo");
347
+ this.rootPath = process.cwd();
348
+ }
349
+ async run() {
350
+ if (isAlreadyInitialized(this.rootPath)) {
351
+ const configPath = getInitializedConfigPath(this.rootPath);
352
+ this.logger.info(
353
+ `\u68C0\u6D4B\u5230 .tnotes.json \u914D\u7F6E\u6587\u4EF6\uFF1A${configPath}\uFF0C\u5F53\u524D\u4ED3\u5E93\u5DF2\u7ECF\u662F\u4E00\u4E2A TNotes \u77E5\u8BC6\u5E93`
354
+ );
355
+ return;
356
+ }
357
+ this.logger.info("\u521D\u59CB\u5316 TNotes \u5B50\u77E5\u8BC6\u5E93");
358
+ this.logger.info("");
359
+ const input = await this.promptForInput();
360
+ const ctx = buildInitContext(input);
361
+ this.logger.info("");
362
+ this.logger.info("\u5373\u5C06\u521B\u5EFA\u4EE5\u4E0B\u77E5\u8BC6\u5E93\uFF1A");
363
+ this.logger.info(` \u4ED3\u5E93\u540D\uFF1A${ctx.repoName}`);
364
+ this.logger.info(` \u5C55\u793A\u540D\uFF1A${ctx.displayName}`);
365
+ this.logger.info(` \u7AEF\u53E3\uFF1A${ctx.port}`);
366
+ this.logger.info(` \u63CF\u8FF0\uFF1A${ctx.description}`);
367
+ this.logger.info("");
368
+ const confirmed = await this.promptConfirm("\u786E\u8BA4\u521D\u59CB\u5316\uFF1F [Y/n] ");
369
+ if (!confirmed) {
370
+ this.logger.info("\u5DF2\u53D6\u6D88\u521D\u59CB\u5316");
371
+ return;
372
+ }
373
+ const service = InitSubRepoService.fromModuleUrl(import.meta.url, this.rootPath);
374
+ const result = service.writeScaffold(input);
375
+ if (result.created.length > 0) {
376
+ this.logger.success(`\u5DF2\u521B\u5EFA ${result.created.length} \u4E2A\u6587\u4EF6\uFF1A`);
377
+ for (const file of result.created) {
378
+ this.logger.info(` + ${file}`);
379
+ }
380
+ }
381
+ if (result.updated.length > 0) {
382
+ this.logger.success(`\u5DF2\u66F4\u65B0 ${result.updated.length} \u4E2A\u6587\u4EF6\uFF1A`);
383
+ for (const file of result.updated) {
384
+ this.logger.info(` ~ ${file}`);
385
+ }
386
+ }
387
+ if (result.skipped.length > 0) {
388
+ this.logger.warn(`\u5DF2\u8DF3\u8FC7 ${result.skipped.length} \u4E2A\u5DF2\u5B58\u5728\u6587\u4EF6\uFF1A`);
389
+ for (const file of result.skipped) {
390
+ this.logger.info(` - ${file}`);
391
+ }
392
+ }
393
+ getConfigManager().clearCache();
394
+ try {
395
+ this.logger.info("");
396
+ this.logger.info("\u6B63\u5728\u89C4\u8303\u5316 TOC.md \u5E76\u751F\u6210 sidebar.json...");
397
+ const noteService = NoteService.getInstance();
398
+ const tocService = TocService.getInstance();
399
+ const notes = noteService.getAllNotes();
400
+ await tocService.normalizeToc(notes);
401
+ await tocService.regenerateSidebar(notes);
402
+ this.logger.success("sidebar.json \u5DF2\u751F\u6210");
403
+ } catch (error) {
404
+ this.logger.warn(
405
+ `\u81EA\u52A8 update \u5931\u8D25\uFF08\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C pnpm tn:update\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`
406
+ );
407
+ }
408
+ this.logger.info("");
409
+ this.logger.success("\u5B50\u77E5\u8BC6\u5E93\u521D\u59CB\u5316\u5B8C\u6210\uFF01");
410
+ this.logger.info("");
411
+ this.logger.info("\u8BF7\u624B\u52A8\u5B8C\u6210\u4EE5\u4E0B\u6B65\u9AA4\uFF1A");
412
+ for (const [index, step] of buildManualSteps(ctx).entries()) {
413
+ this.logger.info(` ${index + 1}. ${step}`);
414
+ }
415
+ }
416
+ async promptForInput() {
417
+ const topic = await this.promptTopic();
418
+ const displayName = await this.promptDisplayName(topic);
419
+ const port2 = await this.promptPort();
420
+ const description = await this.promptDescription();
421
+ return {
422
+ topic,
423
+ displayName,
424
+ port: port2,
425
+ description,
426
+ repoUuid: uuidv4(),
427
+ noteUuid: uuidv4(),
428
+ coreVersion: readCoreVersion()
429
+ };
430
+ }
431
+ async promptTopic() {
432
+ while (true) {
433
+ const answer = await this.ask("\u8BF7\u8F93\u5165 topic\uFF08\u5982 react\uFF0C\u5C06\u751F\u6210 TNotes.react\uFF09: ");
434
+ const error = validateTopic(answer);
435
+ if (error) {
436
+ this.logger.warn(error);
437
+ continue;
438
+ }
439
+ return answer.trim();
440
+ }
441
+ }
442
+ async promptDisplayName(topic) {
443
+ const fallback = defaultDisplayName(topic);
444
+ const answer = await this.ask(
445
+ `\u8BF7\u8F93\u5165\u5C55\u793A\u540D\uFF08\u7528\u4E8E\u9996\u9875 hero\uFF0C\u9ED8\u8BA4 ${fallback}\uFF09: `
446
+ );
447
+ return answer.trim() || fallback;
448
+ }
449
+ async promptPort() {
450
+ const answer = await this.ask(`\u8BF7\u8F93\u5165 dev \u7AEF\u53E3\uFF08\u9ED8\u8BA4 ${DEFAULT_PORT}\uFF09: `);
451
+ const { port: port2, error } = validatePort(answer);
452
+ if (error) this.logger.warn(error);
453
+ return port2;
454
+ }
455
+ async promptDescription() {
456
+ const answer = await this.ask(
457
+ `\u8BF7\u8F93\u5165\u4ED3\u5E93\u63CF\u8FF0\uFF08\u9ED8\u8BA4\uFF1A${DEFAULT_DESCRIPTION}\uFF09: `
458
+ );
459
+ return answer.trim() || DEFAULT_DESCRIPTION;
460
+ }
461
+ async promptConfirm(question) {
462
+ const answer = await this.ask(question);
463
+ const normalized = answer.trim().toLowerCase();
464
+ return normalized === "" || normalized === "y" || normalized === "yes";
465
+ }
466
+ ask(question) {
467
+ const rl = createInterface({
468
+ input: process.stdin,
469
+ output: process.stdout
470
+ });
471
+ return new Promise((resolve) => {
472
+ rl.question(question, (answer) => {
473
+ rl.close();
474
+ resolve(answer);
475
+ });
476
+ });
477
+ }
478
+ };
479
+
480
+ // commands/init-toc/InitTocCommand.ts
481
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
482
+ var InitTocCommand = class extends BaseCommand {
483
+ constructor() {
484
+ super("init-toc");
485
+ this.noteManager = NoteManager.getInstance();
486
+ this.tocService = TocService.getInstance();
487
+ }
488
+ async run() {
489
+ if (!existsSync2(ROOT_README_PATH)) {
490
+ throw new Error(`README.md \u4E0D\u5B58\u5728: ${ROOT_README_PATH}`);
491
+ }
492
+ const readmeContent = readFileSync3(ROOT_README_PATH, "utf-8");
493
+ const notes = this.noteManager.scanNotes();
494
+ const { entries, warnings } = migrateReadmeToToc(readmeContent);
495
+ for (const warning of warnings) {
496
+ this.logger.warn(warning);
497
+ }
498
+ const lines = [];
499
+ let skipped = 0;
500
+ for (const entry of entries) {
501
+ if (entry.kind === "folder") {
502
+ lines.push(buildFolderTocLine(entry.folderTitle, entry.indent));
503
+ continue;
504
+ }
505
+ const note = resolveNoteFromIndex(entry.noteIndex, notes);
506
+ if (!note) {
507
+ skipped++;
508
+ this.logger.warn(
509
+ `\u8DF3\u8FC7 README \u4E2D\u5B58\u5728\u4F46 notes/ \u76EE\u5F55\u4E2D\u4E0D\u5B58\u5728\u7684\u7B14\u8BB0: ${entry.noteIndex}`
510
+ );
511
+ continue;
512
+ }
513
+ lines.push(buildTocLine(note, entry.indent, entry.completed));
514
+ }
515
+ if (lines.length === 0) {
516
+ throw new Error(
517
+ "\u672A\u80FD\u4ECE README.md \u89E3\u6790\u5230\u4EFB\u4F55\u6709\u6548\u7B14\u8BB0\u6761\u76EE\uFF0CTOC.md \u672A\u5199\u5165"
518
+ );
519
+ }
520
+ const content = processTocEmptyLines(lines).join("\n");
521
+ writeFileSync2(ROOT_TOC_PATH, content, "utf-8");
522
+ await this.tocService.regenerateSidebar(notes);
523
+ this.logger.success(
524
+ `TOC.md \u5DF2\u4ECE README.md \u521D\u59CB\u5316\uFF08${lines.length} \u6761\u7B14\u8BB0${skipped > 0 ? `\uFF0C\u8DF3\u8FC7 ${skipped} \u6761` : ""}\uFF09`
525
+ );
526
+ this.logger.info(`\u8F93\u51FA\u8DEF\u5F84: ${ROOT_TOC_PATH}`);
527
+ this.logger.info(
528
+ "\u8FC7\u6E21\u547D\u4EE4 init-toc \u4EC5\u5728 v0.2.x \u63D0\u4F9B\uFF0Cv0.3.x \u5C06\u79FB\u9664\uFF1B\u540E\u7EED\u8BF7\u4F7F\u7528 TOC.md + pnpm tn:update"
529
+ );
530
+ const listedIndexes = new Set(
531
+ entries.filter((e) => e.kind === "note").map((e) => e.noteIndex)
532
+ );
533
+ const missingInReadme = notes.filter((n) => !listedIndexes.has(n.index));
534
+ if (missingInReadme.length > 0) {
535
+ this.logger.info(
536
+ `README \u4E2D\u672A\u5217\u51FA ${missingInReadme.length} \u7BC7\u7B14\u8BB0\uFF0C\u53EF\u6267\u884C pnpm tn:update \u8FFD\u52A0\u5230 TOC.md`
537
+ );
538
+ }
539
+ }
540
+ };
541
+
542
+ // commands/maintenance/FixTimestampsCommand.ts
543
+ var FixTimestampsCommand = class extends BaseCommand {
544
+ constructor() {
545
+ super("fix-timestamps");
546
+ this.timestampService = new TimestampService();
547
+ }
548
+ async run() {
549
+ this.logger.info("\u5F00\u59CB\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233...");
550
+ this.logger.info("\u{1F4CC} \u6B64\u64CD\u4F5C\u4F1A\u5C06\u6240\u6709\u65F6\u95F4\u6233\u66F4\u65B0\u4E3A git \u771F\u5B9E\u65F6\u95F4");
551
+ this.logger.info("");
552
+ const result = await this.timestampService.fixAllTimestamps(true);
553
+ this.logger.info("");
554
+ this.logger.info("\u{1F4CA} \u4FEE\u590D\u7EDF\u8BA1:");
555
+ this.logger.info(
556
+ ` - \u6839\u914D\u7F6E\u6587\u4EF6: ${result.rootConfigFixed ? "\u5DF2\u4FEE\u590D" : "\u65E0\u9700\u4FEE\u590D"}`
557
+ );
558
+ this.logger.info(` - \u603B\u7B14\u8BB0\u6570: ${result.total}`);
559
+ this.logger.info(` - \u5DF2\u4FEE\u590D: ${result.fixed}`);
560
+ this.logger.info(` - \u8DF3\u8FC7: ${result.skipped}`);
561
+ this.logger.info("");
562
+ if (result.fixed > 0 || result.rootConfigFixed) {
563
+ this.logger.success(
564
+ `\u2705 \u6210\u529F\u4FEE\u590D ${result.fixed} \u4E2A\u7B14\u8BB0${result.rootConfigFixed ? " + \u6839\u914D\u7F6E\u6587\u4EF6" : ""}\u7684\u65F6\u95F4\u6233\uFF01`
565
+ );
566
+ this.logger.info("\u{1F4A1} \u63D0\u793A: \u8FD0\u884C pnpm tn:push \u63D0\u4EA4\u66F4\u6539");
567
+ } else {
568
+ this.logger.success("\u2705 \u6240\u6709\u65F6\u95F4\u6233\u5747\u5DF2\u6B63\u786E\uFF01");
569
+ }
570
+ }
571
+ };
572
+
573
+ // commands/misc/HelpCommand.ts
574
+ var COMMAND_CATEGORIES = {
575
+ \u5F00\u53D1\u548C\u6784\u5EFA: [COMMAND_NAMES.DEV, COMMAND_NAMES.BUILD, COMMAND_NAMES.PREVIEW],
576
+ \u5185\u5BB9\u7BA1\u7406: [
577
+ COMMAND_NAMES.UPDATE,
578
+ COMMAND_NAMES.UPDATE_COMPLETED_COUNT,
579
+ COMMAND_NAMES.CREATE_NOTES
580
+ ],
581
+ "Git \u64CD\u4F5C": [COMMAND_NAMES.PUSH, COMMAND_NAMES.PULL],
582
+ "\u8FC1\u79FB\uFF08v0.2.x \u8FC7\u6E21\uFF09": [COMMAND_NAMES.INIT_TOC],
583
+ \u521D\u59CB\u5316: [COMMAND_NAMES.INIT_SUB_REPO],
584
+ \u5176\u4ED6: [COMMAND_NAMES.FIX_TIMESTAMPS, COMMAND_NAMES.HELP]
585
+ };
586
+ var COMMAND_OPTIONS_INFO = {
587
+ [COMMAND_OPTIONS.QUIET]: {
588
+ description: "\u9759\u9ED8\u6A21\u5F0F",
589
+ applicableTo: "update"
590
+ },
591
+ [COMMAND_OPTIONS.FORCE]: {
592
+ description: "\u5F3A\u5236\u63A8\u9001",
593
+ applicableTo: "push"
594
+ }
595
+ };
596
+ var HelpCommand = class extends BaseCommand {
597
+ constructor() {
598
+ super("help");
599
+ this.logger = createLogger("help", {
600
+ timestamp: false,
601
+ level: process.env.DEBUG ? 0 /* DEBUG */ : 1 /* INFO */
602
+ });
603
+ }
604
+ async run() {
605
+ this.logger.info("TNotes \u547D\u4EE4\u884C\u5DE5\u5177");
606
+ this.logger.info("");
607
+ this.logger.info("\u7528\u6CD5\uFF1Apnpm tn:<command> # \u63A8\u8350");
608
+ this.logger.info("\u6216\u8005\uFF1Anpx tsx ./.vitepress/tnotes/index.ts --<command>");
609
+ this.logger.info("");
610
+ this.logger.info("\u53EF\u7528\u547D\u4EE4\uFF1A");
611
+ this.logger.info("");
612
+ for (const [category, cmdNames] of Object.entries(COMMAND_CATEGORIES)) {
613
+ this.logger.info(` ${category}:`);
614
+ for (const cmdName of cmdNames) {
615
+ const description = COMMAND_DESCRIPTIONS[cmdName];
616
+ const paddingLength = Math.max(25 - cmdName.length, 1);
617
+ const padding = " ".repeat(paddingLength);
618
+ this.logger.info(` --${cmdName}${padding}${description}`);
619
+ }
620
+ this.logger.info("");
621
+ }
622
+ this.logger.info("\u793A\u4F8B\uFF1A");
623
+ this.logger.info(" npx tsx ./.vitepress/tnotes/index.ts --dev");
624
+ this.logger.info(" pnpm tn:build");
625
+ this.logger.info(" pnpm tn:create-notes # \u6279\u91CF\u521B\u5EFA\u7B14\u8BB0");
626
+ this.logger.info(" pnpm tn:update");
627
+ this.logger.info(
628
+ " pnpm tn:init-toc # v0.1.x \u2192 v0.2.x\uFF1A\u4ECE README.md \u751F\u6210 TOC.md"
629
+ );
630
+ this.logger.info(
631
+ " pnpm tn:update-completed-count # \u751F\u6210\u5F53\u524D\u77E5\u8BC6\u5E93\u6700\u8FD1 12 \u4E2A\u6708\u7684\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u7EDF\u8BA1"
632
+ );
633
+ this.logger.info("");
634
+ this.logger.info("\u53C2\u6570\uFF1A");
635
+ for (const [option, info] of Object.entries(COMMAND_OPTIONS_INFO)) {
636
+ const paddingLength = Math.max(13 - option.length, 1);
637
+ const padding = " ".repeat(paddingLength);
638
+ this.logger.info(
639
+ ` --${option}${padding}${info.description} (\u9002\u7528\u4E8E ${info.applicableTo})`
640
+ );
641
+ }
642
+ this.logger.info("");
643
+ this.logger.info("\u73AF\u5883\u53D8\u91CF\uFF1A");
644
+ this.logger.info(" DEBUG=1 \u542F\u7528\u8C03\u8BD5\u6A21\u5F0F\uFF0C\u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7");
645
+ this.logger.info("");
646
+ this.logger.info("\u66F4\u591A\u4FE1\u606F\u8BF7\u67E5\u770B: .vitepress/tnotes/README.md");
647
+ }
648
+ };
649
+
650
+ // commands/note/CreateNoteCommand.ts
651
+ import { createInterface as createInterface2 } from "readline";
652
+ import { v4 as uuidv42 } from "uuid";
653
+ var CreateNoteCommand = class extends BaseCommand {
654
+ constructor() {
655
+ super("create-notes");
656
+ this.noteService = NoteService.getInstance();
657
+ this.readmeService = ReadmeService.getInstance();
658
+ }
659
+ async run() {
660
+ this.logger.info("\u521B\u5EFA\u65B0\u7B14\u8BB0...");
661
+ const count = await this.promptForCount();
662
+ let successCount = 0;
663
+ let failCount = 0;
664
+ const createdNotes = [];
665
+ const existingNotes = this.noteService.getAllNotes();
666
+ const usedIndexes = /* @__PURE__ */ new Set();
667
+ for (const note of existingNotes) {
668
+ const index = parseInt(note.index, 10);
669
+ if (!isNaN(index) && index >= 1 && index <= 9999) {
670
+ usedIndexes.add(index);
671
+ }
672
+ }
673
+ for (let i = 1; i <= count; i++) {
674
+ try {
675
+ let title;
676
+ if (count === 1) {
677
+ title = await this.promptForTitle();
678
+ } else {
679
+ title = `new`;
680
+ this.logger.info(`[${i}/${count}] \u521B\u5EFA\u7B14\u8BB0: ${title}`);
681
+ }
682
+ const configId = uuidv42();
683
+ const note = await this.noteService.createNote({
684
+ title: title || `new`,
685
+ enableDiscussions: false,
686
+ configId,
687
+ // 传递 UUID 作为配置 ID(跨知识库唯一)
688
+ usedIndexes
689
+ // 传入已使用编号集合,避免重复扫描
690
+ });
691
+ const newIndex = parseInt(note.index, 10);
692
+ if (!isNaN(newIndex)) {
693
+ usedIndexes.add(newIndex);
694
+ }
695
+ createdNotes.push(note.dirName);
696
+ successCount++;
697
+ if (count === 1) {
698
+ this.logger.success(`\u7B14\u8BB0\u521B\u5EFA\u6210\u529F: ${note.dirName}`);
699
+ this.logger.info(`\u7B14\u8BB0\u8DEF\u5F84: ${note.path}`);
700
+ this.logger.info(`\u7B14\u8BB0\u7F16\u53F7: ${note.index}`);
701
+ this.logger.info(`\u914D\u7F6EID: ${configId}`);
702
+ } else {
703
+ this.logger.success(`[${i}/${count}] \u521B\u5EFA\u6210\u529F: ${note.dirName}`);
704
+ }
705
+ } catch (error) {
706
+ failCount++;
707
+ this.logger.error(`[${i}/${count}] \u521B\u5EFA\u5931\u8D25`, error);
708
+ }
709
+ }
710
+ if (count > 1) {
711
+ console.log("");
712
+ this.logger.info(
713
+ `\u{1F4CA} \u521B\u5EFA\u5B8C\u6210: \u6210\u529F ${successCount} \u7BC7, \u5931\u8D25 ${failCount} \u7BC7`
714
+ );
715
+ }
716
+ if (successCount > 0) {
717
+ this.logger.info("\u6B63\u5728\u66F4\u65B0\u77E5\u8BC6\u5E93\u7D22\u5F15...");
718
+ await this.readmeService.updateAllReadmes();
719
+ this.logger.success("\u77E5\u8BC6\u5E93\u7D22\u5F15\u66F4\u65B0\u5B8C\u6210");
720
+ }
721
+ }
722
+ /**
723
+ * 提示用户输入要创建的笔记数量
724
+ */
725
+ async promptForCount() {
726
+ const rl = createInterface2({
727
+ input: process.stdin,
728
+ output: process.stdout
729
+ });
730
+ return new Promise((resolve) => {
731
+ rl.question("\n\u{1F4DD} \u8BF7\u8F93\u5165\u8981\u521B\u5EFA\u7684\u7B14\u8BB0\u6570\u91CF\uFF08\u9ED8\u8BA4\u4E3A 1\uFF09: ", (answer) => {
732
+ rl.close();
733
+ const trimmed = answer.trim();
734
+ if (!trimmed) {
735
+ this.logger.info("\u4F7F\u7528\u9ED8\u8BA4\u6570\u91CF: 1");
736
+ resolve(1);
737
+ return;
738
+ }
739
+ const num = parseInt(trimmed, 10);
740
+ if (isNaN(num) || num < 1 || !Number.isInteger(num)) {
741
+ this.logger.warn(`\u8F93\u5165 "${trimmed}" \u4E0D\u662F\u6709\u6548\u7684\u6B63\u6574\u6570\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C: 1`);
742
+ resolve(1);
743
+ return;
744
+ }
745
+ this.logger.info(`\u5C06\u521B\u5EFA ${num} \u7BC7\u7B14\u8BB0`);
746
+ resolve(num);
747
+ });
748
+ });
749
+ }
750
+ /**
751
+ * 提示用户输入笔记标题
752
+ */
753
+ async promptForTitle() {
754
+ const rl = createInterface2({
755
+ input: process.stdin,
756
+ output: process.stdout
757
+ });
758
+ return new Promise((resolve) => {
759
+ rl.question("\u8BF7\u8F93\u5165\u7B14\u8BB0\u6807\u9898: ", (answer) => {
760
+ rl.close();
761
+ resolve(answer.trim());
762
+ });
763
+ });
764
+ }
765
+ };
766
+
767
+ // commands/update-completed-count/UpdateCompletedCountCommand.ts
768
+ import { execSync } from "child_process";
769
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
770
+ var UpdateCompletedCountCommand = class extends BaseCommand {
771
+ constructor() {
772
+ super("update-completed-count");
773
+ }
774
+ async run() {
775
+ await this.updateCurrentRepo();
776
+ }
777
+ /**
778
+ * 更新当前知识库
779
+ */
780
+ async updateCurrentRepo() {
781
+ const startTime = Date.now();
782
+ try {
783
+ const configContent = readFileSync4(ROOT_CONFIG_PATH, "utf-8");
784
+ const config = JSON.parse(configContent);
785
+ this.logger.info("\u5F00\u59CB\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55...");
786
+ const completedNotesCountHistory = await this.getCompletedNotesCountHistory(
787
+ config.root_item.created_at ?? Date.now()
788
+ );
789
+ config.root_item = {
790
+ ...config.root_item,
791
+ completed_notes_count: completedNotesCountHistory
792
+ };
793
+ writeFileSync3(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
794
+ const duration = Date.now() - startTime;
795
+ const monthKeys = Object.keys(completedNotesCountHistory);
796
+ const currentKey = monthKeys[monthKeys.length - 1];
797
+ const currentCount = completedNotesCountHistory[currentKey] || 0;
798
+ this.logger.success(
799
+ `\u5386\u53F2\u6570\u636E\u66F4\u65B0\u5B8C\u6210: \u5171 ${monthKeys.length} \u4E2A\u6708, \u5F53\u524D ${currentKey} \u6708\u5B8C\u6210 ${currentCount} \u7BC7\u7B14\u8BB0 (${duration}ms)`
800
+ );
801
+ } catch (error) {
802
+ this.logger.error(
803
+ `\u66F4\u65B0\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
804
+ );
805
+ throw error;
806
+ }
807
+ }
808
+ /**
809
+ * 获取历史每个月的 completed_notes_count(最近12个月)
810
+ *
811
+ * 逻辑:
812
+ * 1. 计算最近12个月的范围(当前月份往前推11个月)
813
+ * 2. 遍历这12个月,从 Git 历史中读取 TOC.md(回退 README.md)
814
+ * 3. 解析获取完成笔记数量
815
+ * 4. 如果知识库创建时间在这12个月内,之前的月份补0
816
+ * 5. 返回对象 { '25.01': 0, '25.02': 1, ..., '25.12': 15 }
817
+ */
818
+ async getCompletedNotesCountHistory(createdAt) {
819
+ try {
820
+ const now = /* @__PURE__ */ new Date();
821
+ const currentYear = now.getFullYear();
822
+ const currentMonth = now.getMonth();
823
+ let firstYear = currentYear;
824
+ let firstMonth = currentMonth - 11;
825
+ if (firstMonth < 0) {
826
+ firstYear = currentYear - 1;
827
+ firstMonth = 12 + firstMonth;
828
+ }
829
+ const createdDate = new Date(createdAt);
830
+ const createdYear = createdDate.getFullYear();
831
+ const createdMonth = createdDate.getMonth();
832
+ const result = {};
833
+ let prevCount = 0;
834
+ for (let i = 0; i < 12; i++) {
835
+ const targetYear = firstYear + Math.floor((firstMonth + i) / 12);
836
+ const targetMonth = (firstMonth + i) % 12;
837
+ const yearShort = String(targetYear).slice(-2);
838
+ const monthStr = String(targetMonth + 1).padStart(2, "0");
839
+ const key = `${yearShort}.${monthStr}`;
840
+ const isBeforeCreation = targetYear < createdYear || targetYear === createdYear && targetMonth < createdMonth;
841
+ if (isBeforeCreation) {
842
+ result[key] = 0;
843
+ prevCount = 0;
844
+ this.logger.info(`\u2713 ${key}: 0 \u7BC7\uFF08\u65E9\u4E8E\u521B\u5EFA\u65F6\u95F4\uFF09`);
845
+ } else {
846
+ try {
847
+ const count = await this.getMonthCompletedCount(
848
+ targetYear,
849
+ targetMonth,
850
+ prevCount
851
+ );
852
+ result[key] = count;
853
+ prevCount = count;
854
+ this.logger.info(`\u2713 ${key}: ${count} \u7BC7`);
855
+ } catch (error) {
856
+ result[key] = prevCount;
857
+ this.logger.warn(`${key}: \u65E0\u6570\u636E\uFF0C\u4F7F\u7528 ${prevCount}\uFF08\u4E0A\u6708\u503C\uFF09`);
858
+ }
859
+ }
860
+ }
861
+ return result;
862
+ } catch (error) {
863
+ this.logger.error(
864
+ `\u83B7\u53D6\u5386\u53F2\u6570\u636E\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
865
+ );
866
+ return {};
867
+ }
868
+ }
869
+ /**
870
+ * 获取指定月份的完成笔记数量
871
+ * @param year - 年份
872
+ * @param month - 月份 (0-11)
873
+ * @param fallbackCount - 回退值(如果该月没有数据)
874
+ * @returns 完成笔记数量
875
+ */
876
+ async getMonthCompletedCount(year, month, fallbackCount = 0) {
877
+ const lastDayOfMonth = new Date(year, month + 1, 0, 23, 59, 59);
878
+ const yearStr = lastDayOfMonth.getFullYear();
879
+ const monthStr = String(lastDayOfMonth.getMonth() + 1).padStart(2, "0");
880
+ const dayStr = String(lastDayOfMonth.getDate()).padStart(2, "0");
881
+ const untilDate = `${yearStr}-${monthStr}-${dayStr} 23:59:59 +0800`;
882
+ let commitHash = execSync(
883
+ `git log --until="${untilDate}" --format=%H -1 -- TOC.md`,
884
+ {
885
+ cwd: ROOT_DIR_PATH,
886
+ encoding: "utf-8"
887
+ }
888
+ ).trim();
889
+ let filePath = "TOC.md";
890
+ if (!commitHash) {
891
+ commitHash = execSync(
892
+ `git log --until="${untilDate}" --format=%H -1 -- README.md`,
893
+ {
894
+ cwd: ROOT_DIR_PATH,
895
+ encoding: "utf-8"
896
+ }
897
+ ).trim();
898
+ filePath = "README.md";
899
+ }
900
+ if (!commitHash) {
901
+ return fallbackCount;
902
+ }
903
+ let fileContent;
904
+ try {
905
+ fileContent = execSync(`git show ${commitHash}:${filePath}`, {
906
+ cwd: ROOT_DIR_PATH,
907
+ encoding: "utf-8"
908
+ });
909
+ } catch (error) {
910
+ return fallbackCount;
911
+ }
912
+ const { completedCount } = filePath === "TOC.md" ? parseTocCompletedNotes(fileContent) : parseReadmeCompletedNotes(fileContent);
913
+ return completedCount;
914
+ }
915
+ };
916
+
917
+ // commands/registry.ts
918
+ var commandFactories = {
919
+ "build": () => new BuildCommand(),
920
+ "create-notes": () => new CreateNoteCommand(),
921
+ "dev": () => new DevCommand(),
922
+ "fix-timestamps": () => new FixTimestampsCommand(),
923
+ "init-sub-repo": () => new InitSubRepoCommand(),
924
+ "init-toc": () => new InitTocCommand(),
925
+ "help": () => new HelpCommand(),
926
+ "preview": () => new PreviewCommand(),
927
+ "pull": () => new PullCommand(),
928
+ "push": () => new PushCommand(),
929
+ "rename-note": () => new RenameNoteCommand(),
930
+ "update-completed-count": () => new UpdateCompletedCountCommand(),
931
+ "update-note-config": () => new UpdateNoteConfigCommand(),
932
+ "update": () => new UpdateCommand()
933
+ };
934
+ var commandCache = /* @__PURE__ */ new Map();
935
+ function getCommand(name) {
936
+ if (!commandFactories[name]) return void 0;
937
+ if (!commandCache.has(name)) commandCache.set(name, commandFactories[name]());
938
+ return commandCache.get(name);
939
+ }
940
+
941
+ // index.ts
942
+ (async () => {
943
+ try {
944
+ const args = parseArgs(process.argv.slice(2));
945
+ const commandName = Object.keys(args).find(
946
+ (key) => key !== "_" && args[key] === true
947
+ );
948
+ const command = commandName ? getCommand(commandName) : null;
949
+ if (!command) {
950
+ const logger2 = createLogger("command-not-found", {
951
+ timestamp: false
952
+ });
953
+ console.log(`
954
+ ${"-".repeat(66)}
955
+ `);
956
+ if (commandName) {
957
+ logger2.warn(`\u672A\u627E\u5230\u547D\u4EE4\uFF1A${commandName}`);
958
+ logger2.info(`\u8BF7\u68C0\u67E5\u547D\u4EE4\u540D\u662F\u5426\u6B63\u786E\uFF01`);
959
+ } else {
960
+ logger2.warn(`\u672A\u68C0\u6D4B\u5230\u547D\u4EE4\u540D\uFF0C\u8BF7\u68C0\u67E5\u547D\u4EE4\u8F93\u5165\u662F\u5426\u6B63\u786E`);
961
+ logger2.info(`\u793A\u4F8B\uFF1A`);
962
+ logger2.info(`pnpm tn:<\u547D\u4EE4\u540D>`);
963
+ logger2.info(`npx tsx ./.vitepress/tnotes/index.ts --<\u547D\u4EE4\u540D>`);
964
+ }
965
+ console.log(`
966
+ ${"-".repeat(66)}
967
+ `);
968
+ const helpCommand = getCommand(COMMAND_NAMES.HELP);
969
+ if (helpCommand) {
970
+ console.log("\n\u6B63\u5728\u6267\u884C pnpm tn:help \u6253\u5370\u5E2E\u52A9\u4FE1\u606F...\n");
971
+ await helpCommand.execute();
972
+ }
973
+ return;
974
+ }
975
+ if (commandName === COMMAND_NAMES.UPDATE) {
976
+ const cmd = command;
977
+ if (args.quiet) cmd.setQuiet(true);
978
+ } else if (commandName === COMMAND_NAMES.PUSH) {
979
+ const cmd = command;
980
+ if (args.force) cmd.setOptions({ force: true });
981
+ }
982
+ await command.execute();
983
+ } catch (error) {
984
+ handleError(error, true);
985
+ }
986
+ })();