@tnotesjs/core 0.1.11 → 0.1.13

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.

Potentially problematic release.


This version of @tnotesjs/core might be problematic. Click here for more details.

package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # tnotesjs/core
1
+ # @tnotesjs/core
2
2
 
3
- TNotes 知识库系统的核心共享脚本,通过 Git Submodule 被所有 [TNotes.xxx](https://github.com/orgs/tnotesjs/repositories) 知识库引用。
3
+ TNotes 知识库系统的核心框架,以 NPM 包形式发布,被所有 [TNotes.xxx](https://github.com/orgs/tnotesjs/repositories) 知识库引用。
4
4
 
5
5
  ## 简介
6
6
 
7
- tnotesjs/core 包含了 TNotes 知识库系统的 CLI 命令、VitePress 主题/插件、服务层、工具函数等核心代码。各 TNotes.xxx 仓库不再各自拷贝脚本,而是统一以 Git Submodule 的形式引用本仓库,挂载到 `.vitepress/tnotes/` 路径下。
7
+ `@tnotesjs/core` 包含了 TNotes 知识库系统的 CLI 命令、VitePress 配置/主题、服务层、工具函数等核心代码。各 TNotes.xxx 仓库通过 `npm install @tnotesjs/core` 安装即可使用。
8
8
 
9
9
  ## 目录结构
10
10
 
11
11
  ```
12
- tnotesjs/core/
12
+ @tnotesjs/core
13
13
  ├── commands/ # CLI 命令(dev、build、push、update 等)
14
14
  ├── config/ # 配置管理(ConfigManager、默认配置、模板)
15
15
  ├── core/ # 核心模块(GitManager、NoteManager、ReadmeGenerator 等)
@@ -18,86 +18,95 @@ tnotesjs/core/
18
18
  ├── utils/ # 工具函数(日志、文件操作、Markdown 解析、校验等)
19
19
  ├── vitepress/ # VitePress 主题、组件、插件、样式
20
20
  │ ├── components/ # 自定义 Vue 组件
21
- │ ├── configs/ # VitePress 配置
21
+ │ ├── config/ # VitePress 配置(defineNotesConfig)
22
22
  │ ├── plugins/ # VitePress 插件
23
23
  │ └── theme/ # 主题入口与样式
24
- └── index.ts # 入口文件
24
+ ├── index.ts # CLI 入口
25
+ └── src/index.ts # 公共 API 导出
25
26
  ```
26
27
 
27
- ## 使用方式
28
-
29
- ### 在现有 TNotes.xxx 仓库中添加
28
+ ## 安装
30
29
 
31
30
  ```bash
32
- # 1. 删除旧的脚本目录
33
- rm -rf .vitepress/tnotes
34
-
35
- # 2. 添加 submodule
36
- git submodule add https://github.com/tnotesjs/TNotes.core.git .vitepress/tnotes
31
+ # 安装核心包
32
+ pnpm add @tnotesjs/core
37
33
 
38
- # 3. 提交变更
39
- git add .gitmodules .vitepress/tnotes
40
- git commit -m "chore: migrate to TNotes.core submodule"
34
+ # 安装 peerDependencies
35
+ pnpm add -D vite vitepress vue
41
36
  ```
42
37
 
43
- ### 克隆含 submodule 的仓库
38
+ ## 使用方式
44
39
 
45
- ```bash
46
- # 方式一:克隆时一并拉取
47
- git clone --recurse-submodules https://github.com/tnotesjs/TNotes.xxx.git
40
+ ### VitePress 配置
48
41
 
49
- # 方式二:克隆后手动初始化
50
- git clone https://github.com/tnotesjs/TNotes.xxx.git
51
- cd TNotes.xxx
52
- git submodule update --init
42
+ ```ts
43
+ // .vitepress/config.mts
44
+ import { defineNotesConfig } from '@tnotesjs/core/vitepress/config'
45
+ export default defineNotesConfig()
53
46
  ```
54
47
 
55
- ### 更新 submodule 到最新版本
48
+ ### VitePress 主题
56
49
 
57
- ```bash
58
- cd .vitepress/tnotes
59
- git pull origin main
60
- cd ../..
61
- git add .vitepress/tnotes
62
- git commit -m "chore: update TNotes.core"
50
+ ```ts
51
+ // .vitepress/theme/index.ts
52
+ export { default } from '@tnotesjs/core/vitepress/theme'
63
53
  ```
64
54
 
65
- ## 开发工作流
55
+ ### CLI 命令
56
+
57
+ 在 `package.json` 中配置脚本:
58
+
59
+ ```json
60
+ {
61
+ "scripts": {
62
+ "tn:dev": "tnotes --dev",
63
+ "tn:build": "tnotes --build",
64
+ "tn:preview": "tnotes --preview",
65
+ "tn:update": "tnotes --update",
66
+ "tn:push": "tnotes --push",
67
+ "tn:pull": "tnotes --pull",
68
+ "tn:create-notes": "tnotes --create-notes",
69
+ "tn:fix-timestamps": "tnotes --fix-timestamps",
70
+ "tn:help": "tnotes --help"
71
+ }
72
+ }
73
+ ```
66
74
 
67
- 你可以在任意 TNotes.xxx 仓库中直接编辑 `.vitepress/tnotes/` 下的文件,改动会即时生效(VitePress HMR)。编辑完成后:
75
+ ## 开发工作流
68
76
 
69
77
  ```bash
70
- # 1. submodule 内提交并推送
71
- cd .vitepress/tnotes
72
- git add -A && git commit -m "feat: your change" && git push
78
+ # 克隆 core 仓库
79
+ git clone https://github.com/tnotesjs/core.git
80
+ cd core
81
+ pnpm install
73
82
 
74
- # 2. 回到父仓库,更新 submodule 指针
75
- cd ../..
76
- git add .vitepress/tnotes && git commit -m "chore: update TNotes.core"
77
- ```
83
+ # 构建
84
+ pnpm build
78
85
 
79
- 其它 TNotes.xxx 仓库要同步这次改动:
86
+ # 类型检查
87
+ pnpm build:check
80
88
 
81
- ```bash
82
- cd .vitepress/tnotes && git pull origin main && cd ../..
83
- git add .vitepress/tnotes && git commit -m "chore: update TNotes.core"
89
+ # 本地调试(在宿主仓库中 link)
90
+ pnpm link --global
91
+ cd ../TNotes.xxx
92
+ pnpm link --global @tnotesjs/core
84
93
  ```
85
94
 
86
95
  ## CI/CD
87
96
 
88
- 各仓库的 GitHub Actions 部署工作流(`deploy.yml`)需要在 checkout 步骤中启用 submodule 拉取:
97
+ 各仓库的 GitHub Actions 部署工作流(`deploy.yml`)只需标准 checkout,无需额外配置:
89
98
 
90
99
  ```yaml
91
100
  - uses: actions/checkout@v4
92
101
  with:
93
102
  fetch-depth: 0
94
- submodules: true
95
103
  ```
96
104
 
97
105
  ## 版本管理
98
106
 
99
107
  - 版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)
100
108
  - 变更记录见 [CHANGELOG.md](./CHANGELOG.md)
109
+ - 发布:`npm publish --access public`
101
110
  - 每个版本通过 Git Tag 标记(如 `v1.0.0`)
102
111
 
103
112
  ## 许可证
@@ -3195,7 +3195,6 @@ var NoteService = class _NoteService {
3195
3195
  const readmeContent = noteTitle + "\n" + NEW_NOTES_README_MD_TEMPLATE;
3196
3196
  writeFileSync4(readmePath, readmeContent, "utf-8");
3197
3197
  const configPath = join7(notePath, ".tnotes.json");
3198
- const now = Date.now();
3199
3198
  const config2 = {
3200
3199
  id: configId || uuidv4(),
3201
3200
  // 配置 ID 使用 UUID(跨知识库唯一)
@@ -3204,9 +3203,8 @@ var NoteService = class _NoteService {
3204
3203
  yuque: [],
3205
3204
  done: false,
3206
3205
  category,
3207
- enableDiscussions,
3208
- created_at: now,
3209
- updated_at: now
3206
+ enableDiscussions
3207
+ // created_at / updated_at 由 tn:push 时 fix-timestamps 自动写入
3210
3208
  };
3211
3209
  this.noteManager.writeNoteConfig(configPath, config2);
3212
3210
  logger.info(`Created new note: ${dirName}`);
@@ -3259,8 +3257,7 @@ var NoteService = class _NoteService {
3259
3257
  const oldConfig = { ...note.config };
3260
3258
  const updatedConfig = {
3261
3259
  ...note.config,
3262
- ...updates,
3263
- updated_at: Date.now()
3260
+ ...updates
3264
3261
  };
3265
3262
  this.ignoreNextConfigChange(note.configPath);
3266
3263
  this.noteManager.updateNoteConfig(note, updatedConfig);
@@ -3621,6 +3618,70 @@ var TimestampService = class {
3621
3618
  constructor() {
3622
3619
  this.noteManager = NoteManager.getInstance();
3623
3620
  }
3621
+ /**
3622
+ * 批量获取所有笔记 README.md 的 git 时间戳
3623
+ * 只执行 2 次 git log,构建 filePath → {created_at, updated_at} 的映射
3624
+ *
3625
+ * - 第 1 次:git log --name-only --format=... --diff-filter=A
3626
+ * 获取每个文件的首次提交时间(created_at)
3627
+ * - 第 2 次:git log --name-only --format=...
3628
+ * 获取每个文件的最后修改时间(updated_at)
3629
+ */
3630
+ buildGitTimestampMap() {
3631
+ const createdMap = /* @__PURE__ */ new Map();
3632
+ const updatedMap = /* @__PURE__ */ new Map();
3633
+ try {
3634
+ const createdRaw = execSync3(
3635
+ `git log --name-only --format="---COMMIT---%ct" --diff-filter=A -- "notes/*/README.md"`,
3636
+ {
3637
+ cwd: ROOT_DIR_PATH,
3638
+ encoding: "utf-8",
3639
+ maxBuffer: 100 * 1024 * 1024,
3640
+ stdio: ["pipe", "pipe", "ignore"]
3641
+ }
3642
+ );
3643
+ let currentTimestamp = 0;
3644
+ for (const line of createdRaw.split(/\r?\n/)) {
3645
+ if (line.startsWith("---COMMIT---")) {
3646
+ currentTimestamp = parseInt(line.slice(12)) * 1e3;
3647
+ } else if (line.trim() && currentTimestamp) {
3648
+ createdMap.set(line.trim(), currentTimestamp);
3649
+ }
3650
+ }
3651
+ const updatedRaw = execSync3(
3652
+ `git log --name-only --format="---COMMIT---%ct" -- "notes/*/README.md"`,
3653
+ {
3654
+ cwd: ROOT_DIR_PATH,
3655
+ encoding: "utf-8",
3656
+ maxBuffer: 100 * 1024 * 1024,
3657
+ stdio: ["pipe", "pipe", "ignore"]
3658
+ }
3659
+ );
3660
+ currentTimestamp = 0;
3661
+ for (const line of updatedRaw.split(/\r?\n/)) {
3662
+ if (line.startsWith("---COMMIT---")) {
3663
+ currentTimestamp = parseInt(line.slice(12)) * 1e3;
3664
+ } else if (line.trim() && currentTimestamp) {
3665
+ if (!updatedMap.has(line.trim())) {
3666
+ updatedMap.set(line.trim(), currentTimestamp);
3667
+ }
3668
+ }
3669
+ }
3670
+ } catch (error) {
3671
+ logger.error("\u6279\u91CF\u83B7\u53D6 git \u65F6\u95F4\u6233\u5931\u8D25", error);
3672
+ }
3673
+ const result = /* @__PURE__ */ new Map();
3674
+ for (const [file, created_at] of createdMap) {
3675
+ const updated_at = updatedMap.get(file) ?? created_at;
3676
+ result.set(file, { created_at, updated_at });
3677
+ }
3678
+ for (const [file, updated_at] of updatedMap) {
3679
+ if (!result.has(file)) {
3680
+ result.set(file, { created_at: updated_at, updated_at });
3681
+ }
3682
+ }
3683
+ return result;
3684
+ }
3624
3685
  /**
3625
3686
  * 从 git 获取文件的创建时间和最后修改时间
3626
3687
  * @param noteDirPath - 笔记目录路径
@@ -3662,9 +3723,10 @@ var TimestampService = class {
3662
3723
  * 修复单个笔记的时间戳
3663
3724
  * @param noteDir - 笔记目录名
3664
3725
  * @param forceUpdate - 是否强制更新(忽略现有值)
3726
+ * @param timestampMap - 预构建的时间戳映射(批量模式传入,避免逐个 git log)
3665
3727
  * @returns 是否进行了修复
3666
3728
  */
3667
- fixNoteTimestamps(noteDir, forceUpdate = false) {
3729
+ fixNoteTimestamps(noteDir, forceUpdate = false, timestampMap) {
3668
3730
  const configPath = join8(NOTES_DIR_PATH, noteDir, ".tnotes.json");
3669
3731
  if (!existsSync7(configPath)) {
3670
3732
  return false;
@@ -3672,8 +3734,8 @@ var TimestampService = class {
3672
3734
  try {
3673
3735
  const configContent = readFileSync6(configPath, "utf-8");
3674
3736
  const config2 = JSON.parse(configContent);
3675
- const noteDirPath = join8(NOTES_DIR_PATH, noteDir);
3676
- const timestamps = this.getGitTimestamps(noteDirPath);
3737
+ const readmeRelPath = `notes/${noteDir}/README.md`;
3738
+ const timestamps = timestampMap ? timestampMap.get(readmeRelPath) ?? null : this.getGitTimestamps(join8(NOTES_DIR_PATH, noteDir));
3677
3739
  if (!timestamps) {
3678
3740
  return false;
3679
3741
  }
@@ -3771,6 +3833,8 @@ var TimestampService = class {
3771
3833
  if (rootConfigFixed) {
3772
3834
  logger.success("\u2705 \u6839\u914D\u7F6E\u6587\u4EF6\u65F6\u95F4\u6233\u5DF2\u4FEE\u590D");
3773
3835
  }
3836
+ const timestampMap = this.buildGitTimestampMap();
3837
+ logger.info(`\u5DF2\u4ECE git \u5386\u53F2\u4E2D\u63D0\u53D6 ${timestampMap.size} \u4E2A\u6587\u4EF6\u7684\u65F6\u95F4\u6233\u4FE1\u606F`);
3774
3838
  if (!existsSync7(NOTES_DIR_PATH)) {
3775
3839
  logger.error("notes \u76EE\u5F55\u4E0D\u5B58\u5728");
3776
3840
  return { fixed: 0, skipped: 0, total: 0, rootConfigFixed };
@@ -3782,7 +3846,7 @@ var TimestampService = class {
3782
3846
  let fixedCount = 0;
3783
3847
  let skippedCount = 0;
3784
3848
  for (const noteDir of noteDirs) {
3785
- const fixed = this.fixNoteTimestamps(noteDir, forceUpdate);
3849
+ const fixed = this.fixNoteTimestamps(noteDir, forceUpdate, timestampMap);
3786
3850
  if (fixed) {
3787
3851
  fixedCount++;
3788
3852
  } else {
@@ -4210,6 +4274,7 @@ export {
4210
4274
  ROOT_DIR_PATH,
4211
4275
  ROOT_CONFIG_PATH,
4212
4276
  NoteManager,
4277
+ runCommand,
4213
4278
  BaseCommand,
4214
4279
  NoteIndexCache,
4215
4280
  ReadmeService,
package/dist/cli/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import {
2
3
  BaseCommand,
3
4
  COMMAND_DESCRIPTIONS,
@@ -19,8 +20,9 @@ import {
19
20
  handleError,
20
21
  logger,
21
22
  parseArgs,
22
- parseReadmeCompletedNotes
23
- } from "../chunk-4WE4NND3.js";
23
+ parseReadmeCompletedNotes,
24
+ runCommand
25
+ } from "../chunk-CSSIRO3I.js";
24
26
  import {
25
27
  ConfigManager
26
28
  } from "../chunk-NASIL5FY.js";
@@ -97,11 +99,9 @@ var UpdateCommand = class extends BaseCommand {
97
99
  ...config.root_item.completed_notes_count || {},
98
100
  [currentKey]: completedCount
99
101
  };
100
- const updatedAt = Date.now();
101
102
  config.root_item = {
102
103
  ...config.root_item,
103
- completed_notes_count: completedNotesCount,
104
- updated_at: updatedAt
104
+ completed_notes_count: completedNotesCount
105
105
  };
106
106
  delete config.root_item.completed_notes_count_last_month;
107
107
  writeFileSync(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
@@ -140,11 +140,12 @@ var UpdateCompletedCountCommand = class extends BaseCommand {
140
140
  const configContent = readFileSync2(ROOT_CONFIG_PATH, "utf-8");
141
141
  const config = JSON.parse(configContent);
142
142
  this.logger.info("\u5F00\u59CB\u66F4\u65B0\u5B8C\u6210\u7B14\u8BB0\u6570\u91CF\u5386\u53F2\u8BB0\u5F55...");
143
- const completedNotesCountHistory = await this.getCompletedNotesCountHistory(config.root_item.created_at);
143
+ const completedNotesCountHistory = await this.getCompletedNotesCountHistory(
144
+ config.root_item.created_at ?? Date.now()
145
+ );
144
146
  config.root_item = {
145
147
  ...config.root_item,
146
- completed_notes_count: completedNotesCountHistory,
147
- updated_at: Date.now()
148
+ completed_notes_count: completedNotesCountHistory
148
149
  };
149
150
  writeFileSync2(ROOT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
150
151
  const duration = Date.now() - startTime;
@@ -277,29 +278,35 @@ var PushCommand = class extends BaseCommand {
277
278
  this.logger.info("\u6CA1\u6709\u66F4\u6539\u9700\u8981\u63A8\u9001");
278
279
  return;
279
280
  }
280
- if (hasPendingCommits && !status.hasChanges) {
281
- this.logger.info(`\u68C0\u6D4B\u5230 ${status.ahead} \u4E2A\u672A\u63A8\u9001\u7684\u63D0\u4EA4\uFF0C\u76F4\u63A5\u63A8\u9001...`);
281
+ const force = this.options.force === true;
282
+ if (force) {
283
+ this.logger.warn("\u4F7F\u7528\u5F3A\u5236\u63A8\u9001\u6A21\u5F0F (--force)");
282
284
  }
283
285
  if (status.hasChanges) {
284
- const changedFiles = status.files.map((f) => f.path);
285
- this.logger.info(`\u5171\u8BA1\u68C0\u6D4B\u5230 ${changedFiles.length} \u4E2A\u53D8\u66F4\u6587\u4EF6`);
286
- const changedNotes = this.timestampService.getChangedNotes(changedFiles);
287
286
  this.logger.info(
288
- `\u5DE5\u5177\u68C0\u6D4B\u5230 ${changedNotes.length} \u4E2A\u53D8\u66F4\u7B14\u8BB0\uFF08README.md\uFF09`
287
+ `\u68C0\u6D4B\u5230 ${status.files.length} \u4E2A\u53D8\u66F4\u6587\u4EF6\uFF0C\u6B63\u5728\u63D0\u4EA4...`
288
+ );
289
+ const commitMessage = this.gitService.generateCommitMessage();
290
+ await runCommand("git add -A", ROOT_DIR_PATH);
291
+ await runCommand(`git commit -m "${commitMessage}"`, ROOT_DIR_PATH);
292
+ this.logger.info("\u4FEE\u590D\u7B14\u8BB0\u65F6\u95F4\u6233...");
293
+ await this.timestampService.fixAllTimestamps(false);
294
+ const afterFixStatus = await runCommand(
295
+ "git -c core.quotePath=false status --porcelain",
296
+ ROOT_DIR_PATH
289
297
  );
290
- if (changedNotes.length > 0) {
291
- this.logger.info(
292
- `\u68C0\u6D4B\u5230 ${changedNotes.length} \u7BC7\u7B14\u8BB0\u7684 README.md \u6709\u53D8\u66F4\uFF0C\u66F4\u65B0\u65F6\u95F4\u6233...`
293
- );
294
- await this.timestampService.updateNotesTimestamp(changedNotes);
298
+ const hasTimestampChanges = afterFixStatus.split(/\r?\n/).filter(Boolean).some((line) => line.includes(".tnotes.json"));
299
+ if (hasTimestampChanges) {
300
+ this.logger.info("\u5C06\u65F6\u95F4\u6233\u53D8\u66F4\u5408\u5E76\u5230 commit...");
301
+ await runCommand("git add -u", ROOT_DIR_PATH);
302
+ await runCommand("git commit --amend --no-edit", ROOT_DIR_PATH);
295
303
  }
296
- }
297
- const force = this.options.force === true;
298
- if (force) {
299
- this.logger.warn("\u4F7F\u7528\u5F3A\u5236\u63A8\u9001\u6A21\u5F0F (--force)");
304
+ } else {
305
+ this.logger.info(`\u68C0\u6D4B\u5230 ${status.ahead} \u4E2A\u672A\u63A8\u9001\u7684\u63D0\u4EA4\uFF0C\u76F4\u63A5\u63A8\u9001...`);
300
306
  }
301
307
  this.logger.info("\u6B63\u5728\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93...");
302
- await this.gitService.quickPush({ force, skipCheck: true });
308
+ const pushCmd = force ? "git push --force" : "git push";
309
+ await runCommand(pushCmd, ROOT_DIR_PATH);
303
310
  this.logger.success("\u63A8\u9001\u5B8C\u6210");
304
311
  } catch (error) {
305
312
  this.logger.error("\u63A8\u9001\u5931\u8D25:", error);
@@ -4,7 +4,7 @@ import {
4
4
  UpdateNoteConfigCommand,
5
5
  generateAnchor,
6
6
  logger
7
- } from "../../chunk-4WE4NND3.js";
7
+ } from "../../chunk-CSSIRO3I.js";
8
8
  import "../../chunk-NASIL5FY.js";
9
9
 
10
10
  // vitepress/config/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tnotesjs/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "TNotes 知识库核心框架 —— 基于 VitePress 的笔记管理系统",
5
5
  "type": "module",
6
6
  "bin": {
package/types/config.ts CHANGED
@@ -55,7 +55,7 @@ export interface RootItem {
55
55
  completed_notes_count: Record<string, number>
56
56
  details: string
57
57
  link: string
58
- created_at: number
59
- updated_at: number
60
- days_since_birth: number
58
+ created_at?: number
59
+ updated_at?: number
60
+ days_since_birth?: number
61
61
  }
package/types/note.ts CHANGED
@@ -16,8 +16,8 @@ export interface NoteConfig {
16
16
  category?: string
17
17
  enableDiscussions: boolean
18
18
  description?: string // 笔记简介(一句话描述)
19
- created_at: number
20
- updated_at: number
19
+ created_at?: number
20
+ updated_at?: number
21
21
  }
22
22
 
23
23
  /**