@tnotesjs/core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/commands/BaseCommand.ts +58 -0
  2. package/commands/build/BuildCommand.ts +25 -0
  3. package/commands/build/PreviewCommand.ts +29 -0
  4. package/commands/build/index.ts +8 -0
  5. package/commands/dev/DevCommand.ts +75 -0
  6. package/commands/dev/index.ts +7 -0
  7. package/commands/git/PullCommand.ts +25 -0
  8. package/commands/git/PushCommand.ts +64 -0
  9. package/commands/git/index.ts +8 -0
  10. package/commands/index.ts +11 -0
  11. package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
  12. package/commands/init-sub-repo/index.ts +1 -0
  13. package/commands/misc/HelpCommand.ts +104 -0
  14. package/commands/misc/index.ts +7 -0
  15. package/commands/models.ts +87 -0
  16. package/commands/note/CreateNoteCommand.ts +160 -0
  17. package/commands/note/RenameNoteCommand.ts +147 -0
  18. package/commands/note/UpdateNoteConfigCommand.ts +78 -0
  19. package/commands/note/index.ts +9 -0
  20. package/commands/registry.ts +47 -0
  21. package/commands/update/UpdateCommand.ts +219 -0
  22. package/commands/update/index.ts +7 -0
  23. package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
  24. package/commands/update-completed-count/index.ts +5 -0
  25. package/dist/markdown/index.cjs +6 -9
  26. package/dist/markdown/index.js +6 -9
  27. package/dist/vitepress/config/index.cjs +214 -58
  28. package/dist/vitepress/config/index.js +208 -52
  29. package/markdown/components.ts +86 -0
  30. package/markdown/index.ts +17 -0
  31. package/markdown/noteFormatter.test.ts +44 -0
  32. package/markdown/noteFormatter.ts +237 -0
  33. package/package.json +7 -3
  34. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
  35. package/vitepress/components/EnWordList/EnWordList.vue +16 -662
  36. package/vitepress/components/Footprints/Footprints.vue +15 -537
  37. package/vitepress/components/Mermaid/Mermaid.vue +13 -588
  38. package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
  39. package/vitepress/components/MindmapPreview/markdown.ts +1 -1
  40. package/vitepress/components/NotesTable/NotesTable.vue +11 -130
  41. package/vitepress/configs/markdown.config.ts +170 -26
  42. package/vitepress/theme/index.ts +9 -13
  43. package/vitepress/theme/styles/base.scss +15 -0
  44. package/workspace/atomic.ts +113 -0
  45. package/workspace/errors.ts +27 -0
  46. package/workspace/index.ts +40 -0
  47. package/workspace/mutationQueue.ts +28 -0
  48. package/workspace/paths.ts +64 -0
  49. package/workspace/reconcile.test.ts +300 -0
  50. package/workspace/reconcile.ts +95 -0
  51. package/workspace/scanner.ts +292 -0
  52. package/workspace/types.ts +224 -0
  53. package/workspace/workspace.test.ts +333 -0
  54. package/workspace/workspace.ts +1020 -0
  55. package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
