@tnotesjs/core 0.0.7 → 0.1.14

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 (53) hide show
  1. package/README.md +6 -3
  2. package/dist/{chunk-JDVN2QYW.js → chunk-FVKQPBPP.js} +643 -546
  3. package/dist/{chunk-NASIL5FY.js → chunk-SQVHJR2Z.js} +0 -4
  4. package/dist/cli/index.js +126 -126
  5. package/dist/index.js +1 -1
  6. package/dist/vitepress/config/index.js +229 -208
  7. package/package.json +16 -3
  8. package/types/shims.d.ts +3 -4
  9. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +2 -1
  10. package/vitepress/components/CodeBlockFullscreen/index.ts +2 -1
  11. package/vitepress/components/Discussions/Discussions.vue +3 -2
  12. package/vitepress/components/EnWordList/EnWordList.vue +2 -1
  13. package/vitepress/components/Layout/AboutModal.vue +1 -1
  14. package/vitepress/components/Layout/AboutPanel.vue +8 -3
  15. package/vitepress/components/Layout/ContentCollapse.vue +2 -1
  16. package/vitepress/components/Layout/CustomSidebar.vue +7 -4
  17. package/vitepress/components/Layout/DocBeforeControls.vue +4 -4
  18. package/vitepress/components/Layout/DocFooter.vue +2 -1
  19. package/vitepress/components/Layout/ImagePreview.vue +2 -1
  20. package/vitepress/components/Layout/Layout.vue +38 -20
  21. package/vitepress/components/Layout/NoteStatus.vue +2 -1
  22. package/vitepress/components/Layout/SidebarItems.vue +7 -2
  23. package/vitepress/components/Layout/SidebarNavBefore.vue +22 -0
  24. package/vitepress/components/Layout/ToggleFullContent.vue +2 -1
  25. package/vitepress/components/Layout/ToggleSidebar.vue +2 -1
  26. package/vitepress/components/Layout/composables/useCollapseControl.ts +1 -1
  27. package/vitepress/components/Layout/composables/useNoteConfig.ts +2 -4
  28. package/vitepress/components/Layout/composables/useNoteSave.ts +23 -18
  29. package/vitepress/components/Layout/composables/useRedirect.ts +8 -5
  30. package/vitepress/components/Layout/composables/useRenameOverlay.ts +33 -0
  31. package/vitepress/components/Layout/composables/useRenameRedirect.ts +59 -0
  32. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +7 -4
  33. package/vitepress/components/Layout/homeReadme.data.ts +1 -1
  34. package/vitepress/components/LoadingPage/LoadingPage.vue +84 -192
  35. package/vitepress/components/MarkMap/MarkMap.vue +5 -4
  36. package/vitepress/components/Mermaid/Mermaid.vue +4 -2
  37. package/vitepress/components/NotesTable/NotesTable.vue +2 -1
  38. package/vitepress/components/Settings/Settings.vue +2 -1
  39. package/vitepress/components/Settings/SettingsDialog.vue +243 -0
  40. package/vitepress/components/Settings/composables/useSettingsDialog.ts +35 -0
  41. package/vitepress/components/SidebarCard/MindMapView.vue +10 -9
  42. package/vitepress/components/SidebarCard/SidebarCard.vue +12 -14
  43. package/vitepress/config/index.ts +11 -4
  44. package/vitepress/configs/head.config.ts +2 -1
  45. package/vitepress/configs/markdown-it.d.ts +6 -3
  46. package/vitepress/configs/markdown.config.ts +6 -5
  47. package/vitepress/configs/theme.config.ts +1 -1
  48. package/vitepress/plugins/buildProgressPlugin.ts +2 -38
  49. package/vitepress/plugins/fileWatcherBridgePlugin.ts +49 -0
  50. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +3 -2
  51. package/vitepress/plugins/renameNotePlugin.ts +9 -1
  52. package/vitepress/plugins/updateConfigPlugin.ts +2 -1
  53. package/vitepress/theme/index.ts +45 -3
@@ -1,41 +1,7 @@
1
1
  import {
2
2
  ConfigManager,
3
3
  getConfigManager
4
- } from "./chunk-NASIL5FY.js";
5
-
6
- // commands/models.ts
7
- var COMMAND_NAMES = {
8
- BUILD: "build",
9
- CREATE_NOTES: "create-notes",
10
- DEV: "dev",
11
- FIX_TIMESTAMPS: "fix-timestamps",
12
- HELP: "help",
13
- PREVIEW: "preview",
14
- PULL: "pull",
15
- PUSH: "push",
16
- RENAME_NOTE: "rename-note",
17
- UPDATE: "update",
18
- UPDATE_COMPLETED_COUNT: "update-completed-count",
19
- UPDATE_NOTE_CONFIG: "update-note-config"
20
- };
21
- var COMMAND_DESCRIPTIONS = {
22
- [COMMAND_NAMES.DEV]: "\u542F\u52A8\u77E5\u8BC6\u5E93\u5F00\u53D1\u670D\u52A1",
23
- [COMMAND_NAMES.BUILD]: "\u6784\u5EFA\u77E5\u8BC6\u5E93",
24
- [COMMAND_NAMES.PREVIEW]: "\u9884\u89C8\u6784\u5EFA\u540E\u7684\u77E5\u8BC6\u5E93",
25
- [COMMAND_NAMES.UPDATE]: "\u6839\u636E\u7B14\u8BB0\u5185\u5BB9\u66F4\u65B0\u77E5\u8BC6\u5E93",
26
- [COMMAND_NAMES.UPDATE_COMPLETED_COUNT]: "\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55\uFF08\u8FD1 1 \u5E74\uFF0C\u6700\u8FD1 12 \u4E2A\u6708\uFF09",
27
- [COMMAND_NAMES.CREATE_NOTES]: "\u65B0\u5EFA\u7B14\u8BB0\uFF08\u652F\u6301\u6279\u91CF\u521B\u5EFA\uFF09",
28
- [COMMAND_NAMES.PUSH]: "\u5C06\u77E5\u8BC6\u5E93\u63A8\u9001\u5230 GitHub",
29
- [COMMAND_NAMES.PULL]: "\u5C06 GitHub \u7684\u77E5\u8BC6\u5E93\u62C9\u4E0B\u6765",
30
- [COMMAND_NAMES.FIX_TIMESTAMPS]: "\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233\uFF08\u57FA\u4E8E git \u5386\u53F2\uFF09",
31
- [COMMAND_NAMES.UPDATE_NOTE_CONFIG]: "\u66F4\u65B0\u7B14\u8BB0\u914D\u7F6E\u6587\u4EF6",
32
- [COMMAND_NAMES.RENAME_NOTE]: "\u91CD\u547D\u540D\u7B14\u8BB0",
33
- [COMMAND_NAMES.HELP]: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F"
34
- };
35
- var COMMAND_OPTIONS = {
36
- QUIET: "quiet",
37
- FORCE: "force"
38
- };
4
+ } from "./chunk-SQVHJR2Z.js";
39
5
 
40
6
  // utils/errorHandler.ts
