@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,128 @@
1
+ /**
2
+ * services/file-watcher/fsWatcherAdapter.ts
3
+ *
4
+ * fs.watch 适配器:仅负责监听和事件分发
5
+ */
6
+
7
+ import { watch } from 'fs'
8
+ import { basename, dirname, join, sep } from 'path'
9
+
10
+ import { WATCH_EVENT_TYPES } from './internal'
11
+ import { NoteManager } from '../../core/NoteManager'
12
+
13
+ import type { WatchEvent, WatchEventType } from './internal'
14
+ import type { Logger } from '../../utils'
15
+ import type { FSWatcher } from 'fs'
16
+
17
+ interface FsWatcherAdapterConfig {
18
+ /** 笔记目录路径 */
19
+ notesDir: string
20
+ /** 检查是否正在更新的方法 */
21
+ isUpdating: () => boolean
22
+ /** 文件夹重命名事件回调 */
23
+ onRename: (folderName: string) => void
24
+ /** 笔记事件处理回调 */
25
+ onNoteEvent: (event: WatchEvent) => void
26
+ /** 日志记录器 */
27
+ logger: Logger
28
+ }
29
+
30
+ export class FsWatcherAdapter {
31
+ /** 文件系统监听器实例 */
32
+ private watcher: FSWatcher | null = null
33
+
34
+ constructor(private config: FsWatcherAdapterConfig) {}
35
+
36
+ start(): void {
37
+ const { logger } = this.config
38
+
39
+ if (this.watcher) {
40
+ logger.warn('文件监听服务已启动')
41
+ return
42
+ }
43
+
44
+ this.watcher = watch(
45
+ this.config.notesDir,
46
+ { recursive: true },
47
+ (eventType, filename) => this.handleFsEvent(eventType, filename),
48
+ )
49
+
50
+ logger.debug(`文件监听已启动,监听目录:${this.config.notesDir}`)
51
+ }
52
+
53
+ stop(): void {
54
+ if (!this.watcher) return
55
+ this.watcher.close()
56
+ this.watcher = null
57
+ }
58
+
59
+ isWatching(): boolean {
60
+ return this.watcher !== null
61
+ }
62
+
63
+ private handleFsEvent(eventType: string, filename: string | undefined): void {
64
+ const { isUpdating, onRename, onNoteEvent } = this.config
65
+
66
+ // 过滤无效事件
67
+ // 处理需要跳过监听的场景
68
+ if (
69
+ !filename || // 忽略无文件变更
70
+ isUpdating() // 如果正在更新,忽略所有变更
71
+ ) {
72
+ return
73
+ }
74
+
75
+ // 文件夹级事件
76
+ // 处理笔记名称(笔记所属的直接父级文件夹名称)发生变化的场景
77
+ // 根层 rename:文件夹创建/删除/重命名,交给 RenameDetector
78
+ if (
79
+ eventType === 'rename' && // 检测文件夹 rename 事件
80
+ !filename.includes(sep) // 顶层文件夹名称发生变更
81
+ ) {
82
+ onRename(filename)
83
+ return
84
+ }
85
+
86
+ // 文件级事件
87
+ // 处理笔记文件内容(笔记 README.md 文件、笔记配置 .tnotes.json 文件)发生变化的场景
88
+ // 只处理 README.md 和 .tnotes.json 文件的变更
89
+ const baseFilename = basename(filename)
90
+ if (baseFilename !== 'README.md' && baseFilename !== '.tnotes.json') {
91
+ // 忽略非目标文件的变更事件(如 assets 文件夹、其他文件等)
92
+ return
93
+ }
94
+
95
+ const fullPath = join(this.config.notesDir, filename)
96
+ const event = this.buildWatchEvent(fullPath, filename)
97
+ if (!event) {
98
+ // 无法构建变更事件 - 通常是笔记格式错误导致,比如笔记名的索引不是 0001-9999
99
+ return
100
+ }
101
+
102
+ onNoteEvent(event)
103
+ }
104
+
105
+ private buildWatchEvent(
106
+ fullPath: string,
107
+ filename: string,
108
+ ): WatchEvent | null {
109
+ const noteDirName = basename(dirname(fullPath))
110
+ const noteIndex = NoteManager.extractNoteIndex(noteDirName)
111
+ if (!noteIndex) {
112
+ NoteManager.warnInvalidNoteIndex(noteDirName)
113
+ return null
114
+ }
115
+
116
+ const fileType: WatchEventType = filename.endsWith('README.md')
117
+ ? WATCH_EVENT_TYPES.README
118
+ : WATCH_EVENT_TYPES.CONFIG
119
+
120
+ return {
121
+ path: fullPath,
122
+ type: fileType,
123
+ noteIndex,
124
+ noteDirName,
125
+ noteDirPath: dirname(fullPath),
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * services/file-watcher/globalUpdateCoordinator.ts
3
+ *
4
+ * 全局更新协调:应用配置更新、更新 README 列表
5
+ */
6
+
7
+ import { safeExecute } from './internal'
8
+
9
+ import type { WatchEvent } from './internal'
10
+ import type { NoteIndexCache } from '../../core/NoteIndexCache'
11
+ import type { Logger } from '../../utils'
12
+ import type { ReadmeService } from '../readme/service'
13
+ import type { TocService } from '../toc/service'
14
+
15
+
16
+ interface GlobalUpdateCoordinatorConfig {
17
+ /** README 服务实例,用于更新笔记 README */
18
+ readmeService: ReadmeService
19
+ /** TOC 服务实例,用于更新 TOC.md 与 sidebar */
20
+ tocService: TocService
21
+ /** 笔记索引缓存实例 */
22
+ noteIndexCache: NoteIndexCache
23
+ /** 日志记录器 */
24
+ logger: Logger
25
+ }
26
+
27
+ export class GlobalUpdateCoordinator {
28
+ constructor(private config: GlobalUpdateCoordinatorConfig) {}
29
+
30
+ async applyConfigUpdates(changedNoteIndexes: string[]): Promise<void> {
31
+ if (changedNoteIndexes.length === 0) return
32
+
33
+ const { tocService, noteIndexCache, logger } = this.config
34
+
35
+ logger.info('检测到笔记状态变化,增量更新全局文件...')
36
+
37
+ for (const noteIndex of changedNoteIndexes) {
38
+ await safeExecute(
39
+ `增量更新 ${noteIndex}`,
40
+ async () => {
41
+ const item = noteIndexCache.getByNoteIndex(noteIndex)
42
+ await tocService.updateNoteInToc(
43
+ noteIndex,
44
+ item?.noteConfig || {},
45
+ )
46
+ logger.info(`增量更新 TOC.md 中的笔记: ${noteIndex}`)
47
+ },
48
+ logger,
49
+ )
50
+ }
51
+
52
+ await tocService.regenerateSidebar()
53
+ }
54
+
55
+ async updateNoteReadmesOnly(events: WatchEvent[]): Promise<void> {
56
+ const noteIndexesToUpdate = [...new Set(events.map((c) => c.noteIndex))]
57
+ if (noteIndexesToUpdate.length === 0) return
58
+ await this.config.readmeService.updateNoteReadmesOnly(noteIndexesToUpdate)
59
+ }
60
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * services/file-watcher/index.ts
3
+ *
4
+ * 文件监听层入口
5
+ */
6
+
7
+ export { FileWatcherService } from './service'
@@ -0,0 +1,79 @@
1
+ /**
2
+ * services/file-watcher/internal.ts
3
+ *
4
+ * 文件监听层内部模型:仅供 file-watcher 层使用
5
+ */
6
+
7
+ import type { Logger } from '../../utils'
8
+
9
+ /**
10
+ * 监听事件类型常量
11
+ */
12
+ export const WATCH_EVENT_TYPES = {
13
+ README: 'readme',
14
+ CONFIG: 'config',
15
+ } as const
16
+
17
+ export type WatchEventType =
18
+ (typeof WATCH_EVENT_TYPES)[keyof typeof WATCH_EVENT_TYPES]
19
+
20
+ /**
21
+ * 监听到的文件变更事件
22
+ *
23
+ * 示例:
24
+ *
25
+ * ```js
26
+ * {
27
+ * path: 'C:\\tnotesjs\\TNotes.introduction\\notes\\0001. TNotes 简介\\README.md',
28
+ * type: 'readme',
29
+ * noteIndex: '0001',
30
+ * noteDirName: '0001. TNotes 简介',
31
+ * noteDirPath: 'C:\\tnotesjs\\TNotes.introduction\\notes\\0001. TNotes 简介'
32
+ * }
33
+ * ```
34
+ */
35
+ export interface WatchEvent {
36
+ /**
37
+ * 笔记文件(README.md、.tnotes.json)的绝对路径
38
+ */
39
+ path: string
40
+ type: WatchEventType
41
+ noteIndex: string
42
+ noteDirName: string
43
+ noteDirPath: string
44
+ /**
45
+ * 文件夹重命名时的旧名称
46
+ */
47
+ oldNoteDirName?: string
48
+ }
49
+
50
+ export type ConfigSnapshot = {
51
+ done: boolean
52
+ enableDiscussions: boolean
53
+ description: string
54
+ }
55
+
56
+ /**
57
+ * 统一的异步错误处理辅助函数
58
+ *
59
+ * 用于包装可能抛出异常的异步操作,将错误统一通过 logger.error 输出,
60
+ * 避免各模块错误处理方式不一致(静默吞掉、格式各异等)。
61
+ *
62
+ * @param label 操作标签,用于错误日志前缀
63
+ * @param fn 需要执行的异步函数
64
+ * @param logger 日志记录器
65
+ * @returns true 表示执行成功,false 表示捕获到异常
66
+ */
67
+ export async function safeExecute(
68
+ label: string,
69
+ fn: () => Promise<void>,
70
+ logger: Logger,
71
+ ): Promise<boolean> {
72
+ try {
73
+ await fn()
74
+ return true
75
+ } catch (error) {
76
+ logger.error(`[${label}] ${error}`)
77
+ return false
78
+ }
79
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * services/file-watcher/readmeChangeHandler.ts
3
+ *
4
+ * README 变更处理
5
+ */
6
+
7
+ import type { WatchEvent } from './internal'
8
+ import type { NoteService } from '../note/service'
9
+
10
+ interface ReadmeChangeHandlerConfig {
11
+ /** 笔记服务实例 */
12
+ noteService: NoteService
13
+ }
14
+
15
+ export class ReadmeChangeHandler {
16
+ constructor(private config: ReadmeChangeHandlerConfig) {}
17
+
18
+ async handle(events: WatchEvent[]): Promise<void> {
19
+ if (events.length === 0) return
20
+ const indexes = [...new Set(events.map((c) => c.noteIndex))]
21
+ for (const noteIndex of indexes) {
22
+ const noteInfo = this.config.noteService.getNoteByIndex(noteIndex)
23
+ if (noteInfo) {
24
+ await this.config.noteService.fixNoteTitle(noteInfo)
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * services/file-watcher/renameDetector.ts
3
+ *
4
+ * 文件夹重命名/删除检测
5
+ */
6
+
7
+ import { existsSync } from 'fs'
8
+ import { join } from 'path'
9
+
10
+ import { NoteManager } from '../../core/NoteManager'
11
+
12
+ import type { Logger } from '../../utils'
13
+
14
+ const FOLDER_RENAME_DETECT_WINDOW_MS = 500
15
+
16
+ interface RenameDetectorDeps {
17
+ /** 笔记目录路径 */
18
+ notesDir: string
19
+ /** 目录缓存操作 */
20
+ dirCache: {
21
+ has: (name: string) => boolean
22
+ add: (name: string) => void
23
+ delete: (name: string) => void
24
+ }
25
+ /** 日志记录器 */
26
+ logger: Logger
27
+ }
28
+
29
+ interface RenameDetectorCallbacks {
30
+ /** 笔记删除事件回调 */
31
+ onDelete: (oldName: string) => void
32
+ /** 笔记重命名事件回调 */
33
+ onRename: (oldName: string, newName: string) => void
34
+ }
35
+
36
+ interface RenameDetectorConfig
37
+ extends RenameDetectorDeps, RenameDetectorCallbacks {}
38
+
39
+ export interface PendingRename {
40
+ oldName: string
41
+ time: number
42
+ }
43
+
44
+ export class RenameDetector {
45
+ /** 待处理的文件夹重命名 */
46
+ private pendingFolderRename: PendingRename | null = null
47
+
48
+ /** 文件夹重命名检测定时器 */
49
+ private folderRenameTimer: NodeJS.Timeout | null = null
50
+
51
+ constructor(private config: RenameDetectorConfig) {}
52
+
53
+ handleFsRename(folderName: string) {
54
+ const { notesDir, dirCache, logger, onDelete, onRename } = this.config
55
+
56
+ const folderPath = join(notesDir, folderName)
57
+ const folderExists = existsSync(folderPath)
58
+ const noteIndex = NoteManager.extractNoteIndex(folderName)
59
+ if (!noteIndex) {
60
+ logger.warn(`无法从文件夹名称提取笔记索引: ${folderName}`)
61
+ return
62
+ }
63
+
64
+ if (!folderExists) {
65
+ // 第一次收到“删除”,先假设是重命名的起点,延迟一段时间再决策
66
+ if (dirCache.has(folderName)) {
67
+ logger.info(`检测到文件夹删除/重命名: ${folderName}`)
68
+ this.pendingFolderRename = { oldName: folderName, time: Date.now() }
69
+ if (this.folderRenameTimer) clearTimeout(this.folderRenameTimer)
70
+ this.folderRenameTimer = setTimeout(() => {
71
+ if (this.pendingFolderRename) {
72
+ logger.warn(`检测到笔记删除: ${this.pendingFolderRename.oldName}`)
73
+ onDelete(this.pendingFolderRename.oldName)
74
+ }
75
+ this.pendingFolderRename = null
76
+ this.folderRenameTimer = null
77
+ }, FOLDER_RENAME_DETECT_WINDOW_MS)
78
+ }
79
+ return
80
+ }
81
+
82
+ // folder exists
83
+ if (!dirCache.has(folderName)) {
84
+ logger.info(`检测到文件夹创建/重命名: ${folderName}`)
85
+ if (
86
+ this.pendingFolderRename &&
87
+ Date.now() - this.pendingFolderRename.time <
88
+ FOLDER_RENAME_DETECT_WINDOW_MS
89
+ ) {
90
+ const oldName = this.pendingFolderRename.oldName
91
+ const oldNoteIndex = NoteManager.extractNoteIndex(oldName)
92
+ if (oldNoteIndex && oldNoteIndex === noteIndex) {
93
+ logger.info(`检测到文件夹重命名: ${oldName} → ${folderName}`)
94
+ if (this.folderRenameTimer) {
95
+ clearTimeout(this.folderRenameTimer)
96
+ this.folderRenameTimer = null
97
+ }
98
+ onRename(oldName, folderName)
99
+ this.pendingFolderRename = null
100
+ } else if (oldNoteIndex && oldNoteIndex !== noteIndex) {
101
+ logger.warn(`索引冲突,回退: ${oldName} -> ${folderName}`)
102
+ }
103
+ }
104
+
105
+ // 新文件夹:无论是否匹配 rename,都把目录缓存补上,必要时移除旧目录
106
+ dirCache.add(folderName)
107
+ if (this.pendingFolderRename) {
108
+ dirCache.delete(this.pendingFolderRename.oldName)
109
+ }
110
+ }
111
+ }
112
+
113
+ clearTimers() {
114
+ if (this.folderRenameTimer) {
115
+ clearTimeout(this.folderRenameTimer)
116
+ this.folderRenameTimer = null
117
+ }
118
+ this.pendingFolderRename = null
119
+ }
120
+ }