@@ -0,0 +1,58 @@
1
+ /**
2
+ * commands/BaseCommand.ts
3
+ *
4
+ * 命令基类
5
+ */
6
+
7
+ import { COMMAND_DESCRIPTIONS } from './models'
8
+ import { handleError, logger } from '../utils'
9
+
10
+ import type { Command, CommandName, CommandOptions } from './models'
11
+ import type { Logger } from '../utils'
12
+
13
+
14
+ /**
15
+ * 命令基类
16
+ */
17
+ export abstract class BaseCommand implements Command {
18
+ protected logger: Logger
19
+ protected options: CommandOptions = {}
20
+
21
+ /** 命令描述(从静态配置读取) */
22
+ get description(): string {
23
+ return COMMAND_DESCRIPTIONS[this.name]
24
+ }
25
+
26
+ constructor(public name: CommandName) {
27
+ this.logger = logger.child(name)
28
+ }
29
+
30
+ /**
31
+ * 设置命令选项
32
+ */
33
+ setOptions(options: CommandOptions): void {
34
+ this.options = { ...this.options, ...options }
35
+ }
36
+
37
+ /**
38
+ * 执行命令(带错误处理)
39
+ */
40
+ async execute(): Promise<void> {
41
+ const startTime = Date.now()
42
+
43
+ try {
44
+ this.logger.start(this.description)
45
+ await this.run()
46
+ const duration = Date.now() - startTime
47
+ this.logger.done(`命令执行耗时:${duration} ms`)
48
+ } catch (error) {
49
+ handleError(error)
50
+ throw error
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 子类需要实现的运行逻辑
56
+ */
57
+ protected abstract run(): Promise<void>
58
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * commands/build/BuildCommand.ts
3
+ *
4
+ * 构建命令 - 使用 VitepressService
5
+ */
6
+
7
+ import { VitepressService } from '../../services'
8
+ import { BaseCommand } from '../BaseCommand'
9
+
10
+ export class BuildCommand extends BaseCommand {
11
+ private vitepressService: VitepressService
12
+
13
+ constructor() {
14
+ super('build')
15
+ this.vitepressService = new VitepressService()
16
+ }
17
+
18
+ protected async run(): Promise<void> {
19
+ this.logger.info('开始构建知识库...')
20
+
21
+ await this.vitepressService.build()
22
+
23
+ this.logger.success('知识库构建完成')
24
+ }
25
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * commands/build/PreviewCommand.ts
3
+ *
4
+ * 预览命令 - 使用 VitepressService
5
+ */
6
+
7
+ import { VitepressService } from '../../services'
8
+ import { BaseCommand } from '../BaseCommand'
9
+
10
+ export class PreviewCommand extends BaseCommand {
11
+ private vitepressService: VitepressService
12
+
13
+ constructor() {
14
+ super('preview')
15
+ this.vitepressService = new VitepressService()
16
+ }
17
+
18
+ protected async run(): Promise<void> {
19
+ this.logger.info('启动预览服务器...')
20
+
21
+ const pid = await this.vitepressService.preview()
22
+
23
+ if (pid) {
24
+ this.logger.success(`预览服务器已启动 (PID: ${pid})`)
25
+ } else {
26
+ this.logger.error('启动预览服务器失败')
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * commands/build/index.ts
3
+ *
4
+ * Build 命令统一导出
5
+ */
6
+
7
+ export { BuildCommand } from './BuildCommand'
8
+ export { PreviewCommand } from './PreviewCommand'
@@ -0,0 +1,75 @@
1
+ /**
2
+ * commands/dev/DevCommand.ts
3
+ *
4
+ * 开发服务器命令 - 使用 VitepressService 和 FileWatcherService
5
+ */
6
+
7
+ import { ConfigManager } from '../../config/ConfigManager'
8
+ import { ROOT_DIR_PATH } from '../../config/constants'
9
+ import { NoteManager, NoteIndexCache } from '../../core'
10
+ import { VitepressService, FileWatcherService } from '../../services'
11
+ import { reconcileTocFromFiles } from '../../services/reconcileToc'
12
+ import { BaseCommand } from '../BaseCommand'
13
+
14
+ export class DevCommand extends BaseCommand {
15
+ private configManager: ConfigManager
16
+ private fileWatcherService: FileWatcherService
17
+ private noteIndexCache: NoteIndexCache
18
+ private noteManager: NoteManager
19
+ private vitepressService: VitepressService
20
+
21
+ constructor() {
22
+ super('dev')
23
+
24
+ this.configManager = ConfigManager.getInstance()
25
+ this.fileWatcherService = new FileWatcherService()
26
+ this.noteIndexCache = NoteIndexCache.getInstance()
27
+ this.noteManager = NoteManager.getInstance()
28
+ this.vitepressService = new VitepressService()
29
+ }
30
+
31
+ protected async run(): Promise<void> {
32
+ // 1. 扫描笔记目录并校验完整性(noteIndex 冲突 + config id 缺失/重复)
33
+ const notes = this.noteManager.scanNotes()
34
+ this.logger.info(`扫描到 ${notes.length} 篇笔记`)
35
+
36
+ // 2. 初始化笔记索引缓存(在 VitePress 启动前完成,供插件使用)
37
+ this.noteIndexCache.initialize(notes)
38
+
39
+ // 3. 重新生成 sidebar.json(必须在 VitePress 启动前完成,基于 TOC.md)
40
+ //
41
+ // sidebar.data.ts 这个 VitePress data loader 只在启动时读取磁盘上的
42
+ // sidebar.json,运行期间靠 HMR 监听其变化做热更新。但冷启动时若 sidebar.json
43
+ // 与当前笔记/TOC.md 不同步(例如 git pull、切分支、或上次会话外离线增删改了
44
+ // 笔记),VitePress 就会把过期数据读进来,导致侧边栏显示错误,需要手动删除
45
+ // .vitepress/cache 才能恢复。这里在启动前主动重建一次,消除启动时的过期窗口。
46
+ // files→TOC 对齐(Workspace):冷启动前从磁盘真值重建 TOC/sidebar
47
+ await reconcileTocFromFiles(ROOT_DIR_PATH)
48
+
49
+ // 4. 启动 VitePress 服务器(会等待服务就绪后返回)
50
+ const result = await this.vitepressService.startServer()
51
+
52
+ if (result) {
53
+ const versionInfo = result.version ? `(v${result.version})` : ''
54
+ this.logger.success(
55
+ `VitePress 服务${versionInfo}已就绪,耗时:${result.elapsed} ms`,
56
+ )
57
+
58
+ // 5. 启动文件监听服务
59
+ const watcherStart = Date.now()
60
+ this.fileWatcherService.start()
61
+ const watcherElapsed = Date.now() - watcherStart
62
+ this.logger.success(`文件监听服务已就绪,耗时:${watcherElapsed} ms`)
63
+
64
+ // 6. 显示本地开发服务地址
65
+ const port =
66
+ this.configManager.get('port') || VitepressService.DEFAULT_DEV_PORT
67
+ const repoName = this.configManager.get('repoName')
68
+ this.logger.info(
69
+ `本地开发服务地址:http://localhost:${port}/${repoName}/`,
70
+ )
71
+ } else {
72
+ this.logger.error('启动服务器失败')
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * commands/dev/index.ts
3
+ *
4
+ * Dev 命令统一导出
5
+ */
6
+
7
+ export { DevCommand } from './DevCommand'
@@ -0,0 +1,25 @@
1
+ /**
2
+ * commands/git/PullCommand.ts
3
+ *
4
+ * Git Pull 命令 - 使用 GitService
5
+ */
6
+
7
+ import { GitService } from '../../services'
8
+ import { BaseCommand } from '../BaseCommand'
9
+
10
+ export class PullCommand extends BaseCommand {
11
+ private gitService: GitService
12
+
13
+ constructor() {
14
+ super('pull')
15
+ this.gitService = new GitService()
16
+ }
17
+
18
+ protected async run(): Promise<void> {
19
+ this.logger.info('正在从远程仓库拉取...')
20
+
21
+ await this.gitService.pull()
22
+
23
+ this.logger.success('拉取完成')
24
+ }
25
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * commands/git/PushCommand.ts
3
+ *
4
+ * Git Push 命令
5
+ *
6
+ * 流程:git add -A → git commit → git push
7
+ */
8
+
9
+ import { ROOT_DIR_PATH } from '../../config/constants'
10
+ import { GitManager } from '../../core'
11
+ import { GitService } from '../../services'
12
+ import { runCommand } from '../../utils'
13
+ import { BaseCommand } from '../BaseCommand'
14
+
15
+ export class PushCommand extends BaseCommand {
16
+ private gitManager: GitManager
17
+ private gitService: GitService
18
+
19
+ constructor() {
20
+ super('push')
21
+
22
+ this.gitManager = new GitManager(ROOT_DIR_PATH)
23
+ this.gitService = new GitService()
24
+ }
25
+
26
+ protected async run(): Promise<void> {
27
+ try {
28
+ // 1. 检查是否有更改或已有未推送的提交
29
+ this.logger.info('检查是否有更改...')
30
+ const status = await this.gitManager.getStatus()
31
+ const hasPendingCommits = (status.ahead ?? 0) > 0
32
+
33
+ if (!status.hasChanges && !hasPendingCommits) {
34
+ this.logger.info('没有更改需要推送')
35
+ return
36
+ }
37
+
38
+ const force = this.options.force === true
39
+ if (force) {
40
+ this.logger.warn('使用强制推送模式 (--force)')
41
+ }
42
+
43
+ if (status.hasChanges) {
44
+ this.logger.info(
45
+ `检测到 ${status.files.length} 个变更文件,正在提交...`,
46
+ )
47
+ const commitMessage = this.gitService.generateCommitMessage()
48
+ await runCommand('git add -A', ROOT_DIR_PATH)
49
+ await runCommand(`git commit -m "${commitMessage}"`, ROOT_DIR_PATH)
50
+ } else {
51
+ this.logger.info(`检测到 ${status.ahead} 个未推送的提交,直接推送...`)
52
+ }
53
+
54
+ // 2. 推送到远程
55
+ this.logger.info('正在推送到远程仓库...')
56
+ const pushCmd = force ? 'git push --force' : 'git push'
57
+ await runCommand(pushCmd, ROOT_DIR_PATH)
58
+ this.logger.success('推送完成')
59
+ } catch (error) {
60
+ this.logger.error('推送失败:', error)
61
+ throw error
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * commands/git/index.ts
3
+ *
4
+ * Git 命令统一导出
5
+ */
6
+
7
+ export { PushCommand } from './PushCommand'
8
+ export { PullCommand } from './PullCommand'
@@ -0,0 +1,11 @@
1
+ /**
2
+ * commands/index.ts
3
+ *
4
+ * commands entry(对外暴露的公共 API)
5
+ */
6
+
7
+ export { UpdateCommand } from './update'
8
+ export { PushCommand } from './git'
9
+ export { COMMAND_NAMES } from './models'
10
+ export type { CommandArgs } from './models'
11
+ export { getCommand } from './registry'
@@ -0,0 +1,206 @@
1
+ /**
2
+ * commands/init-sub-repo/InitSubRepoCommand.ts
3
+ *
4
+ * 初始化 TNotes 子知识库
5
+ */
6
+
7
+ import { readFileSync } from 'fs'
8
+ import { join, dirname } from 'path'
9
+ import { createInterface } from 'readline'
10
+ import { fileURLToPath } from 'url'
11
+ import { v4 as uuidv4 } from 'uuid'
12
+
13
+ import { getConfigManager } from '../../config/ConfigManager'
14
+ import { ROOT_DIR_PATH } from '../../config/constants'
15
+ import {
16
+ buildInitContext,
17
+ buildManualSteps,
18
+ defaultDisplayName,
19
+ DEFAULT_DESCRIPTION,
20
+ DEFAULT_PORT,
21
+ getInitializedConfigPath,
22
+ isAlreadyInitialized,
23
+ validatePort,
24
+ validateTopic,
25
+ InitSubRepoService,
26
+ } from '../../services/init-sub-repo'
27
+ import { reconcileTocFromFiles } from '../../services/reconcileToc'
28
+ import { BaseCommand } from '../BaseCommand'
29
+
30
+ import type { InitSubRepoInput } from '../../services/init-sub-repo'
31
+
32
+ function readCoreVersion(): string {
33
+ const moduleDir = dirname(fileURLToPath(import.meta.url))
34
+ let dir = moduleDir
35
+ while (dir !== dirname(dir)) {
36
+ const pkgPath = join(dir, 'package.json')
37
+ try {
38
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {
39
+ name?: string
40
+ version?: string
41
+ }
42
+ if (pkg.name === '@tnotesjs/core' && pkg.version) return pkg.version
43
+ } catch {
44
+ // continue walking up
45
+ }
46
+ dir = dirname(dir)
47
+ }
48
+ return '0.1.28'
49
+ }
50
+
51
+ export class InitSubRepoCommand extends BaseCommand {
52
+ private rootPath: string
53
+
54
+ constructor() {
55
+ super('init-sub-repo')
56
+ this.rootPath = process.cwd()
57
+ }
58
+
59
+ protected async run(): Promise<void> {
60
+ if (isAlreadyInitialized(this.rootPath)) {
61
+ const configPath = getInitializedConfigPath(this.rootPath)
62
+ this.logger.info(
63
+ `检测到 .tnotes.json 配置文件:${configPath},当前仓库已经是一个 TNotes 知识库`,
64
+ )
65
+ return
66
+ }
67
+
68
+ this.logger.info('初始化 TNotes 子知识库')
69
+ this.logger.info('')
70
+
71
+ const input = await this.promptForInput()
72
+ const ctx = buildInitContext(input)
73
+
74
+ this.logger.info('')
75
+ this.logger.info('即将创建以下知识库:')
76
+ this.logger.info(` 仓库名:${ctx.repoName}`)
77
+ this.logger.info(` 展示名:${ctx.displayName}`)
78
+ this.logger.info(` 端口:${ctx.port}`)
79
+ this.logger.info(` 描述:${ctx.description}`)
80
+ this.logger.info('')
81
+
82
+ const confirmed = await this.promptConfirm('确认初始化? [Y/n] ')
83
+ if (!confirmed) {
84
+ this.logger.info('已取消初始化')
85
+ return
86
+ }
87
+
88
+ const service = InitSubRepoService.fromModuleUrl(import.meta.url, this.rootPath)
89
+ const result = service.writeScaffold(input)
90
+
91
+ if (result.created.length > 0) {
92
+ this.logger.success(`已创建 ${result.created.length} 个文件:`)
93
+ for (const file of result.created) {
94
+ this.logger.info(` + ${file}`)
95
+ }
96
+ }
97
+
98
+ if (result.updated.length > 0) {
99
+ this.logger.success(`已更新 ${result.updated.length} 个文件:`)
100
+ for (const file of result.updated) {
101
+ this.logger.info(` ~ ${file}`)
102
+ }
103
+ }
104
+
105
+ if (result.skipped.length > 0) {
106
+ this.logger.warn(`已跳过 ${result.skipped.length} 个已存在文件:`)
107
+ for (const file of result.skipped) {
108
+ this.logger.info(` - ${file}`)
109
+ }
110
+ }
111
+
112
+ getConfigManager().clearCache()
113
+
114
+ try {
115
+ this.logger.info('')
116
+ this.logger.info('正在规范化 TOC.md 并生成 sidebar.json...')
117
+ await reconcileTocFromFiles(ROOT_DIR_PATH)
118
+ this.logger.success('sidebar.json 已生成')
119
+ } catch (error) {
120
+ this.logger.warn(
121
+ `自动 update 失败(可稍后手动执行 pnpm tn:update):${
122
+ error instanceof Error ? error.message : String(error)
123
+ }`,
124
+ )
125
+ }
126
+
127
+ this.logger.info('')
128
+ this.logger.success('子知识库初始化完成!')
129
+ this.logger.info('')
130
+ this.logger.info('请手动完成以下步骤:')
131
+ for (const [index, step] of buildManualSteps(ctx).entries()) {
132
+ this.logger.info(` ${index + 1}. ${step}`)
133
+ }
134
+ }
135
+
136
+ private async promptForInput(): Promise<InitSubRepoInput> {
137
+ const topic = await this.promptTopic()
138
+ const displayName = await this.promptDisplayName(topic)
139
+ const port = await this.promptPort()
140
+ const description = await this.promptDescription()
141
+
142
+ return {
143
+ topic,
144
+ displayName,
145
+ port,
146
+ description,
147
+ repoUuid: uuidv4(),
148
+ noteUuid: uuidv4(),
149
+ coreVersion: readCoreVersion(),
150
+ }
151
+ }
152
+
153
+ private async promptTopic(): Promise<string> {
154
+ while (true) {
155
+ const answer = await this.ask('请输入 topic(如 react,将生成 TNotes.react): ')
156
+ const error = validateTopic(answer)
157
+ if (error) {
158
+ this.logger.warn(error)
159
+ continue
160
+ }
161
+ return answer.trim()
162
+ }
163
+ }
164
+
165
+ private async promptDisplayName(topic: string): Promise<string> {
166
+ const fallback = defaultDisplayName(topic)
167
+ const answer = await this.ask(
168
+ `请输入展示名(用于首页 hero,默认 ${fallback}): `,
169
+ )
170
+ return answer.trim() || fallback
171
+ }
172
+
173
+ private async promptPort(): Promise<number> {
174
+ const answer = await this.ask(`请输入 dev 端口(默认 ${DEFAULT_PORT}): `)
175
+ const { port, error } = validatePort(answer)
176
+ if (error) this.logger.warn(error)
177
+ return port
178
+ }
179
+
180
+ private async promptDescription(): Promise<string> {
181
+ const answer = await this.ask(
182
+ `请输入仓库描述(默认:${DEFAULT_DESCRIPTION}): `,
183
+ )
184
+ return answer.trim() || DEFAULT_DESCRIPTION
185
+ }
186
+
187
+ private async promptConfirm(question: string): Promise<boolean> {
188
+ const answer = await this.ask(question)
189
+ const normalized = answer.trim().toLowerCase()
190
+ return normalized === '' || normalized === 'y' || normalized === 'yes'
191
+ }
192
+
193
+ private ask(question: string): Promise<string> {
194
+ const rl = createInterface({
195
+ input: process.stdin,
196
+ output: process.stdout,
197
+ })
198
+
199
+ return new Promise((resolve) => {
200
+ rl.question(question, (answer) => {
201
+ rl.close()
202
+ resolve(answer)
203
+ })
204
+ })
205
+ }
206
+ }
@@ -0,0 +1 @@
1
+ export { InitSubRepoCommand } from './InitSubRepoCommand'
@@ -0,0 +1,104 @@
1
+ /**
2
+ * commands/misc/HelpCommand.ts
3
+ *
4
+ * 帮助命令
5
+ */
6
+
7
+ import { createLogger, LogLevel } from '../../utils'
8
+ import { BaseCommand } from '../BaseCommand'
9
+ import {
10
+ COMMAND_NAMES,
11
+ COMMAND_OPTIONS,
12
+ COMMAND_DESCRIPTIONS,
13
+ } from '../models'
14
+
15
+ import type { CommandOption } from '../models'
16
+
17
+ /**
18
+ * 命令分组(用于帮助信息展示)
19
+ *
20
+ * 注意:`update-note-config` 和 `rename-note` 是内部命令,
21
+ * 由 VitePress dev server 通过 HTTP API 调用,不在 CLI 帮助中展示。
22
+ */
23
+ const COMMAND_CATEGORIES = {
24
+ 开发和构建: [COMMAND_NAMES.DEV, COMMAND_NAMES.BUILD, COMMAND_NAMES.PREVIEW],
25
+ 内容管理: [
26
+ COMMAND_NAMES.UPDATE,
27
+ COMMAND_NAMES.UPDATE_COMPLETED_COUNT,
28
+ COMMAND_NAMES.CREATE_NOTES,
29
+ ],
30
+ 'Git 操作': [COMMAND_NAMES.PUSH, COMMAND_NAMES.PULL],
31
+ 初始化: [COMMAND_NAMES.INIT_SUB_REPO],
32
+ 其他: [COMMAND_NAMES.HELP],
33
+ } as const
34
+
35
+ /** 命令选项描述(用于帮助信息展示) */
36
+ const COMMAND_OPTIONS_INFO: Record<
37
+ CommandOption,
38
+ { description: string; applicableTo: string }
39
+ > = {
40
+ [COMMAND_OPTIONS.QUIET]: {
41
+ description: '静默模式',
42
+ applicableTo: 'update',
43
+ },
44
+ [COMMAND_OPTIONS.FORCE]: {
45
+ description: '强制推送',
46
+ applicableTo: 'push',
47
+ },
48
+ }
49
+
50
+ export class HelpCommand extends BaseCommand {
51
+ constructor() {
52
+ super('help')
53
+
54
+ // 禁用时间戳输出,help 结束后其他命令不受影响
55
+ this.logger = createLogger('help', {
56
+ timestamp: false,
57
+ level: process.env.DEBUG ? LogLevel.DEBUG : LogLevel.INFO,
58
+ })
59
+ }
60
+
61
+ protected async run(): Promise<void> {
62
+ this.logger.info('TNotes 命令行工具')
63
+ this.logger.info('')
64
+ this.logger.info('用法:pnpm tn:<command> # 推荐')
65
+ this.logger.info('或者:npx tsx ./.vitepress/tnotes/index.ts --<command>')
66
+ this.logger.info('')
67
+ this.logger.info('可用命令:')
68
+ this.logger.info('')
69
+
70
+ for (const [category, cmdNames] of Object.entries(COMMAND_CATEGORIES)) {
71
+ this.logger.info(` ${category}:`)
72
+ for (const cmdName of cmdNames) {
73
+ const description = COMMAND_DESCRIPTIONS[cmdName]
74
+ const paddingLength = Math.max(25 - cmdName.length, 1)
75
+ const padding = ' '.repeat(paddingLength)
76
+ this.logger.info(` --${cmdName}${padding}${description}`)
77
+ }
78
+ this.logger.info('')
79
+ }
80
+
81
+ this.logger.info('示例:')
82
+ this.logger.info(' npx tsx ./.vitepress/tnotes/index.ts --dev')
83
+ this.logger.info(' pnpm tn:build')
84
+ this.logger.info(' pnpm tn:create-notes # 批量创建笔记')
85
+ this.logger.info(' pnpm tn:update')
86
+ this.logger.info(
87
+ ' pnpm tn:update-completed-count # 生成当前知识库最近 12 个月的完成笔记数量统计',
88
+ )
89
+ this.logger.info('')
90
+ this.logger.info('参数:')
91
+ for (const [option, info] of Object.entries(COMMAND_OPTIONS_INFO)) {
92
+ const paddingLength = Math.max(13 - option.length, 1)
93
+ const padding = ' '.repeat(paddingLength)
94
+ this.logger.info(
95
+ ` --${option}${padding}${info.description} (适用于 ${info.applicableTo})`,
96
+ )
97
+ }
98
+ this.logger.info('')
99
+ this.logger.info('环境变量:')
100
+ this.logger.info(' DEBUG=1 启用调试模式,显示详细日志')
101
+ this.logger.info('')
102
+ this.logger.info('更多信息请查看: .vitepress/tnotes/README.md')
103
+ }
104
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * commands/misc/index.ts
3
+ *
4
+ * 其他杂项命令
5
+ */
6
+
7
+ export { HelpCommand } from './HelpCommand'