@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.
- 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/dist/markdown/index.cjs +6 -9
- package/dist/markdown/index.js +6 -9
- package/dist/vitepress/config/index.cjs +214 -58
- package/dist/vitepress/config/index.js +208 -52
- 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/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/configs/markdown.config.ts +170 -26
- 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/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,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,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,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
|
+
}
|