41
7
  var TNotesError = class _TNotesError extends Error {
@@ -399,8 +365,8 @@ var genHierarchicalSidebar = (itemList, titles, titlesNotesCount, sidebarIsColla
399
365
  };
400
366
 
401
367
  // utils/getChangedIds.ts
402
- import path from "path";
403
368
  import { execSync } from "child_process";
369
+ import path from "path";
404
370
  function getChangedIds() {
405
371
  const changedFiles = execSync(
406
372
  `git diff --name-only HEAD -- "notes/[0-9][0-9][0-9][0-9]*/README.md"`
@@ -419,7 +385,7 @@ function createAddNumberToTitle() {
419
385
  const titleNumbers = Array(7).fill(0);
420
386
  return function addNumberToTitle(title) {
421
387
  const match = title.match(
422
- /^(\#+)\s*((\d+(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?)\.\s*)?(.*)/
388
+ /^(#+)\s*((\d+(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?)\.\s*)?(.*)/
423
389
  );
424
390
  const plainTitle = match ? match[9].trim() : title.trim();
425
391
  const level = title.indexOf(" ");
@@ -1006,7 +972,7 @@ function buildNoteLink(note, repoOwner, repoName2) {
1006
972
  }
1007
973
  function updateNoteStatus(note) {
1008
974
  let status = " ";
1009
- let deprecatedMark = "";
975
+ const deprecatedMark = "";
1010
976
  if (note.config) {
1011
977
  if (note.config.done) {
1012
978
  status = "x";
@@ -1140,6 +1106,40 @@ function validateNoteTitle(title) {
1140
1106
  return { valid: true };
1141
1107
  }
1142
1108
 
1109
+ // commands/models.ts
1110
+ var COMMAND_NAMES = {
1111
+ BUILD: "build",
1112
+ CREATE_NOTES: "create-notes",
1113
+ DEV: "dev",
1114
+ FIX_TIMESTAMPS: "fix-timestamps",
1115
+ HELP: "help",
1116
+ PREVIEW: "preview",
1117
+ PULL: "pull",
1118
+ PUSH: "push",
1119
+ RENAME_NOTE: "rename-note",
1120
+ UPDATE: "update",
1121
+ UPDATE_COMPLETED_COUNT: "update-completed-count",
1122
+ UPDATE_NOTE_CONFIG: "update-note-config"
1123
+ };
1124
+ var COMMAND_DESCRIPTIONS = {
1125
+ [COMMAND_NAMES.DEV]: "\u542F\u52A8\u77E5\u8BC6\u5E93\u5F00\u53D1\u670D\u52A1",
1126
+ [COMMAND_NAMES.BUILD]: "\u6784\u5EFA\u77E5\u8BC6\u5E93",
1127
+ [COMMAND_NAMES.PREVIEW]: "\u9884\u89C8\u6784\u5EFA\u540E\u7684\u77E5\u8BC6\u5E93",
1128
+ [COMMAND_NAMES.UPDATE]: "\u6839\u636E\u7B14\u8BB0\u5185\u5BB9\u66F4\u65B0\u77E5\u8BC6\u5E93",
1129
+ [COMMAND_NAMES.UPDATE_COMPLETED_COUNT]: "\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55\uFF08\u8FD1 1 \u5E74\uFF0C\u6700\u8FD1 12 \u4E2A\u6708\uFF09",
1130
+ [COMMAND_NAMES.CREATE_NOTES]: "\u65B0\u5EFA\u7B14\u8BB0\uFF08\u652F\u6301\u6279\u91CF\u521B\u5EFA\uFF09",
1131
+ [COMMAND_NAMES.PUSH]: "\u5C06\u77E5\u8BC6\u5E93\u63A8\u9001\u5230 GitHub",
1132
+ [COMMAND_NAMES.PULL]: "\u5C06 GitHub \u7684\u77E5\u8BC6\u5E93\u62C9\u4E0B\u6765",
1133
+ [COMMAND_NAMES.FIX_TIMESTAMPS]: "\u4FEE\u590D\u6240\u6709\u7B14\u8BB0\u7684\u65F6\u95F4\u6233\uFF08\u57FA\u4E8E git \u5386\u53F2\uFF09",
1134
+ [COMMAND_NAMES.UPDATE_NOTE_CONFIG]: "\u66F4\u65B0\u7B14\u8BB0\u914D\u7F6E\u6587\u4EF6",
1135
+ [COMMAND_NAMES.RENAME_NOTE]: "\u91CD\u547D\u540D\u7B14\u8BB0",
1136
+ [COMMAND_NAMES.HELP]: "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F"
1137
+ };
1138
+ var COMMAND_OPTIONS = {
1139
+ QUIET: "quiet",
1140
+ FORCE: "force"
1141
+ };
1142
+
1143
1143
  // core/NoteIndexCache.ts
1144
1144
  import { join as join2 } from "path";
1145
1145
  var NoteIndexCache = class _NoteIndexCache {
@@ -1338,7 +1338,7 @@ var TocGenerator = class {
1338
1338
  titles.push(numberedTitle);
1339
1339
  lines[i] = numberedTitle;
1340
1340
  } else if (isUnnumberedHeader) {
1341
- const match = line.match(/^(\#+)\s*(\d+(\.\d+)*\.\s*)?(.*)/);
1341
+ const match = line.match(/^(#+)\s*(\d+(\.\d+)*\.\s*)?(.*)/);
1342
1342
  if (match) {
1343
1343
  const headerSymbol = match[1];
1344
1344
  const plainTitle = match[4];
@@ -1373,7 +1373,7 @@ var TocGenerator = class {
1373
1373
  });
1374
1374
  }
1375
1375
  if (noteConfig.yuque.length > 0) {
1376
- noteConfig.yuque.forEach((slug, i) => {
1376
+ noteConfig.yuque.forEach((slug) => {
1377
1377
  yuqueTOCItems.push(
1378
1378
  ` - [TNotes.yuque.${repoName2.replace(
1379
1379
  "TNotes.",
@@ -1416,9 +1416,9 @@ var TocGenerator = class {
1416
1416
  * 更新首页目录
1417
1417
  * @param lines - 首页内容行数组
1418
1418
  * @param titles - 标题数组
1419
- * @param titlesNotesCount - 每个标题下的笔记数量
1419
+ * @param _titlesNotesCount - 每个标题下的笔记数量
1420
1420
  */
1421
- updateHomeToc(lines, titles, titlesNotesCount) {
1421
+ updateHomeToc(lines, titles, _titlesNotesCount) {
1422
1422
  let startLineIdx = -1, endLineIdx = -1;
1423
1423
  lines.forEach((line, idx) => {
1424
1424
  if (line.startsWith(NOTES_TOC_START_TAG)) startLineIdx = idx;
@@ -1740,7 +1740,6 @@ var GitManager = class {
1740
1740
  didStash = await this.stash("Auto-stash before pull");
1741
1741
  }
1742
1742
  try {
1743
- const status = await this.getStatus();
1744
1743
  const beforeCommit = await runCommand("git rev-parse HEAD", this.dir);
1745
1744
  this.logger.info("\u6B63\u5728\u62C9\u53D6\u8FDC\u7A0B\u66F4\u65B0...");
1746
1745
  const cmd = `git pull ${rebase ? "--rebase" : ""} ${autostash ? "--autostash" : ""}`.trim();
@@ -2054,292 +2053,116 @@ var ProcessManager = class {
2054
2053
  }
2055
2054
  };
2056
2055
 
2057
- // commands/BaseCommand.ts
2058
- var BaseCommand = class {
2059
- constructor(name) {
2060
- this.name = name;
2061
- this.logger = logger.child(name);
2062
- }
2063
- logger;
2064
- options = {};
2065
- /** 命令描述(从静态配置读取) */
2066
- get description() {
2067
- return COMMAND_DESCRIPTIONS[this.name];
2068
- }
2069
- /**
2070
- * 设置命令选项
2071
- */
2072
- setOptions(options) {
2073
- this.options = { ...this.options, ...options };
2056
+ // services/file-watcher/service.ts
2057
+ import { request as httpRequest } from "http";
2058
+
2059
+ // services/file-watcher/configChangeHandler.ts
2060
+ var ConfigChangeHandler = class {
2061
+ constructor(config2) {
2062
+ this.config = config2;
2074
2063
  }
2075
- /**
2076
- * 执行命令(带错误处理)
2077
- */
2078
- async execute() {
2079
- const startTime = Date.now();
2080
- try {
2081
- this.logger.start(this.description);
2082
- await this.run();
2083
- const duration = Date.now() - startTime;
2084
- this.logger.done(`\u547D\u4EE4\u6267\u884C\u8017\u65F6\uFF1A${duration} ms`);
2085
- } catch (error) {
2086
- handleError(error);
2087
- throw error;
2064
+ async handle(events) {
2065
+ if (events.length === 0) return [];
2066
+ const changedIndexes = [];
2067
+ const { state, noteService, noteIndexCache, logger: logger2 } = this.config;
2068
+ for (const change of events) {
2069
+ if (noteService.shouldIgnoreConfigChange(change.path)) {
2070
+ logger2.debug(`\u5FFD\u7565 API \u5199\u5165\u7684\u914D\u7F6E\u6587\u4EF6: ${change.path}`);
2071
+ continue;
2072
+ }
2073
+ const snapshot = state.readConfigSnapshot(change.path);
2074
+ if (!snapshot) continue;
2075
+ const cached = state.getConfigSnapshot(change.path);
2076
+ state.setConfigSnapshot(change.path, snapshot);
2077
+ noteIndexCache.updateConfig(change.noteIndex, snapshot);
2078
+ if (!cached) continue;
2079
+ const statusChanged = cached.done !== snapshot.done;
2080
+ const otherChanged = cached.enableDiscussions !== snapshot.enableDiscussions || cached.description !== snapshot.description;
2081
+ if (statusChanged) {
2082
+ changedIndexes.push(change.noteIndex);
2083
+ logger2.info(`\u68C0\u6D4B\u5230\u914D\u7F6E\u72B6\u6001\u53D8\u5316: done(${cached.done}\u2192${snapshot.done})`);
2084
+ } else if (otherChanged) {
2085
+ logger2.info("\u68C0\u6D4B\u5230\u914D\u7F6E\u975E\u72B6\u6001\u5B57\u6BB5\u53D8\u5316\uFF0C\u5DF2\u5237\u65B0\u7F13\u5B58\uFF08\u65E0\u9700\u5168\u5C40\u66F4\u65B0\uFF09");
2086
+ }
2088
2087
  }
2088
+ return changedIndexes;
2089
2089
  }
2090
2090
  };
2091
2091
 
2092
- // services/file-watcher/internal.ts
2093
- var WATCH_EVENT_TYPES = {
2094
- README: "readme",
2095
- CONFIG: "config"
2096
- };
2097
- async function safeExecute(label, fn, logger2) {
2098
- try {
2099
- await fn();
2100
- return true;
2101
- } catch (error) {
2102
- logger2.error(`[${label}] ${error}`);
2103
- return false;
2104
- }
2105
- }
2106
-
2107
- // services/file-watcher/watchState.ts
2108
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
2109
- import { createHash } from "crypto";
2110
- import { join as join3 } from "path";
2111
- var WatchState = class {
2092
+ // services/file-watcher/eventScheduler.ts
2093
+ var DEFAULT_DEBOUNCE_MS = 1e3;
2094
+ var DEFAULT_BATCH_WINDOW_MS = 1e3;
2095
+ var DEFAULT_BATCH_THRESHOLD = 3;
2096
+ var DEFAULT_BATCH_BUFFER_MS = 2e3;
2097
+ var EventScheduler = class {
2112
2098
  constructor(config2) {
2113
2099
  this.config = config2;
2100
+ this.debounceMs = config2.debounceMs ?? DEFAULT_DEBOUNCE_MS;
2101
+ this.batchWindowMs = config2.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS;
2102
+ this.batchThreshold = config2.batchThreshold ?? DEFAULT_BATCH_THRESHOLD;
2103
+ this.batchBufferMs = config2.batchBufferMs ?? DEFAULT_BATCH_BUFFER_MS;
2114
2104
  }
2115
- /** 文件哈希缓存 */
2116
- fileHashes = /* @__PURE__ */ new Map();
2117
- /** 笔记目录缓存 */
2118
- noteDirCache = /* @__PURE__ */ new Set();
2119
- /** 笔记配置缓存 */
2120
- configCache = /* @__PURE__ */ new Map();
2105
+ /** 待处理的文件变更事件队列 */
2106
+ pendingEvents = /* @__PURE__ */ new Map();
2107
+ /** 防抖定时器 */
2108
+ updateTimer = null;
2109
+ /** 批量更新恢复定时器 */
2110
+ batchResumeTimer = null;
2111
+ /** 记录最近的变更时间戳 */
2112
+ recentChanges = [];
2113
+ /** 标记是否正在更新,避免循环触发 - 类似一把更新行为锁 */
2114
+ isUpdating = false;
2115
+ debounceMs;
2116
+ batchWindowMs;
2117
+ batchThreshold;
2118
+ batchBufferMs;
2121
2119
  /**
2122
- * 获取指定文件的 MD5 哈希值,若文件不存在或读取失败返回 null
2120
+ * 设置更新状态锁,用于防止在执行耗时更新操作时被新的文件变更事件打断
2123
2121
  *
2124
- * @param filePath 文件路径
2125
- * @returns 文件哈希
2122
+ * @param flag - true 表示正在更新(锁定),false 表示更新完成(解锁)
2126
2123
  */
2127
- getFileHash(filePath) {
2128
- try {
2129
- if (!existsSync3(filePath)) return null;
2130
- const content = readFileSync3(filePath, "utf-8");
2131
- if (content.length === 0) return null;
2132
- return createHash("md5").update(content).digest("hex");
2133
- } catch {
2134
- return null;
2135
- }
2124
+ setUpdating(flag) {
2125
+ this.isUpdating = flag;
2136
2126
  }
2137
2127
  /**
2138
- * 更新文件哈希缓存,只有当文件内容发生变化时才更新并返回 true
2128
+ * 获取当前是否处于更新锁定状态
2139
2129
  *
2140
- * @param filePath 文件路径
2141
- * @returns 是否发生变化
2130
+ * @returns true 表示正在执行更新操作(事件处理被暂停),false 表示空闲可处理新事件
2142
2131
  */
2143
- updateFileHash(filePath) {
2144
- const current = this.getFileHash(filePath);
2145
- if (!current) return false;
2146
- const prev = this.fileHashes.get(filePath);
2147
- if (prev === current) return false;
2148
- this.fileHashes.set(filePath, current);
2149
- return true;
2132
+ getUpdating() {
2133
+ return this.isUpdating;
2150
2134
  }
2151
2135
  /**
2152
- * 检查指定名称的笔记目录是否已存在于缓存中
2136
+ * 将文件变更事件加入待处理队列,并启动防抖定时器
2153
2137
  *
2154
- * @param name 笔记目录名称
2155
- * @returns 若存在则返回 true,否则返回 false
2156
- */
2157
- hasNoteDir(name) {
2158
- return this.noteDirCache.has(name);
2159
- }
2160
- /**
2161
- * 将指定名称的笔记目录添加到缓存中
2138
+ * - 若同一文件路径的事件已存在,则忽略重复事件(去重)
2139
+ * - 每次新事件都会重置防抖计时器,确保在变更停止后才触发处理
2162
2140
  *
2163
- * @param name 笔记目录名称
2141
+ * @param event 文件变更事件
2164
2142
  */
2165
- addNoteDir(name) {
2166
- this.noteDirCache.add(name);
2143
+ enqueue(event) {
2144
+ if (this.pendingEvents.has(event.path)) return;
2145
+ this.pendingEvents.set(event.path, event);
2146
+ if (this.updateTimer) clearTimeout(this.updateTimer);
2147
+ this.updateTimer = setTimeout(() => this.flush(), this.debounceMs);
2167
2148
  }
2168
2149
  /**
2169
- * 从缓存中移除指定名称的笔记目录
2150
+ * 立即触发事件队列的处理(防抖到期或手动调用)
2170
2151
  *
2171
- * @param name 笔记目录名称
2172
- */
2173
- deleteNoteDir(name) {
2174
- this.noteDirCache.delete(name);
2175
- }
2176
- /**
2177
- * 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
2152
+ * - 若当前正在更新(isUpdating 为 true),则跳过以避免重复处理
2153
+ * - 清空待处理事件队列,并通过 onFlush 回调交由上层服务处理
2154
+ * - 处理开始后会锁定更新状态,防止处理过程中被新事件打断
2178
2155
  */
2179
- clearAll() {
2180
- this.fileHashes.clear();
2181
- this.noteDirCache.clear();
2182
- this.configCache.clear();
2156
+ flush() {
2157
+ if (this.isUpdating) return;
2158
+ if (this.pendingEvents.size === 0) return;
2159
+ const events = Array.from(this.pendingEvents.values());
2160
+ this.pendingEvents.clear();
2161
+ this.isUpdating = true;
2162
+ this.config.onFlush(events);
2183
2163
  }
2184
2164
  /**
2185
- * 清除指定笔记目录相关的缓存数据,包括 README.md 和 .tnotes.json 的文件哈希及配置快照
2186
- *
2187
- * @param noteDirName 笔记目录名称
2188
- */
2189
- clearNoteCaches(noteDirName) {
2190
- const readmePath = join3(this.config.notesDir, noteDirName, "README.md");
2191
- const configPath = join3(this.config.notesDir, noteDirName, ".tnotes.json");
2192
- this.fileHashes.delete(readmePath);
2193
- this.fileHashes.delete(configPath);
2194
- this.configCache.delete(configPath);
2195
- }
2196
- /**
2197
- * 获取指定配置文件路径对应的配置快照
2198
- *
2199
- * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2200
- * @returns 配置快照,若不存在则返回 undefined
2201
- */
2202
- getConfigSnapshot(configPath) {
2203
- return this.configCache.get(configPath);
2204
- }
2205
- /**
2206
- * 设置指定配置文件路径的配置快照到缓存中
2207
- *
2208
- * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2209
- * @param snapshot 配置快照对象
2210
- */
2211
- setConfigSnapshot(configPath, snapshot) {
2212
- this.configCache.set(configPath, snapshot);
2213
- }
2214
- /**
2215
- * 读取指定配置文件的快照
2216
- *
2217
- * 解析 .tnotes.json 配置文件,提取 done、enableDiscussions、description 字段。
2218
- *
2219
- * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2220
- * @returns 配置快照,若文件不存在或解析失败则返回 null
2221
- */
2222
- readConfigSnapshot(configPath) {
2223
- try {
2224
- if (!existsSync3(configPath)) return null;
2225
- const content = readFileSync3(configPath, "utf-8");
2226
- const config2 = JSON.parse(content);
2227
- return {
2228
- done: Boolean(config2.done),
2229
- enableDiscussions: Boolean(config2.enableDiscussions),
2230
- description: config2.description || ""
2231
- };
2232
- } catch (error) {
2233
- this.config.logger.error(`[\u8BFB\u53D6\u914D\u7F6E\u5FEB\u7167] ${error}`);
2234
- return null;
2235
- }
2236
- }
2237
- /**
2238
- * 从磁盘初始化监听状态缓存:
2239
- * 遍历笔记根目录下的所有子目录,将每个笔记目录的 README.md 和 .tnotes.json
2240
- * 的哈希值及配置快照加载到缓存中。
2241
- */
2242
- initializeFromDisk() {
2243
- try {
2244
- const noteDirs = readdirSync2(this.config.notesDir);
2245
- this.clearAll();
2246
- for (const noteDir of noteDirs) {
2247
- const noteDirPath = join3(this.config.notesDir, noteDir);
2248
- if (!statSync(noteDirPath).isDirectory()) continue;
2249
- this.noteDirCache.add(noteDir);
2250
- const readmePath = join3(noteDirPath, "README.md");
2251
- const readmeHash = this.getFileHash(readmePath);
2252
- if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
2253
- const configPath = join3(noteDirPath, ".tnotes.json");
2254
- const configHash = this.getFileHash(configPath);
2255
- if (configHash) {
2256
- this.fileHashes.set(configPath, configHash);
2257
- const snapshot = this.readConfigSnapshot(configPath);
2258
- if (snapshot) this.configCache.set(configPath, snapshot);
2259
- }
2260
- }
2261
- } catch (error) {
2262
- this.config.logger.warn(
2263
- `[initializeFromDisk] \u521D\u59CB\u5316\u76D1\u542C\u72B6\u6001\u5931\u8D25: ${error}`
2264
- );
2265
- }
2266
- }
2267
- };
2268
-
2269
- // services/file-watcher/eventScheduler.ts
2270
- var DEFAULT_DEBOUNCE_MS = 1e3;
2271
- var DEFAULT_BATCH_WINDOW_MS = 1e3;
2272
- var DEFAULT_BATCH_THRESHOLD = 3;
2273
- var DEFAULT_BATCH_BUFFER_MS = 2e3;
2274
- var EventScheduler = class {
2275
- constructor(config2) {
2276
- this.config = config2;
2277
- this.debounceMs = config2.debounceMs ?? DEFAULT_DEBOUNCE_MS;
2278
- this.batchWindowMs = config2.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS;
2279
- this.batchThreshold = config2.batchThreshold ?? DEFAULT_BATCH_THRESHOLD;
2280
- this.batchBufferMs = config2.batchBufferMs ?? DEFAULT_BATCH_BUFFER_MS;
2281
- }
2282
- /** 待处理的文件变更事件队列 */
2283
- pendingEvents = /* @__PURE__ */ new Map();
2284
- /** 防抖定时器 */
2285
- updateTimer = null;
2286
- /** 批量更新恢复定时器 */
2287
- batchResumeTimer = null;
2288
- /** 记录最近的变更时间戳 */
2289
- recentChanges = [];
2290
- /** 标记是否正在更新,避免循环触发 - 类似一把更新行为锁 */
2291
- isUpdating = false;
2292
- debounceMs;
2293
- batchWindowMs;
2294
- batchThreshold;
2295
- batchBufferMs;
2296
- /**
2297
- * 设置更新状态锁,用于防止在执行耗时更新操作时被新的文件变更事件打断
2298
- *
2299
- * @param flag - true 表示正在更新(锁定),false 表示更新完成(解锁)
2300
- */
2301
- setUpdating(flag) {
2302
- this.isUpdating = flag;
2303
- }
2304
- /**
2305
- * 获取当前是否处于更新锁定状态
2306
- *
2307
- * @returns true 表示正在执行更新操作(事件处理被暂停),false 表示空闲可处理新事件
2308
- */
2309
- getUpdating() {
2310
- return this.isUpdating;
2311
- }
2312
- /**
2313
- * 将文件变更事件加入待处理队列,并启动防抖定时器
2314
- *
2315
- * - 若同一文件路径的事件已存在,则忽略重复事件(去重)
2316
- * - 每次新事件都会重置防抖计时器,确保在变更停止后才触发处理
2317
- *
2318
- * @param event 文件变更事件
2319
- */
2320
- enqueue(event) {
2321
- if (this.pendingEvents.has(event.path)) return;
2322
- this.pendingEvents.set(event.path, event);
2323
- if (this.updateTimer) clearTimeout(this.updateTimer);
2324
- this.updateTimer = setTimeout(() => this.flush(), this.debounceMs);
2325
- }
2326
- /**
2327
- * 立即触发事件队列的处理(防抖到期或手动调用)
2328
- *
2329
- * - 若当前正在更新(isUpdating 为 true),则跳过以避免重复处理
2330
- * - 清空待处理事件队列,并通过 onFlush 回调交由上层服务处理
2331
- * - 处理开始后会锁定更新状态,防止处理过程中被新事件打断
2332
- */
2333
- flush() {
2334
- if (this.isUpdating) return;
2335
- if (this.pendingEvents.size === 0) return;
2336
- const events = Array.from(this.pendingEvents.values());
2337
- this.pendingEvents.clear();
2338
- this.isUpdating = true;
2339
- this.config.onFlush(events);
2340
- }
2341
- /**
2342
- * 记录当前变更时间并检测是否触发批量更新模式
2165
+ * 记录当前变更时间并检测是否触发批量更新模式
2343
2166
  *
2344
2167
  * - 维护一个滑动时间窗口(BATCH_UPDATE_WINDOW_MS)内的变更记录
2345
2168
  * - 若短时间内(1秒内)变更次数达到阈值(BATCH_UPDATE_THRESHOLD = 3),则判定为批量操作
@@ -2388,160 +2211,33 @@ var EventScheduler = class {
2388
2211
  }
2389
2212
  };
2390
2213
 
2391
- // services/file-watcher/renameDetector.ts
2392
- import { existsSync as existsSync4 } from "fs";
2393
- import { join as join4 } from "path";
2394
- var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
2395
- var RenameDetector = class {
2396
- constructor(config2) {
2397
- this.config = config2;
2398
- }
2399
- /** 待处理的文件夹重命名 */
2400
- pendingFolderRename = null;
2401
- /** 文件夹重命名检测定时器 */
2402
- folderRenameTimer = null;
2403
- handleFsRename(folderName) {
2404
- const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
2405
- const folderPath = join4(notesDir, folderName);
2406
- const folderExists = existsSync4(folderPath);
2407
- const noteIndex = NoteManager.extractNoteIndex(folderName);
2408
- if (!noteIndex) {
2409
- logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
2410
- return;
2411
- }
2412
- if (!folderExists) {
2413
- if (dirCache.has(folderName)) {
2414
- logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u5220\u9664/\u91CD\u547D\u540D: ${folderName}`);
2415
- this.pendingFolderRename = { oldName: folderName, time: Date.now() };
2416
- if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer);
2417
- this.folderRenameTimer = setTimeout(() => {
2418
- if (this.pendingFolderRename) {
2419
- logger2.warn(`\u68C0\u6D4B\u5230\u7B14\u8BB0\u5220\u9664: ${this.pendingFolderRename.oldName}`);
2420
- onDelete(this.pendingFolderRename.oldName);
2421
- }
2422
- this.pendingFolderRename = null;
2423
- this.folderRenameTimer = null;
2424
- }, FOLDER_RENAME_DETECT_WINDOW_MS);
2425
- }
2426
- return;
2427
- }
2428
- if (!dirCache.has(folderName)) {
2429
- logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u521B\u5EFA/\u91CD\u547D\u540D: ${folderName}`);
2430
- if (this.pendingFolderRename && Date.now() - this.pendingFolderRename.time < FOLDER_RENAME_DETECT_WINDOW_MS) {
2431
- const oldName = this.pendingFolderRename.oldName;
2432
- const oldNoteIndex = NoteManager.extractNoteIndex(oldName);
2433
- if (oldNoteIndex && oldNoteIndex === noteIndex) {
2434
- logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u91CD\u547D\u540D: ${oldName} \u2192 ${folderName}`);
2435
- if (this.folderRenameTimer) {
2436
- clearTimeout(this.folderRenameTimer);
2437
- this.folderRenameTimer = null;
2438
- }
2439
- onRename(oldName, folderName);
2440
- this.pendingFolderRename = null;
2441
- } else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
2442
- logger2.warn(`\u7D22\u5F15\u51B2\u7A81\uFF0C\u56DE\u9000: ${oldName} -> ${folderName}`);
2443
- }
2444
- }
2445
- dirCache.add(folderName);
2446
- if (this.pendingFolderRename) {
2447
- dirCache.delete(this.pendingFolderRename.oldName);
2448
- }
2449
- }
2450
- }
2451
- clearTimers() {
2452
- if (this.folderRenameTimer) {
2453
- clearTimeout(this.folderRenameTimer);
2454
- this.folderRenameTimer = null;
2455
- }
2456
- this.pendingFolderRename = null;
2457
- }
2458
- };
2459
-
2460
- // services/file-watcher/configChangeHandler.ts
2461
- var ConfigChangeHandler = class {
2462
- constructor(config2) {
2463
- this.config = config2;
2464
- }
2465
- async handle(events) {
2466
- if (events.length === 0) return [];
2467
- const changedIndexes = [];
2468
- const { state, noteService, noteIndexCache, logger: logger2 } = this.config;
2469
- for (const change of events) {
2470
- if (noteService.shouldIgnoreConfigChange(change.path)) {
2471
- logger2.debug(`\u5FFD\u7565 API \u5199\u5165\u7684\u914D\u7F6E\u6587\u4EF6: ${change.path}`);
2472
- continue;
2473
- }
2474
- const snapshot = state.readConfigSnapshot(change.path);
2475
- if (!snapshot) continue;
2476
- const cached = state.getConfigSnapshot(change.path);
2477
- state.setConfigSnapshot(change.path, snapshot);
2478
- noteIndexCache.updateConfig(change.noteIndex, snapshot);
2479
- if (!cached) continue;
2480
- const statusChanged = cached.done !== snapshot.done;
2481
- const otherChanged = cached.enableDiscussions !== snapshot.enableDiscussions || cached.description !== snapshot.description;
2482
- if (statusChanged) {
2483
- changedIndexes.push(change.noteIndex);
2484
- logger2.info(`\u68C0\u6D4B\u5230\u914D\u7F6E\u72B6\u6001\u53D8\u5316: done(${cached.done}\u2192${snapshot.done})`);
2485
- } else if (otherChanged) {
2486
- logger2.info("\u68C0\u6D4B\u5230\u914D\u7F6E\u975E\u72B6\u6001\u5B57\u6BB5\u53D8\u5316\uFF0C\u5DF2\u5237\u65B0\u7F13\u5B58\uFF08\u65E0\u9700\u5168\u5C40\u66F4\u65B0\uFF09");
2487
- }
2488
- }
2489
- return changedIndexes;
2490
- }
2491
- };
2214
+ // services/file-watcher/folderChangeHandler.ts
2215
+ import { existsSync as existsSync3, promises as fsPromises } from "fs";
2216
+ import { join as join3 } from "path";
2492
2217
 
2493
- // services/file-watcher/readmeChangeHandler.ts
2494
- var ReadmeChangeHandler = class {
2495
- constructor(config2) {
2496
- this.config = config2;
2497
- }
2498
- async handle(events) {
2499
- if (events.length === 0) return;
2500
- const indexes = [...new Set(events.map((c) => c.noteIndex))];
2501
- for (const noteIndex of indexes) {
2502
- const noteInfo = this.config.noteService.getNoteByIndex(noteIndex);
2503
- if (noteInfo) {
2504
- await this.config.noteService.fixNoteTitle(noteInfo);
2505
- }
2506
- }
2507
- }
2218
+ // services/file-watcher/internal.ts
2219
+ var WATCH_EVENT_TYPES = {
2220
+ README: "readme",
2221
+ CONFIG: "config"
2508
2222
  };
2509
-
2510
- // services/file-watcher/globalUpdateCoordinator.ts
2511
- var GlobalUpdateCoordinator = class {
2512
- constructor(config2) {
2513
- this.config = config2;
2514
- }
2515
- async applyConfigUpdates(changedNoteIndexes) {
2516
- if (changedNoteIndexes.length === 0) return;
2517
- const { readmeService, noteIndexCache, logger: logger2 } = this.config;
2518
- logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...");
2519
- for (const noteIndex of changedNoteIndexes) {
2520
- await safeExecute(
2521
- `\u589E\u91CF\u66F4\u65B0 ${noteIndex}`,
2522
- async () => {
2523
- const item = noteIndexCache.getByNoteIndex(noteIndex);
2524
- await readmeService.updateNoteInReadme(
2525
- noteIndex,
2526
- item?.noteConfig || {}
2527
- );
2528
- logger2.info(`\u589E\u91CF\u66F4\u65B0 README \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
2529
- },
2530
- logger2
2531
- );
2532
- }
2533
- await readmeService.regenerateSidebar();
2534
- }
2535
- async updateNoteReadmesOnly(events) {
2536
- const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))];
2537
- if (noteIndexesToUpdate.length === 0) return;
2538
- await this.config.readmeService.updateNoteReadmesOnly(noteIndexesToUpdate);
2223
+ async function safeExecute(label, fn, logger2) {
2224
+ try {
2225
+ await fn();
2226
+ return true;
2227
+ } catch (error) {
2228
+ logger2.error(`[${label}] ${error}`);
2229
+ return false;
2539
2230
  }
2540
- };
2231
+ }
2232
+
2233
+ // config/templates.ts
2234
+ function generateNoteTitle(noteIndex, title, repoUrl) {
2235
+ const dirName = `${noteIndex}. ${title}`;
2236
+ const encodedDirName = encodeURIComponent(dirName);
2237
+ return `# [${dirName}](${repoUrl}/${encodedDirName})`;
2238
+ }
2541
2239
 
2542
2240
  // services/file-watcher/folderChangeHandler.ts
2543
- import { existsSync as existsSync5, promises as fsPromises } from "fs";
2544
- import { join as join5 } from "path";
2545
2241
  var RENAME_REVERT_DELAY_MS = 2e3;
2546
2242
  var DELETE_REINIT_DELAY_MS = 1e3;
2547
2243
  var UPDATE_UNLOCK_DELAY_MS = 500;
@@ -2619,6 +2315,11 @@ var FolderChangeHandler = class {
2619
2315
  logger2
2620
2316
  );
2621
2317
  }
2318
+ this.config.onRenameSuccess?.({
2319
+ oldFolder: oldName,
2320
+ newFolder: newName,
2321
+ noteIndex: newNoteIndex
2322
+ });
2622
2323
  } finally {
2623
2324
  this.scheduleTimer(() => {
2624
2325
  scheduler.setUpdating(false);
@@ -2647,9 +2348,14 @@ var FolderChangeHandler = class {
2647
2348
  return { oldNoteIndex, newNoteIndex };
2648
2349
  }
2649
2350
  async handleTitleOnlyRename(noteIndex, newName) {
2650
- const { noteIndexCache, readmeService, logger: logger2 } = this.config;
2351
+ const { noteIndexCache, readmeService, logger: logger2, notesDir } = this.config;
2651
2352
  logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u672A\u53D8 (${noteIndex})\uFF0C\u53EA\u66F4\u65B0\u6807\u9898`);
2652
2353
  noteIndexCache.updateFolderName(noteIndex, newName);
2354
+ await safeExecute(
2355
+ `\u66F4\u65B0 README \u4E00\u7EA7\u6807\u9898 ${noteIndex}`,
2356
+ () => this.rewriteNoteReadmeH1(notesDir, newName, noteIndex),
2357
+ logger2
2358
+ );
2653
2359
  const item = noteIndexCache.getByNoteIndex(noteIndex);
2654
2360
  if (item) {
2655
2361
  await readmeService.updateNoteInReadme(noteIndex, item.noteConfig);
@@ -2657,6 +2363,37 @@ var FolderChangeHandler = class {
2657
2363
  await readmeService.regenerateSidebar();
2658
2364
  logger2.success(`\u6807\u9898\u66F4\u65B0\u5B8C\u6210`);
2659
2365
  }
2366
+ /**
2367
+ * 重写指定笔记目录下 README.md 的一级标题。
2368
+ *
2369
+ * 输入的 `folderName` 形如 `0003. 评论功能的技术实现`,
2370
+ * 据此推导出新标题文本(去掉 `${noteIndex}. ` 前缀)。
2371
+ */
2372
+ async rewriteNoteReadmeH1(notesDir, folderName, noteIndex) {
2373
+ const readmePath = join3(notesDir, folderName, "README.md");
2374
+ if (!existsSync3(readmePath)) return;
2375
+ const newTitle = folderName.replace(/^\d{4}\.\s*/, "").trim();
2376
+ if (!newTitle) return;
2377
+ const content = await fsPromises.readFile(readmePath, "utf-8");
2378
+ const lines = content.split("\n");
2379
+ let h1Index = -1;
2380
+ for (let i = 0; i < lines.length; i++) {
2381
+ if (lines[i].trim().startsWith("# ")) {
2382
+ h1Index = i;
2383
+ break;
2384
+ }
2385
+ }
2386
+ if (h1Index === -1) {
2387
+ this.config.logger.warn(
2388
+ `\u672A\u5728 ${readmePath} \u627E\u5230\u4E00\u7EA7\u6807\u9898\uFF0C\u8DF3\u8FC7 README H1 \u66F4\u65B0`
2389
+ );
2390
+ return;
2391
+ }
2392
+ const newH1 = generateNoteTitle(noteIndex, newTitle, REPO_NOTES_URL);
2393
+ if (lines[h1Index] === newH1) return;
2394
+ lines[h1Index] = newH1;
2395
+ await fsPromises.writeFile(readmePath, lines.join("\n"), "utf-8");
2396
+ }
2660
2397
  async handleIndexChangedRename(oldNoteIndex, newNoteIndex) {
2661
2398
  const { noteService, noteIndexCache, readmeService, logger: logger2 } = this.config;
2662
2399
  logger2.info(`\u7B14\u8BB0\u7D22\u5F15\u53D8\u66F4: ${oldNoteIndex} \u2192 ${newNoteIndex}`);
@@ -2675,9 +2412,9 @@ var FolderChangeHandler = class {
2675
2412
  async revertFolderRename(oldName, newName) {
2676
2413
  const { notesDir, scheduler, watchState, logger: logger2 } = this.config;
2677
2414
  try {
2678
- const oldPath = join5(notesDir, oldName);
2679
- const newPath = join5(notesDir, newName);
2680
- if (existsSync5(newPath)) {
2415
+ const oldPath = join3(notesDir, oldName);
2416
+ const newPath = join3(notesDir, newName);
2417
+ if (existsSync3(newPath)) {
2681
2418
  scheduler.setUpdating(true);
2682
2419
  await fsPromises.rename(newPath, oldPath);
2683
2420
  logger2.warn(`\u6587\u4EF6\u5939\u5DF2\u56DE\u9000: ${newName} \u2192 ${oldName}`);
@@ -2690,86 +2427,371 @@ var FolderChangeHandler = class {
2690
2427
  logger2.error(`\u56DE\u9000\u6587\u4EF6\u5939\u91CD\u547D\u540D\u5931\u8D25: ${error}`);
2691
2428
  }
2692
2429
  }
2693
- extractNoteIndexOrWarn(name) {
2694
- const noteIndex = name.match(/^(\d{4})/)?.[1] || null;
2695
- if (!noteIndex) {
2696
- this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
2697
- }
2698
- return noteIndex;
2430
+ extractNoteIndexOrWarn(name) {
2431
+ const noteIndex = name.match(/^(\d{4})/)?.[1] || null;
2432
+ if (!noteIndex) {
2433
+ this.config.logger.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${name}`);
2434
+ }
2435
+ return noteIndex;
2436
+ }
2437
+ // #endregion - 私有实现
2438
+ };
2439
+
2440
+ // services/file-watcher/fsWatcherAdapter.ts
2441
+ import { watch } from "fs";
2442
+ import { basename, dirname, join as join4, sep } from "path";
2443
+ var FsWatcherAdapter = class {
2444
+ constructor(config2) {
2445
+ this.config = config2;
2446
+ }
2447
+ /** 文件系统监听器实例 */
2448
+ watcher = null;
2449
+ start() {
2450
+ const { logger: logger2 } = this.config;
2451
+ if (this.watcher) {
2452
+ logger2.warn("\u6587\u4EF6\u76D1\u542C\u670D\u52A1\u5DF2\u542F\u52A8");
2453
+ return;
2454
+ }
2455
+ this.watcher = watch(
2456
+ this.config.notesDir,
2457
+ { recursive: true },
2458
+ (eventType, filename) => this.handleFsEvent(eventType, filename)
2459
+ );
2460
+ logger2.debug(`\u6587\u4EF6\u76D1\u542C\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u76EE\u5F55\uFF1A${this.config.notesDir}`);
2461
+ }
2462
+ stop() {
2463
+ if (!this.watcher) return;
2464
+ this.watcher.close();
2465
+ this.watcher = null;
2466
+ }
2467
+ isWatching() {
2468
+ return this.watcher !== null;
2469
+ }
2470
+ handleFsEvent(eventType, filename) {
2471
+ const { isUpdating, onRename, onNoteEvent } = this.config;
2472
+ if (!filename || // 忽略无文件变更
2473
+ isUpdating()) {
2474
+ return;
2475
+ }
2476
+ if (eventType === "rename" && // 检测文件夹 rename 事件
2477
+ !filename.includes(sep)) {
2478
+ onRename(filename);
2479
+ return;
2480
+ }
2481
+ const baseFilename = basename(filename);
2482
+ if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
2483
+ return;
2484
+ }
2485
+ const fullPath = join4(this.config.notesDir, filename);
2486
+ const event = this.buildWatchEvent(fullPath, filename);
2487
+ if (!event) {
2488
+ return;
2489
+ }
2490
+ onNoteEvent(event);
2491
+ }
2492
+ buildWatchEvent(fullPath, filename) {
2493
+ const noteDirName = basename(dirname(fullPath));
2494
+ const noteIndex = NoteManager.extractNoteIndex(noteDirName);
2495
+ if (!noteIndex) {
2496
+ NoteManager.warnInvalidNoteIndex(noteDirName);
2497
+ return null;
2498
+ }
2499
+ const fileType = filename.endsWith("README.md") ? WATCH_EVENT_TYPES.README : WATCH_EVENT_TYPES.CONFIG;
2500
+ return {
2501
+ path: fullPath,
2502
+ type: fileType,
2503
+ noteIndex,
2504
+ noteDirName,
2505
+ noteDirPath: dirname(fullPath)
2506
+ };
2507
+ }
2508
+ };
2509
+
2510
+ // services/file-watcher/globalUpdateCoordinator.ts
2511
+ var GlobalUpdateCoordinator = class {
2512
+ constructor(config2) {
2513
+ this.config = config2;
2514
+ }
2515
+ async applyConfigUpdates(changedNoteIndexes) {
2516
+ if (changedNoteIndexes.length === 0) return;
2517
+ const { readmeService, noteIndexCache, logger: logger2 } = this.config;
2518
+ logger2.info("\u68C0\u6D4B\u5230\u7B14\u8BB0\u72B6\u6001\u53D8\u5316\uFF0C\u589E\u91CF\u66F4\u65B0\u5168\u5C40\u6587\u4EF6...");
2519
+ for (const noteIndex of changedNoteIndexes) {
2520
+ await safeExecute(
2521
+ `\u589E\u91CF\u66F4\u65B0 ${noteIndex}`,
2522
+ async () => {
2523
+ const item = noteIndexCache.getByNoteIndex(noteIndex);
2524
+ await readmeService.updateNoteInReadme(
2525
+ noteIndex,
2526
+ item?.noteConfig || {}
2527
+ );
2528
+ logger2.info(`\u589E\u91CF\u66F4\u65B0 README \u4E2D\u7684\u7B14\u8BB0: ${noteIndex}`);
2529
+ },
2530
+ logger2
2531
+ );
2532
+ }
2533
+ await readmeService.regenerateSidebar();
2534
+ }
2535
+ async updateNoteReadmesOnly(events) {
2536
+ const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))];
2537
+ if (noteIndexesToUpdate.length === 0) return;
2538
+ await this.config.readmeService.updateNoteReadmesOnly(noteIndexesToUpdate);
2539
+ }
2540
+ };
2541
+
2542
+ // services/file-watcher/readmeChangeHandler.ts
2543
+ var ReadmeChangeHandler = class {
2544
+ constructor(config2) {
2545
+ this.config = config2;
2546
+ }
2547
+ async handle(events) {
2548
+ if (events.length === 0) return;
2549
+ const indexes = [...new Set(events.map((c) => c.noteIndex))];
2550
+ for (const noteIndex of indexes) {
2551
+ const noteInfo = this.config.noteService.getNoteByIndex(noteIndex);
2552
+ if (noteInfo) {
2553
+ await this.config.noteService.fixNoteTitle(noteInfo);
2554
+ }
2555
+ }
2556
+ }
2557
+ };
2558
+
2559
+ // services/file-watcher/renameDetector.ts
2560
+ import { existsSync as existsSync4 } from "fs";
2561
+ import { join as join5 } from "path";
2562
+ var FOLDER_RENAME_DETECT_WINDOW_MS = 500;
2563
+ var RenameDetector = class {
2564
+ constructor(config2) {
2565
+ this.config = config2;
2566
+ }
2567
+ /** 待处理的文件夹重命名 */
2568
+ pendingFolderRename = null;
2569
+ /** 文件夹重命名检测定时器 */
2570
+ folderRenameTimer = null;
2571
+ handleFsRename(folderName) {
2572
+ const { notesDir, dirCache, logger: logger2, onDelete, onRename } = this.config;
2573
+ const folderPath = join5(notesDir, folderName);
2574
+ const folderExists = existsSync4(folderPath);
2575
+ const noteIndex = NoteManager.extractNoteIndex(folderName);
2576
+ if (!noteIndex) {
2577
+ logger2.warn(`\u65E0\u6CD5\u4ECE\u6587\u4EF6\u5939\u540D\u79F0\u63D0\u53D6\u7B14\u8BB0\u7D22\u5F15: ${folderName}`);
2578
+ return;
2579
+ }
2580
+ if (!folderExists) {
2581
+ if (dirCache.has(folderName)) {
2582
+ logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u5220\u9664/\u91CD\u547D\u540D: ${folderName}`);
2583
+ this.pendingFolderRename = { oldName: folderName, time: Date.now() };
2584
+ if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer);
2585
+ this.folderRenameTimer = setTimeout(() => {
2586
+ if (this.pendingFolderRename) {
2587
+ logger2.warn(`\u68C0\u6D4B\u5230\u7B14\u8BB0\u5220\u9664: ${this.pendingFolderRename.oldName}`);
2588
+ onDelete(this.pendingFolderRename.oldName);
2589
+ }
2590
+ this.pendingFolderRename = null;
2591
+ this.folderRenameTimer = null;
2592
+ }, FOLDER_RENAME_DETECT_WINDOW_MS);
2593
+ }
2594
+ return;
2595
+ }
2596
+ if (!dirCache.has(folderName)) {
2597
+ logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u521B\u5EFA/\u91CD\u547D\u540D: ${folderName}`);
2598
+ if (this.pendingFolderRename && Date.now() - this.pendingFolderRename.time < FOLDER_RENAME_DETECT_WINDOW_MS) {
2599
+ const oldName = this.pendingFolderRename.oldName;
2600
+ const oldNoteIndex = NoteManager.extractNoteIndex(oldName);
2601
+ if (oldNoteIndex && oldNoteIndex === noteIndex) {
2602
+ logger2.info(`\u68C0\u6D4B\u5230\u6587\u4EF6\u5939\u91CD\u547D\u540D: ${oldName} \u2192 ${folderName}`);
2603
+ if (this.folderRenameTimer) {
2604
+ clearTimeout(this.folderRenameTimer);
2605
+ this.folderRenameTimer = null;
2606
+ }
2607
+ onRename(oldName, folderName);
2608
+ this.pendingFolderRename = null;
2609
+ } else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
2610
+ logger2.warn(`\u7D22\u5F15\u51B2\u7A81\uFF0C\u56DE\u9000: ${oldName} -> ${folderName}`);
2611
+ }
2612
+ }
2613
+ dirCache.add(folderName);
2614
+ if (this.pendingFolderRename) {
2615
+ dirCache.delete(this.pendingFolderRename.oldName);
2616
+ }
2617
+ }
2618
+ }
2619
+ clearTimers() {
2620
+ if (this.folderRenameTimer) {
2621
+ clearTimeout(this.folderRenameTimer);
2622
+ this.folderRenameTimer = null;
2623
+ }
2624
+ this.pendingFolderRename = null;
2625
+ }
2626
+ };
2627
+
2628
+ // services/file-watcher/watchState.ts
2629
+ import { createHash } from "crypto";
2630
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
2631
+ import { join as join6 } from "path";
2632
+ var WatchState = class {
2633
+ constructor(config2) {
2634
+ this.config = config2;
2635
+ }
2636
+ /** 文件哈希缓存 */
2637
+ fileHashes = /* @__PURE__ */ new Map();
2638
+ /** 笔记目录缓存 */
2639
+ noteDirCache = /* @__PURE__ */ new Set();
2640
+ /** 笔记配置缓存 */
2641
+ configCache = /* @__PURE__ */ new Map();
2642
+ /**
2643
+ * 获取指定文件的 MD5 哈希值,若文件不存在或读取失败返回 null
2644
+ *
2645
+ * @param filePath 文件路径
2646
+ * @returns 文件哈希
2647
+ */
2648
+ getFileHash(filePath) {
2649
+ try {
2650
+ if (!existsSync5(filePath)) return null;
2651
+ const content = readFileSync3(filePath, "utf-8");
2652
+ if (content.length === 0) return null;
2653
+ return createHash("md5").update(content).digest("hex");
2654
+ } catch {
2655
+ return null;
2656
+ }
2657
+ }
2658
+ /**
2659
+ * 更新文件哈希缓存,只有当文件内容发生变化时才更新并返回 true
2660
+ *
2661
+ * @param filePath 文件路径
2662
+ * @returns 是否发生变化
2663
+ */
2664
+ updateFileHash(filePath) {
2665
+ const current = this.getFileHash(filePath);
2666
+ if (!current) return false;
2667
+ const prev = this.fileHashes.get(filePath);
2668
+ if (prev === current) return false;
2669
+ this.fileHashes.set(filePath, current);
2670
+ return true;
2671
+ }
2672
+ /**
2673
+ * 检查指定名称的笔记目录是否已存在于缓存中
2674
+ *
2675
+ * @param name 笔记目录名称
2676
+ * @returns 若存在则返回 true,否则返回 false
2677
+ */
2678
+ hasNoteDir(name) {
2679
+ return this.noteDirCache.has(name);
2699
2680
  }
2700
- // #endregion - 私有实现
2701
- };
2702
-
2703
- // services/file-watcher/fsWatcherAdapter.ts
2704
- import { watch } from "fs";
2705
- import { basename, dirname, join as join6, sep } from "path";
2706
- var FsWatcherAdapter = class {
2707
- constructor(config2) {
2708
- this.config = config2;
2681
+ /**
2682
+ * 将指定名称的笔记目录添加到缓存中
2683
+ *
2684
+ * @param name 笔记目录名称
2685
+ */
2686
+ addNoteDir(name) {
2687
+ this.noteDirCache.add(name);
2709
2688
  }
2710
- /** 文件系统监听器实例 */
2711
- watcher = null;
2712
- start() {
2713
- const { logger: logger2 } = this.config;
2714
- if (this.watcher) {
2715
- logger2.warn("\u6587\u4EF6\u76D1\u542C\u670D\u52A1\u5DF2\u542F\u52A8");
2716
- return;
2717
- }
2718
- this.watcher = watch(
2719
- this.config.notesDir,
2720
- { recursive: true },
2721
- (eventType, filename) => this.handleFsEvent(eventType, filename)
2722
- );
2723
- logger2.debug(`\u6587\u4EF6\u76D1\u542C\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u76EE\u5F55\uFF1A${this.config.notesDir}`);
2689
+ /**
2690
+ * 从缓存中移除指定名称的笔记目录
2691
+ *
2692
+ * @param name 笔记目录名称
2693
+ */
2694
+ deleteNoteDir(name) {
2695
+ this.noteDirCache.delete(name);
2724
2696
  }
2725
- stop() {
2726
- if (!this.watcher) return;
2727
- this.watcher.close();
2728
- this.watcher = null;
2697
+ /**
2698
+ * 清空所有缓存数据,包括文件哈希、笔记目录和配置快照
2699
+ */
2700
+ clearAll() {
2701
+ this.fileHashes.clear();
2702
+ this.noteDirCache.clear();
2703
+ this.configCache.clear();
2729
2704
  }
2730
- isWatching() {
2731
- return this.watcher !== null;
2705
+ /**
2706
+ * 清除指定笔记目录相关的缓存数据,包括 README.md .tnotes.json 的文件哈希及配置快照
2707
+ *
2708
+ * @param noteDirName 笔记目录名称
2709
+ */
2710
+ clearNoteCaches(noteDirName) {
2711
+ const readmePath = join6(this.config.notesDir, noteDirName, "README.md");
2712
+ const configPath = join6(this.config.notesDir, noteDirName, ".tnotes.json");
2713
+ this.fileHashes.delete(readmePath);
2714
+ this.fileHashes.delete(configPath);
2715
+ this.configCache.delete(configPath);
2732
2716
  }
2733
- handleFsEvent(eventType, filename) {
2734
- const { isUpdating, onRename, onNoteEvent } = this.config;
2735
- if (!filename || // 忽略无文件变更
2736
- isUpdating()) {
2737
- return;
2738
- }
2739
- if (eventType === "rename" && // 检测文件夹 rename 事件
2740
- !filename.includes(sep)) {
2741
- onRename(filename);
2742
- return;
2743
- }
2744
- const baseFilename = basename(filename);
2745
- if (baseFilename !== "README.md" && baseFilename !== ".tnotes.json") {
2746
- return;
2747
- }
2748
- const fullPath = join6(this.config.notesDir, filename);
2749
- const event = this.buildWatchEvent(fullPath, filename);
2750
- if (!event) {
2751
- return;
2752
- }
2753
- onNoteEvent(event);
2717
+ /**
2718
+ * 获取指定配置文件路径对应的配置快照
2719
+ *
2720
+ * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2721
+ * @returns 配置快照,若不存在则返回 undefined
2722
+ */
2723
+ getConfigSnapshot(configPath) {
2724
+ return this.configCache.get(configPath);
2754
2725
  }
2755
- buildWatchEvent(fullPath, filename) {
2756
- const noteDirName = basename(dirname(fullPath));
2757
- const noteIndex = NoteManager.extractNoteIndex(noteDirName);
2758
- if (!noteIndex) {
2759
- NoteManager.warnInvalidNoteIndex(noteDirName);
2726
+ /**
2727
+ * 设置指定配置文件路径的配置快照到缓存中
2728
+ *
2729
+ * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2730
+ * @param snapshot 配置快照对象
2731
+ */
2732
+ setConfigSnapshot(configPath, snapshot) {
2733
+ this.configCache.set(configPath, snapshot);
2734
+ }
2735
+ /**
2736
+ * 读取指定配置文件的快照
2737
+ *
2738
+ * 解析 .tnotes.json 配置文件,提取 done、enableDiscussions、description 字段。
2739
+ *
2740
+ * @param configPath 配置文件路径(通常为 .tnotes.json 的绝对路径)
2741
+ * @returns 配置快照,若文件不存在或解析失败则返回 null
2742
+ */
2743
+ readConfigSnapshot(configPath) {
2744
+ try {
2745
+ if (!existsSync5(configPath)) return null;
2746
+ const content = readFileSync3(configPath, "utf-8");
2747
+ const config2 = JSON.parse(content);
2748
+ return {
2749
+ done: Boolean(config2.done),
2750
+ enableDiscussions: Boolean(config2.enableDiscussions),
2751
+ description: config2.description || ""
2752
+ };
2753
+ } catch (error) {
2754
+ this.config.logger.error(`[\u8BFB\u53D6\u914D\u7F6E\u5FEB\u7167] ${error}`);
2760
2755
  return null;
2761
2756
  }
2762
- const fileType = filename.endsWith("README.md") ? WATCH_EVENT_TYPES.README : WATCH_EVENT_TYPES.CONFIG;
2763
- return {
2764
- path: fullPath,
2765
- type: fileType,
2766
- noteIndex,
2767
- noteDirName,
2768
- noteDirPath: dirname(fullPath)
2769
- };
2757
+ }
2758
+ /**
2759
+ * 从磁盘初始化监听状态缓存:
2760
+ * 遍历笔记根目录下的所有子目录,将每个笔记目录的 README.md 和 .tnotes.json
2761
+ * 的哈希值及配置快照加载到缓存中。
2762
+ */
2763
+ initializeFromDisk() {
2764
+ try {
2765
+ const noteDirs = readdirSync2(this.config.notesDir);
2766
+ this.clearAll();
2767
+ for (const noteDir of noteDirs) {
2768
+ const noteDirPath = join6(this.config.notesDir, noteDir);
2769
+ if (!statSync(noteDirPath).isDirectory()) continue;
2770
+ this.noteDirCache.add(noteDir);
2771
+ const readmePath = join6(noteDirPath, "README.md");
2772
+ const readmeHash = this.getFileHash(readmePath);
2773
+ if (readmeHash) this.fileHashes.set(readmePath, readmeHash);
2774
+ const configPath = join6(noteDirPath, ".tnotes.json");
2775
+ const configHash = this.getFileHash(configPath);
2776
+ if (configHash) {
2777
+ this.fileHashes.set(configPath, configHash);
2778
+ const snapshot = this.readConfigSnapshot(configPath);
2779
+ if (snapshot) this.configCache.set(configPath, snapshot);
2780
+ }
2781
+ }
2782
+ } catch (error) {
2783
+ this.config.logger.warn(
2784
+ `[initializeFromDisk] \u521D\u59CB\u5316\u76D1\u542C\u72B6\u6001\u5931\u8D25: ${error}`
2785
+ );
2786
+ }
2770
2787
  }
2771
2788
  };
2772
2789
 
2790
+ // services/note/service.ts
2791
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "fs";
2792
+ import { join as join7 } from "path";
2793
+ import { v4 as uuidv4 } from "uuid";
2794
+
2773
2795
  // services/readme/service.ts
2774
2796
  import {
2775
2797
  existsSync as existsSync6,
@@ -3091,18 +3113,6 @@ var ReadmeService = class _ReadmeService {
3091
3113
  }
3092
3114
  };
3093
3115
 
3094
- // services/note/service.ts
3095
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "fs";
3096
- import { join as join7 } from "path";
3097
- import { v4 as uuidv4 } from "uuid";
3098
-
3099
- // config/templates.ts
3100
- function generateNoteTitle(noteIndex, title, repoUrl) {
3101
- const dirName = `${noteIndex}. ${title}`;
3102
- const encodedDirName = encodeURIComponent(dirName);
3103
- return `# [${dirName}](${repoUrl}/${encodedDirName})`;
3104
- }
3105
-
3106
3116
  // services/note/service.ts
3107
3117
  var NEW_NOTES_README_MD_TEMPLATE = `
3108
3118
  <!-- region:toc -->
@@ -3355,6 +3365,7 @@ var NoteService = class _NoteService {
3355
3365
  // services/file-watcher/service.ts
3356
3366
  var NOTES_DIR_NOT_SET_ERROR = "NOTES_DIR_PATH \u672A\u8BBE\u7F6E\uFF0C\u65E0\u6CD5\u542F\u52A8\u6587\u4EF6\u76D1\u542C";
3357
3367
  var UPDATE_UNLOCK_DELAY_MS2 = 500;
3368
+ var RENAME_BROADCAST_PATH = "/__tnotes_broadcast_rename";
3358
3369
  var FileWatcherService = class _FileWatcherService {
3359
3370
  constructor(notesDir = NOTES_DIR_PATH) {
3360
3371
  this.notesDir = notesDir;
@@ -3414,7 +3425,58 @@ var FileWatcherService = class _FileWatcherService {
3414
3425
  noteService: this.noteService,
3415
3426
  readmeService: this.readmeService,
3416
3427
  noteIndexCache: this.noteIndexCache,
3417
- logger
3428
+ logger,
3429
+ onRenameSuccess: (payload) => {
3430
+ void this.broadcastRename(payload);
3431
+ }
3432
+ });
3433
+ }
3434
+ async broadcastRename(payload) {
3435
+ const path2 = `/${repoName}${RENAME_BROADCAST_PATH}`;
3436
+ const body = JSON.stringify(payload);
3437
+ const hosts = ["127.0.0.1", "::1"];
3438
+ let lastError = null;
3439
+ for (const host of hosts) {
3440
+ try {
3441
+ await this.postOnce(host, path2, body);
3442
+ return;
3443
+ } catch (error) {
3444
+ lastError = error;
3445
+ }
3446
+ }
3447
+ logger.warn(
3448
+ `\u5E7F\u64AD\u91CD\u547D\u540D\u4E8B\u4EF6\u5931\u8D25 (POST http://[127.0.0.1|::1]:${port}${path2}): ${String(lastError)}`
3449
+ );
3450
+ }
3451
+ postOnce(host, path2, body) {
3452
+ return new Promise((resolve2, reject) => {
3453
+ const req = httpRequest(
3454
+ {
3455
+ host,
3456
+ port,
3457
+ path: path2,
3458
+ method: "POST",
3459
+ family: host.includes(":") ? 6 : 4,
3460
+ headers: {
3461
+ "Content-Type": "application/json",
3462
+ "Content-Length": Buffer.byteLength(body)
3463
+ }
3464
+ },
3465
+ (res) => {
3466
+ res.on("data", () => {
3467
+ });
3468
+ res.on("end", () => {
3469
+ if (res.statusCode && res.statusCode >= 400) {
3470
+ reject(new Error(`HTTP ${res.statusCode}`));
3471
+ return;
3472
+ }
3473
+ resolve2();
3474
+ });
3475
+ }
3476
+ );
3477
+ req.on("error", reject);
3478
+ req.write(body);
3479
+ req.end();
3418
3480
  });
3419
3481
  }
3420
3482
  initRenameDetector() {
@@ -3626,6 +3688,7 @@ var GitService = class {
3626
3688
  };
3627
3689
 
3628
3690
  // services/timestamp/service.ts
3691
+ import { execSync as execSync3 } from "child_process";
3629
3692
  import {
3630
3693
  existsSync as existsSync7,
3631
3694
  readFileSync as readFileSync6,
@@ -3634,7 +3697,6 @@ import {
3634
3697
  statSync as statSync2
3635
3698
  } from "fs";
3636
3699
  import { join as join8 } from "path";
3637
- import { execSync as execSync3 } from "child_process";
3638
3700
  var BIRTH_DATE = (/* @__PURE__ */ new Date("1999-06-29T00:00:00+08:00")).getTime();
3639
3701
  var TimestampService = class {
3640
3702
  noteManager;
@@ -4146,6 +4208,41 @@ var VitepressService = class _VitepressService {
4146
4208
  }
4147
4209
  };
4148
4210
 
4211
+ // commands/BaseCommand.ts
4212
+ var BaseCommand = class {
4213
+ constructor(name) {
4214
+ this.name = name;
4215
+ this.logger = logger.child(name);
4216
+ }
4217
+ logger;
4218
+ options = {};
4219
+ /** 命令描述(从静态配置读取) */
4220
+ get description() {
4221
+ return COMMAND_DESCRIPTIONS[this.name];
4222
+ }
4223
+ /**
4224
+ * 设置命令选项
4225
+ */
4226
+ setOptions(options) {
4227
+ this.options = { ...this.options, ...options };
4228
+ }
4229
+ /**
4230
+ * 执行命令(带错误处理)
4231
+ */
4232
+ async execute() {
4233
+ const startTime = Date.now();
4234
+ try {
4235
+ this.logger.start(this.description);
4236
+ await this.run();
4237
+ const duration = Date.now() - startTime;
4238
+ this.logger.done(`\u547D\u4EE4\u6267\u884C\u8017\u65F6\uFF1A${duration} ms`);
4239
+ } catch (error) {
4240
+ handleError(error);
4241
+ throw error;
4242
+ }
4243
+ }
4244
+ };
4245
+
4149
4246
  // commands/note/UpdateNoteConfigCommand.ts
4150
4247
  var UpdateNoteConfigCommand = class extends BaseCommand {
4151
4248
  noteService;
@@ -4295,20 +4392,16 @@ var RenameNoteCommand = class extends BaseCommand {
4295
4392
  };
4296
4393
 
4297
4394
  export {
4298
- COMMAND_NAMES,
4299
- COMMAND_DESCRIPTIONS,
4300
- COMMAND_OPTIONS,
4395
+ ROOT_DIR_PATH,
4396
+ ROOT_CONFIG_PATH,
4301
4397
  handleError,
4302
4398
  generateAnchor,
4303
4399
  logger,
4304
4400
  createLogger,
4305
4401
  parseArgs,
4306
4402
  parseReadmeCompletedNotes,
4307
- ROOT_DIR_PATH,
4308
- ROOT_CONFIG_PATH,
4309
- NoteManager,
4310
4403
  runCommand,
4311
- BaseCommand,
4404
+ NoteManager,
4312
4405
  NoteIndexCache,
4313
4406
  ReadmeService,
4314
4407
  NoteService,
@@ -4316,6 +4409,10 @@ export {
4316
4409
  GitService,
4317
4410
  TimestampService,
4318
4411
  VitepressService,
4412
+ COMMAND_NAMES,
4413
+ COMMAND_DESCRIPTIONS,
4414
+ COMMAND_OPTIONS,
4415
+ BaseCommand,
4319
4416
  UpdateNoteConfigCommand,
4320
4417
  RenameNoteCommand
4321
4418
  };