@tnotesjs/core 0.6.1 → 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.
- package/commands/BaseCommand.ts +58 -0
- package/commands/build/BuildCommand.ts +25 -0
- package/commands/build/PreviewCommand.ts +29 -0
- package/commands/build/index.ts +8 -0
- package/commands/dev/DevCommand.ts +75 -0
- package/commands/dev/index.ts +7 -0
- package/commands/git/PullCommand.ts +25 -0
- package/commands/git/PushCommand.ts +64 -0
- package/commands/git/index.ts +8 -0
- package/commands/index.ts +11 -0
- package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
- package/commands/init-sub-repo/index.ts +1 -0
- package/commands/misc/HelpCommand.ts +104 -0
- package/commands/misc/index.ts +7 -0
- package/commands/models.ts +87 -0
- package/commands/note/CreateNoteCommand.ts +160 -0
- package/commands/note/RenameNoteCommand.ts +147 -0
- package/commands/note/UpdateNoteConfigCommand.ts +78 -0
- package/commands/note/index.ts +9 -0
- package/commands/registry.ts +47 -0
- package/commands/update/UpdateCommand.ts +219 -0
- package/commands/update/index.ts +7 -0
- package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
- package/commands/update-completed-count/index.ts +5 -0
- package/config/templates.ts +0 -5
- package/dist/{chunk-5F5RJ2HV.cjs → chunk-7FEANCT6.cjs} +16 -7
- package/dist/chunk-AGVER5MX.cjs +1746 -0
- package/dist/{chunk-TJ4527KA.cjs → chunk-BL22KD4V.cjs} +324 -950
- package/dist/{chunk-56PKXCOX.js → chunk-GWG75A5M.js} +13 -4
- package/dist/{chunk-DUTS75WQ.js → chunk-XNO3PCEH.js} +374 -1000
- package/dist/chunk-ZEQS64PE.js +1746 -0
- package/dist/cli/index.cjs +76 -155
- package/dist/cli/index.js +10 -89
- package/dist/markdown/index.cjs +8 -12
- package/dist/markdown/index.d.cts +1 -1
- package/dist/markdown/index.d.ts +1 -1
- package/dist/markdown/index.js +7 -11
- package/dist/{types-CwBWwNVv.d.ts → types-C_d0fFeD.d.ts} +8 -1
- package/dist/{types-CpsEy8lA.d.cts → types-MRBGgJTX.d.cts} +8 -1
- package/dist/vitepress/config/index.cjs +402 -178
- package/dist/vitepress/config/index.js +392 -168
- package/dist/workspace/index.cjs +4 -1202
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.ts +2 -2
- package/dist/workspace/index.js +4 -1202
- package/markdown/components.ts +86 -0
- package/markdown/index.ts +17 -0
- package/markdown/noteFormatter.test.ts +44 -0
- package/markdown/noteFormatter.ts +237 -0
- package/package.json +7 -3
- package/services/file-watcher/folderChangeHandler.ts +19 -15
- package/services/file-watcher/globalUpdateCoordinator.ts +21 -27
- package/services/file-watcher/service.ts +1 -6
- package/services/index.ts +0 -1
- package/services/note/service.ts +4 -6
- package/services/readme/service.ts +11 -18
- package/services/reconcileToc.ts +20 -0
- package/utils/index.ts +0 -9
- package/{services/toc → utils}/moveTocInside.test.ts +1 -1
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
- package/vitepress/components/EnWordList/EnWordList.vue +16 -662
- package/vitepress/components/Footprints/Footprints.vue +15 -537
- package/vitepress/components/Mermaid/Mermaid.vue +13 -588
- package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
- package/vitepress/components/MindmapPreview/markdown.ts +1 -1
- package/vitepress/components/NotesTable/NotesTable.vue +11 -130
- package/vitepress/components/constants.ts +2 -2
- package/vitepress/components/sidebar.data.ts +8 -5
- package/vitepress/configs/markdown.config.ts +170 -26
- package/vitepress/plugins/sidebarStructurePlugin.ts +242 -145
- package/vitepress/theme/index.ts +9 -13
- package/vitepress/theme/styles/base.scss +15 -0
- package/workspace/atomic.ts +113 -0
- package/workspace/errors.ts +27 -0
- package/workspace/index.ts +40 -0
- package/workspace/mutationQueue.ts +28 -0
- package/workspace/paths.ts +64 -0
- package/workspace/reconcile.test.ts +300 -0
- package/workspace/reconcile.ts +95 -0
- package/workspace/scanner.ts +292 -0
- package/workspace/types.ts +224 -0
- package/workspace/workspace.test.ts +333 -0
- package/workspace/workspace.ts +1020 -0
- package/dist/chunk-3R7QSABB.cjs +0 -502
- package/dist/chunk-CZXRQPFJ.js +0 -11
- package/dist/chunk-HXED7KVT.cjs +0 -11
- package/dist/chunk-U6IBLREL.js +0 -502
- package/services/toc/index.ts +0 -5
- package/services/toc/service.ts +0 -759
- package/utils/migrateReadmeToToc.test.ts +0 -111
- package/utils/migrateReadmeToToc.ts +0 -135
- package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/models.ts
|
|
3
|
+
*
|
|
4
|
+
* commands 层对外暴露的类型和常量
|
|
5
|
+
* 注意:此文件不应导入任何命令类,以避免循环引用
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TNotes 内置命令名称常量
|
|
10
|
+
*/
|
|
11
|
+
export const COMMAND_NAMES = {
|
|
12
|
+
BUILD: 'build',
|
|
13
|
+
CREATE_NOTES: 'create-notes',
|
|
14
|
+
DEV: 'dev',
|
|
15
|
+
HELP: 'help',
|
|
16
|
+
PREVIEW: 'preview',
|
|
17
|
+
PULL: 'pull',
|
|
18
|
+
PUSH: 'push',
|
|
19
|
+
RENAME_NOTE: 'rename-note',
|
|
20
|
+
INIT_SUB_REPO: 'init-sub-repo',
|
|
21
|
+
UPDATE: 'update',
|
|
22
|
+
UPDATE_COMPLETED_COUNT: 'update-completed-count',
|
|
23
|
+
UPDATE_NOTE_CONFIG: 'update-note-config',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* TNotes 内置命令名称类型
|
|
28
|
+
*/
|
|
29
|
+
export type CommandName = (typeof COMMAND_NAMES)[keyof typeof COMMAND_NAMES]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 命令描述映射(静态配置,无需实例化即可读取)
|
|
33
|
+
*/
|
|
34
|
+
export const COMMAND_DESCRIPTIONS: Record<CommandName, string> = {
|
|
35
|
+
[COMMAND_NAMES.DEV]: '启动知识库开发服务',
|
|
36
|
+
[COMMAND_NAMES.BUILD]: '构建知识库',
|
|
37
|
+
[COMMAND_NAMES.PREVIEW]: '预览构建后的知识库',
|
|
38
|
+
[COMMAND_NAMES.INIT_SUB_REPO]: '初始化 TNotes 子知识库(一次性脚手架)',
|
|
39
|
+
[COMMAND_NAMES.UPDATE]: '根据笔记内容更新知识库',
|
|
40
|
+
[COMMAND_NAMES.UPDATE_COMPLETED_COUNT]:
|
|
41
|
+
'更新完成笔记数量历史记录(近 1 年,最近 12 个月)',
|
|
42
|
+
[COMMAND_NAMES.CREATE_NOTES]: '新建笔记(支持批量创建)',
|
|
43
|
+
[COMMAND_NAMES.PUSH]: '将知识库推送到 GitHub',
|
|
44
|
+
[COMMAND_NAMES.PULL]: '将 GitHub 的知识库拉下来',
|
|
45
|
+
[COMMAND_NAMES.UPDATE_NOTE_CONFIG]: '更新笔记配置文件',
|
|
46
|
+
[COMMAND_NAMES.RENAME_NOTE]: '重命名笔记',
|
|
47
|
+
[COMMAND_NAMES.HELP]: '显示帮助信息',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 命令参数选项常量
|
|
52
|
+
*/
|
|
53
|
+
export const COMMAND_OPTIONS = {
|
|
54
|
+
QUIET: 'quiet',
|
|
55
|
+
FORCE: 'force',
|
|
56
|
+
} as const
|
|
57
|
+
|
|
58
|
+
export type CommandOption =
|
|
59
|
+
(typeof COMMAND_OPTIONS)[keyof typeof COMMAND_OPTIONS]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 命令选项类型(传递给 BaseCommand.setOptions 的参数类型)
|
|
63
|
+
*/
|
|
64
|
+
export interface CommandOptions {
|
|
65
|
+
force?: boolean
|
|
66
|
+
quiet?: boolean
|
|
67
|
+
[key: string]: unknown
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 命令参数类型
|
|
72
|
+
*/
|
|
73
|
+
export type CommandArgs = {
|
|
74
|
+
[K in CommandName]?: boolean
|
|
75
|
+
} & {
|
|
76
|
+
[COMMAND_OPTIONS.QUIET]?: boolean
|
|
77
|
+
[COMMAND_OPTIONS.FORCE]?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 命令接口
|
|
82
|
+
*/
|
|
83
|
+
export interface Command {
|
|
84
|
+
name: CommandName
|
|
85
|
+
description: string
|
|
86
|
+
execute(): Promise<void>
|
|
87
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/note/CreateNoteCommand.ts
|
|
3
|
+
*
|
|
4
|
+
* 新建笔记命令 - 使用 NoteService(支持批量创建,生成全局唯一 ID)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createInterface } from 'readline'
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
9
|
+
|
|
10
|
+
import { NoteService, ReadmeService } from '../../services'
|
|
11
|
+
import { BaseCommand } from '../BaseCommand'
|
|
12
|
+
|
|
13
|
+
export class CreateNoteCommand extends BaseCommand {
|
|
14
|
+
private noteService: NoteService
|
|
15
|
+
private readmeService: ReadmeService
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super('create-notes')
|
|
19
|
+
|
|
20
|
+
this.noteService = NoteService.getInstance()
|
|
21
|
+
this.readmeService = ReadmeService.getInstance()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected async run(): Promise<void> {
|
|
25
|
+
this.logger.info('创建新笔记...')
|
|
26
|
+
|
|
27
|
+
// 提示用户输入要创建的笔记数量
|
|
28
|
+
const count = await this.promptForCount()
|
|
29
|
+
|
|
30
|
+
// 批量创建笔记
|
|
31
|
+
let successCount = 0
|
|
32
|
+
let failCount = 0
|
|
33
|
+
const createdNotes: string[] = []
|
|
34
|
+
|
|
35
|
+
// 预先扫描一次,构建已使用编号集合,避免循环内重复扫描
|
|
36
|
+
const existingNotes = this.noteService.getAllNotes()
|
|
37
|
+
const usedIndexes = new Set<number>()
|
|
38
|
+
for (const note of existingNotes) {
|
|
39
|
+
const index = parseInt(note.index, 10)
|
|
40
|
+
if (!isNaN(index) && index >= 1 && index <= 9999) {
|
|
41
|
+
usedIndexes.add(index)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = 1; i <= count; i++) {
|
|
46
|
+
try {
|
|
47
|
+
// 为每篇笔记提示标题(如果是批量创建,使用默认标题)
|
|
48
|
+
let title: string
|
|
49
|
+
if (count === 1) {
|
|
50
|
+
title = await this.promptForTitle()
|
|
51
|
+
} else {
|
|
52
|
+
title = `new`
|
|
53
|
+
this.logger.info(`[${i}/${count}] 创建笔记: ${title}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 生成全局唯一的 UUID 作为配置文件中的 id
|
|
57
|
+
const configId = uuidv4()
|
|
58
|
+
|
|
59
|
+
const note = await this.noteService.createNote({
|
|
60
|
+
title: title || `new`,
|
|
61
|
+
enableDiscussions: false,
|
|
62
|
+
configId, // 传递 UUID 作为配置 ID(跨知识库唯一)
|
|
63
|
+
usedIndexes, // 传入已使用编号集合,避免重复扫描
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// 将新创建的笔记编号加入集合,确保后续创建不会冲突
|
|
67
|
+
const newIndex = parseInt(note.index, 10)
|
|
68
|
+
if (!isNaN(newIndex)) {
|
|
69
|
+
usedIndexes.add(newIndex)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createdNotes.push(note.dirName)
|
|
73
|
+
successCount++
|
|
74
|
+
|
|
75
|
+
if (count === 1) {
|
|
76
|
+
this.logger.success(`笔记创建成功: ${note.dirName}`)
|
|
77
|
+
this.logger.info(`笔记路径: ${note.path}`)
|
|
78
|
+
this.logger.info(`笔记编号: ${note.index}`)
|
|
79
|
+
this.logger.info(`配置ID: ${configId}`)
|
|
80
|
+
} else {
|
|
81
|
+
this.logger.success(`[${i}/${count}] 创建成功: ${note.dirName}`)
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
failCount++
|
|
85
|
+
this.logger.error(`[${i}/${count}] 创建失败`, error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 显示统计信息
|
|
90
|
+
if (count > 1) {
|
|
91
|
+
console.log('')
|
|
92
|
+
this.logger.info(
|
|
93
|
+
`📊 创建完成: 成功 ${successCount} 篇, 失败 ${failCount} 篇`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 自动更新索引文件(TOC.md、sidebar.json)
|
|
98
|
+
if (successCount > 0) {
|
|
99
|
+
this.logger.info('正在更新知识库索引...')
|
|
100
|
+
await this.readmeService.updateAllReadmes()
|
|
101
|
+
this.logger.success('知识库索引更新完成')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 提示用户输入要创建的笔记数量
|
|
107
|
+
*/
|
|
108
|
+
private async promptForCount(): Promise<number> {
|
|
109
|
+
const rl = createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stdout,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
rl.question('\n📝 请输入要创建的笔记数量(默认为 1): ', (answer) => {
|
|
116
|
+
rl.close()
|
|
117
|
+
|
|
118
|
+
// 解析输入
|
|
119
|
+
const trimmed = answer.trim()
|
|
120
|
+
|
|
121
|
+
// 如果输入为空,默认为 1
|
|
122
|
+
if (!trimmed) {
|
|
123
|
+
this.logger.info('使用默认数量: 1')
|
|
124
|
+
resolve(1)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 尝试解析为数字
|
|
129
|
+
const num = parseInt(trimmed, 10)
|
|
130
|
+
|
|
131
|
+
// 如果不是正整数,视为 1
|
|
132
|
+
if (isNaN(num) || num < 1 || !Number.isInteger(num)) {
|
|
133
|
+
this.logger.warn(`输入 "${trimmed}" 不是有效的正整数,使用默认值: 1`)
|
|
134
|
+
resolve(1)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.logger.info(`将创建 ${num} 篇笔记`)
|
|
139
|
+
resolve(num)
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 提示用户输入笔记标题
|
|
146
|
+
*/
|
|
147
|
+
private async promptForTitle(): Promise<string> {
|
|
148
|
+
const rl = createInterface({
|
|
149
|
+
input: process.stdin,
|
|
150
|
+
output: process.stdout,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
rl.question('请输入笔记标题: ', (answer) => {
|
|
155
|
+
rl.close()
|
|
156
|
+
resolve(answer.trim())
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/note/RenameNoteCommand.ts
|
|
3
|
+
*
|
|
4
|
+
* 重命名笔记命令 - 用于在开发环境中重命名笔记文件夹
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, renameSync, readFileSync, writeFileSync } from 'fs'
|
|
8
|
+
import { join } from 'path'
|
|
9
|
+
|
|
10
|
+
import { NOTES_PATH, REPO_NOTES_URL } from '../../config/constants'
|
|
11
|
+
import { ROOT_DIR_PATH } from '../../config/constants'
|
|
12
|
+
import { generateNoteTitle } from '../../config/templates'
|
|
13
|
+
import { NoteService, FileWatcherService } from '../../services'
|
|
14
|
+
import { reconcileTocFromFiles } from '../../services/reconcileToc'
|
|
15
|
+
import { validateNoteTitle } from '../../utils'
|
|
16
|
+
import { BaseCommand } from '../BaseCommand'
|
|
17
|
+
|
|
18
|
+
interface RenameNoteParams {
|
|
19
|
+
noteIndex: string
|
|
20
|
+
newTitle: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class RenameNoteCommand extends BaseCommand {
|
|
24
|
+
private noteService: NoteService
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super('rename-note')
|
|
28
|
+
this.noteService = NoteService.getInstance()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected async run(): Promise<void> {
|
|
32
|
+
// 从命令行参数读取
|
|
33
|
+
const noteIndex = process.env.NOTE_ID
|
|
34
|
+
const newTitle = process.env.NOTE_TITLE
|
|
35
|
+
|
|
36
|
+
if (!noteIndex || !newTitle) {
|
|
37
|
+
throw new Error('缺少 NOTE_ID 或 NOTE_TITLE 参数')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await this.renameNote({ noteIndex, newTitle })
|
|
42
|
+
this.logger.success(`笔记 ${noteIndex} 已重命名为: ${newTitle}`)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.logger.error('重命名失败', error)
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 重命名笔记(可被外部调用)
|
|
51
|
+
*/
|
|
52
|
+
async renameNote(params: RenameNoteParams): Promise<void> {
|
|
53
|
+
const { noteIndex, newTitle } = params
|
|
54
|
+
|
|
55
|
+
// 验证笔记是否存在
|
|
56
|
+
const note = this.noteService.getNoteByIndex(noteIndex)
|
|
57
|
+
if (!note) {
|
|
58
|
+
throw new Error(`笔记未找到: ${noteIndex}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 验证新标题
|
|
62
|
+
const validation = validateNoteTitle(newTitle)
|
|
63
|
+
if (!validation.valid) {
|
|
64
|
+
throw new Error(validation.error || '标题格式无效')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 构建新的文件夹名称
|
|
68
|
+
const newDirName = `${noteIndex}. ${newTitle.trim()}`
|
|
69
|
+
const newPath = join(NOTES_PATH, newDirName)
|
|
70
|
+
|
|
71
|
+
// 检查新路径是否已存在
|
|
72
|
+
if (existsSync(newPath)) {
|
|
73
|
+
throw new Error(`目标文件夹已存在: ${newDirName}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 重命名文件夹
|
|
77
|
+
// Windows 上 fs.watch 会锁住文件夹句柄,需要先挂起监听
|
|
78
|
+
const watcher = FileWatcherService.getInstance()
|
|
79
|
+
try {
|
|
80
|
+
if (watcher) watcher.suspend()
|
|
81
|
+
renameSync(note.path, newPath)
|
|
82
|
+
this.logger.info(`✅ 文件夹已重命名:`)
|
|
83
|
+
this.logger.info(` 原名称: ${note.dirName}`)
|
|
84
|
+
this.logger.info(` 新名称: ${newDirName}`)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`重命名文件夹失败: ${
|
|
88
|
+
error instanceof Error ? error.message : String(error)
|
|
89
|
+
}`,
|
|
90
|
+
)
|
|
91
|
+
} finally {
|
|
92
|
+
if (watcher) watcher.unsuspend()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 更新笔记内部的标题(README.md 第一行)
|
|
96
|
+
try {
|
|
97
|
+
this.logger.info('正在更新笔记内部标题...')
|
|
98
|
+
const readmePath = join(newPath, 'README.md')
|
|
99
|
+
|
|
100
|
+
if (existsSync(readmePath)) {
|
|
101
|
+
const content = readFileSync(readmePath, 'utf-8')
|
|
102
|
+
const lines = content.split('\n')
|
|
103
|
+
|
|
104
|
+
// 查找第一个一级标题
|
|
105
|
+
let h1Index = -1
|
|
106
|
+
for (let i = 0; i < lines.length; i++) {
|
|
107
|
+
if (lines[i].trim().startsWith('# ')) {
|
|
108
|
+
h1Index = i
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (h1Index !== -1) {
|
|
114
|
+
// 生成新的标题
|
|
115
|
+
const newH1 = generateNoteTitle(
|
|
116
|
+
noteIndex,
|
|
117
|
+
newTitle.trim(),
|
|
118
|
+
REPO_NOTES_URL,
|
|
119
|
+
)
|
|
120
|
+
lines[h1Index] = newH1
|
|
121
|
+
|
|
122
|
+
// 写回文件
|
|
123
|
+
writeFileSync(readmePath, lines.join('\n'), 'utf-8')
|
|
124
|
+
this.logger.success('✅ 笔记标题已更新')
|
|
125
|
+
} else {
|
|
126
|
+
this.logger.warn(
|
|
127
|
+
`⚠️ 笔记标题格式不符合规范,未找到一级标题,请手动检查修正: ${readmePath}`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.warn('⚠️ 更新笔记标题时出错:', error)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 重命名成功后,更新 TOC.md 和 sidebar.json
|
|
136
|
+
try {
|
|
137
|
+
this.logger.info('正在更新 TOC.md 和 sidebar.json...')
|
|
138
|
+
|
|
139
|
+
await reconcileTocFromFiles(ROOT_DIR_PATH)
|
|
140
|
+
|
|
141
|
+
this.logger.success('✅ 全局文件已更新')
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.logger.warn('⚠️ 文件夹重命名成功,但更新全局文件时出错:', error)
|
|
144
|
+
// 不抛出错误,因为主要任务(重命名)已完成
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/note/UpdateNoteConfigCommand.ts
|
|
3
|
+
*
|
|
4
|
+
* 更新笔记配置命令 - 用于在开发环境中更新笔记配置
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NoteService } from '../../services'
|
|
8
|
+
import { BaseCommand } from '../BaseCommand'
|
|
9
|
+
|
|
10
|
+
import type { NoteConfig } from '../../types'
|
|
11
|
+
|
|
12
|
+
interface UpdateConfigParams {
|
|
13
|
+
noteIndex: string
|
|
14
|
+
config: Partial<
|
|
15
|
+
Pick<NoteConfig, 'done' | 'enableDiscussions' | 'description'>
|
|
16
|
+
>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class UpdateNoteConfigCommand extends BaseCommand {
|
|
20
|
+
private noteService: NoteService
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super('update-note-config')
|
|
24
|
+
this.noteService = NoteService.getInstance()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected async run(): Promise<void> {
|
|
28
|
+
// 从命令行参数读取配置(用于 CLI 调用)
|
|
29
|
+
const noteIndex = process.env.NOTE_ID
|
|
30
|
+
const done = process.env.NOTE_DONE === 'true'
|
|
31
|
+
const enableDiscussions = process.env.NOTE_DISCUSSIONS === 'true'
|
|
32
|
+
const description = process.env.NOTE_DESCRIPTION || ''
|
|
33
|
+
|
|
34
|
+
if (!noteIndex) {
|
|
35
|
+
throw new Error('缺少 NOTE_ID 参数')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await this.updateConfig({
|
|
40
|
+
noteIndex,
|
|
41
|
+
config: {
|
|
42
|
+
done,
|
|
43
|
+
enableDiscussions,
|
|
44
|
+
description,
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.logger.success(`笔记 ${noteIndex} 配置已更新`)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.logger.error('更新配置失败', error)
|
|
51
|
+
throw error
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 更新笔记配置(可被外部调用)
|
|
57
|
+
*/
|
|
58
|
+
async updateConfig(params: UpdateConfigParams): Promise<void> {
|
|
59
|
+
const { noteIndex, config } = params
|
|
60
|
+
|
|
61
|
+
// 验证笔记是否存在
|
|
62
|
+
const note = this.noteService.getNoteByIndex(noteIndex)
|
|
63
|
+
if (!note) {
|
|
64
|
+
throw new Error(`笔记未找到: ${noteIndex}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 更新配置
|
|
68
|
+
await this.noteService.updateNoteConfig(noteIndex, config)
|
|
69
|
+
|
|
70
|
+
this.logger.info(`✅ 笔记 ${noteIndex} 配置已更新:`)
|
|
71
|
+
if (config.done !== undefined)
|
|
72
|
+
this.logger.info(` - 完成状态: ${config.done}`)
|
|
73
|
+
if (config.enableDiscussions !== undefined)
|
|
74
|
+
this.logger.info(` - 评论状态: ${config.enableDiscussions}`)
|
|
75
|
+
if (config.description !== undefined)
|
|
76
|
+
this.logger.info(` - 笔记简介: ${config.description || '(空)'}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/registry.ts
|
|
3
|
+
*
|
|
4
|
+
* 命令注册表 - 管理命令的注册、实例化和获取
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BuildCommand, PreviewCommand } from './build'
|
|
8
|
+
import { DevCommand } from './dev'
|
|
9
|
+
import { PushCommand, PullCommand } from './git'
|
|
10
|
+
import { InitSubRepoCommand } from './init-sub-repo'
|
|
11
|
+
import { HelpCommand } from './misc'
|
|
12
|
+
import { type Command, type CommandName } from './models'
|
|
13
|
+
import {
|
|
14
|
+
CreateNoteCommand,
|
|
15
|
+
UpdateNoteConfigCommand,
|
|
16
|
+
RenameNoteCommand,
|
|
17
|
+
} from './note'
|
|
18
|
+
import { UpdateCommand } from './update'
|
|
19
|
+
import { UpdateCompletedCountCommand } from './update-completed-count'
|
|
20
|
+
|
|
21
|
+
/** 命令注册表(懒加载) */
|
|
22
|
+
const commandFactories: Record<CommandName, () => Command> = {
|
|
23
|
+
'build': () => new BuildCommand(),
|
|
24
|
+
'create-notes': () => new CreateNoteCommand(),
|
|
25
|
+
'dev': () => new DevCommand(),
|
|
26
|
+
'init-sub-repo': () => new InitSubRepoCommand(),
|
|
27
|
+
'help': () => new HelpCommand(),
|
|
28
|
+
'preview': () => new PreviewCommand(),
|
|
29
|
+
'pull': () => new PullCommand(),
|
|
30
|
+
'push': () => new PushCommand(),
|
|
31
|
+
'rename-note': () => new RenameNoteCommand(),
|
|
32
|
+
'update-completed-count': () => new UpdateCompletedCountCommand(),
|
|
33
|
+
'update-note-config': () => new UpdateNoteConfigCommand(),
|
|
34
|
+
'update': () => new UpdateCommand(),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 命令实例缓存 */
|
|
38
|
+
const commandCache = new Map<CommandName, Command>()
|
|
39
|
+
|
|
40
|
+
/** 获取命令(懒加载,首次调用时才实例化) */
|
|
41
|
+
export function getCommand(name: CommandName): Command | undefined {
|
|
42
|
+
if (!commandFactories[name]) return undefined
|
|
43
|
+
|
|
44
|
+
if (!commandCache.has(name)) commandCache.set(name, commandFactories[name]())
|
|
45
|
+
|
|
46
|
+
return commandCache.get(name)
|
|
47
|
+
}
|