@ruan-cat/vitepress-preset-config 2.5.0 → 2.6.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/dist/config.d.mts CHANGED
@@ -5,7 +5,6 @@ import { generateSidebar } from 'vitepress-sidebar';
5
5
  import llmstxt from 'vitepress-plugin-llms';
6
6
  import { GitChangelog, GitChangelogMarkdownSectionOptions } from '@nolebase/vitepress-plugin-git-changelog/vite';
7
7
  import { defineTeekConfig } from 'vitepress-theme-teek/config';
8
- export { addChangelog2doc, copyChangelogMd, copyClaudeAgents, copyReadmeMd } from '@ruan-cat/utils/node-esm';
9
8
 
10
9
  type LlmstxtSettings = NonNullable<Parameters<typeof llmstxt>[0]>;
11
10
  type GitChangelogOptions = NonNullable<Parameters<typeof GitChangelog>[0]>;
@@ -45,6 +44,98 @@ interface ExtraConfig {
45
44
  teekConfig?: TeekConfigOptions;
46
45
  }
47
46
 
47
+ /**
48
+ * 将 README.md 文件移动到指定要求的位置内,并重命名为 index.md
49
+ * @description
50
+ * 该函数相当于实现 `cpx README.md docs` 命令
51
+ */
52
+ declare function copyReadmeMd(/** 目标文件夹 */ target: string): void;
53
+
54
+ /**
55
+ * 将 .claude/agents 文件夹复制到指定位置的配置选项
56
+ */
57
+ interface CopyClaudeAgentsOptions {
58
+ /**
59
+ * 目标文件夹路径(必须是相对路径,相对于当前工作目录)
60
+ * @description
61
+ * **重要**:此参数仅接受相对路径,不接受绝对路径(禁止以 `/` 或盘符如 `C:\` 开头)。
62
+ * 使用绝对路径会抛出错误,这是为了防止意外覆盖系统目录。
63
+ *
64
+ * 该地址是写相对路径的 不能写绝对路径,容易导致意外。
65
+ * vitepress 命令运行在 apps/admin 目录内,该地址是相对于该运行目录的。
66
+ * 比如期望将 `.claude/agents` 复制到 `apps/admin/src/docs/prompts/agents` 文件夹。
67
+ * 则写 `src/docs/prompts/agents` 即可。
68
+ *
69
+ * @throws {Error} 当传入绝对路径时抛出错误
70
+ * @example
71
+ * // ✅ 正确:相对路径
72
+ * "src/docs/prompts/agents"
73
+ * "dist/agents"
74
+ * "./public/claude"
75
+ *
76
+ * @example
77
+ * // ❌ 错误:绝对路径(会抛出错误)
78
+ * "/var/www/agents" // Unix 绝对路径
79
+ * "C:\\Users\\agents" // Windows 绝对路径
80
+ */
81
+ target: string;
82
+ /**
83
+ * 可选的根目录路径,支持相对路径(如 `../../../` 表示向上三级目录)
84
+ * @description
85
+ * - 如果不传入,将自动向上查找包含 pnpm-workspace.yaml 的 monorepo 根目录
86
+ * - 相对路径会基于当前工作目录 (process.cwd()) 解析为绝对路径
87
+ * - 绝对路径将直接使用
88
+ * @example
89
+ * // 相对路径:向上三级目录
90
+ * '../../../'
91
+ *
92
+ * // 绝对路径
93
+ * '/absolute/path/to/monorepo'
94
+ */
95
+ rootDir?: string;
96
+ }
97
+ /**
98
+ * 将 .claude/agents 文件夹复制到指定位置
99
+ * @param options - 配置选项
100
+ * @throws {Error} 当 `options.target` 为绝对路径时抛出错误
101
+ * @description
102
+ * 该函数相当于实现 `cpx .claude/agents <target>` 命令。
103
+ * 从根目录的 .claude/agents 复制到目标位置。
104
+ *
105
+ * **安全限制**:`target` 参数必须是相对路径,禁止使用绝对路径,以防止意外覆盖系统目录。
106
+ *
107
+ * @example
108
+ * // ✅ 自动检测 monorepo 根目录,复制到当前目录的 dist 文件夹
109
+ * copyClaudeAgents({ target: 'dist' })
110
+ *
111
+ * // ✅ 手动指定根目录为向上三级,复制到 build 文件夹
112
+ * copyClaudeAgents({
113
+ * target: 'build',
114
+ * rootDir: '../../../'
115
+ * })
116
+ *
117
+ * // ✅ 使用绝对路径指定根目录(rootDir 允许绝对路径)
118
+ * copyClaudeAgents({
119
+ * target: 'dist', // target 必须是相对路径
120
+ * rootDir: '/absolute/path/to/monorepo' // rootDir 可以是绝对路径
121
+ * })
122
+ *
123
+ * @example
124
+ * // ❌ 错误用法:target 使用绝对路径会抛出错误
125
+ * copyClaudeAgents({ target: '/var/www/agents' }) // 抛出 Error
126
+ * copyClaudeAgents({ target: 'C:\\Windows\\agents' }) // 抛出 Error
127
+ */
128
+ declare function copyClaudeAgents(options: CopyClaudeAgentsOptions): void;
129
+
130
+ interface AddChangelog2docOptions<T = Record<string, any>> {
131
+ /** 目标文件夹 */
132
+ target: string;
133
+ /** 被插入到md头部的数据 */
134
+ data: T;
135
+ }
136
+ /** 将变更日志添加到指定的文档目录内 并提供参数 */
137
+ declare function addChangelog2doc<T>(options: AddChangelog2docOptions<T>): void;
138
+
48
139
  type VitePressSidebarOptions = Parameters<typeof generateSidebar>[0];
49
140
  /**
50
141
  * 设置自动生成侧边栏的配置
@@ -54,4 +145,4 @@ declare function setGenerateSidebar(options?: VitePressSidebarOptions): vitepres
54
145
  /** 设置vitepress主配置 */
55
146
  declare function setUserConfig(config?: UserConfig<DefaultTheme.Config>, extraConfig?: ExtraConfig): UserConfig<DefaultTheme.Config>;
56
147
 
57
- export { setGenerateSidebar, setUserConfig };
148
+ export { addChangelog2doc, copyClaudeAgents, copyReadmeMd, setGenerateSidebar, setUserConfig };
package/dist/config.mjs CHANGED
@@ -2,17 +2,147 @@
2
2
  import { defineConfig } from "vitepress";
3
3
  import { generateSidebar } from "vitepress-sidebar";
4
4
  import { vitepressDemoPlugin } from "vitepress-demo-plugin";
5
- import { merge as merge2, isUndefined, cloneDeep as cloneDeep2 } from "lodash-es";
5
+ import { merge as merge2, isUndefined as isUndefined2, cloneDeep as cloneDeep2 } from "lodash-es";
6
+
7
+ // src/config/copy-readme.ts
8
+ import fs from "fs";
9
+ import path from "path";
6
10
  import consola from "consola";
7
- import {
8
- addChangelog2doc,
9
- hasChangelogMd,
10
- copyReadmeMd,
11
- copyClaudeAgents,
12
- copyChangelogMd
13
- } from "@ruan-cat/utils/node-esm";
14
- import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
15
- import { copyOrDownloadAsMarkdownButtons } from "vitepress-plugin-llms";
11
+ import { isConditionsSome } from "@ruan-cat/utils";
12
+ var capitalReadmeMd = "README.md";
13
+ var lowerCaseReadmeMd = "readme.md";
14
+ function hasCapitalReadmeMd() {
15
+ const res = fs.existsSync(path.resolve(process.cwd(), capitalReadmeMd));
16
+ if (!res) {
17
+ consola.warn(`\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E0D\u5B58\u5728 ${capitalReadmeMd} \u6587\u4EF6`);
18
+ }
19
+ return res;
20
+ }
21
+ function hasLowerCaseReadmeMd() {
22
+ const res = fs.existsSync(path.resolve(process.cwd(), lowerCaseReadmeMd));
23
+ if (!res) {
24
+ consola.log(`\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E0D\u5B58\u5728 ${lowerCaseReadmeMd} \u6587\u4EF6`);
25
+ }
26
+ return res;
27
+ }
28
+ function hasReadmeMd() {
29
+ const res = isConditionsSome([() => hasCapitalReadmeMd(), () => hasCapitalReadmeMd()]);
30
+ return res;
31
+ }
32
+ function copyReadmeMd(target) {
33
+ if (!hasReadmeMd()) {
34
+ return;
35
+ }
36
+ let readmeFileName = capitalReadmeMd;
37
+ if (hasCapitalReadmeMd()) {
38
+ readmeFileName = capitalReadmeMd;
39
+ } else if (hasLowerCaseReadmeMd()) {
40
+ readmeFileName = lowerCaseReadmeMd;
41
+ }
42
+ const source = path.resolve(process.cwd(), readmeFileName);
43
+ const destination = path.resolve(process.cwd(), target, "index.md");
44
+ fs.copyFileSync(source, destination);
45
+ }
46
+
47
+ // src/config/copy-claude-agents.ts
48
+ import fs2 from "fs";
49
+ import path2 from "path";
50
+ import consola2 from "consola";
51
+ function findMonorepoRoot() {
52
+ let currentDir = process.cwd();
53
+ const root = path2.parse(currentDir).root;
54
+ while (currentDir !== root) {
55
+ const workspaceFile2 = path2.join(currentDir, "pnpm-workspace.yaml");
56
+ if (fs2.existsSync(workspaceFile2)) {
57
+ return currentDir;
58
+ }
59
+ currentDir = path2.dirname(currentDir);
60
+ }
61
+ const workspaceFile = path2.join(root, "pnpm-workspace.yaml");
62
+ if (fs2.existsSync(workspaceFile)) {
63
+ return root;
64
+ }
65
+ return null;
66
+ }
67
+ function resolveRootDir(rootDir) {
68
+ if (rootDir) {
69
+ return path2.resolve(process.cwd(), rootDir);
70
+ }
71
+ const monorepoRoot = findMonorepoRoot();
72
+ if (monorepoRoot) {
73
+ return monorepoRoot;
74
+ }
75
+ return process.cwd();
76
+ }
77
+ function hasClaudeAgents(options) {
78
+ const root = resolveRootDir(options == null ? void 0 : options.rootDir);
79
+ const claudeAgentsPath = path2.join(root, ".claude/agents");
80
+ const exists = fs2.existsSync(claudeAgentsPath);
81
+ if (!exists) {
82
+ consola2.log("\u68C0\u6D4B\u7684\u6839\u76EE\u5F55\u4E3A\uFF1A", root);
83
+ consola2.warn("\u8BE5\u6839\u76EE\u5F55\u4E0D\u5B58\u5728 .claude/agents \u6587\u4EF6\u5939");
84
+ }
85
+ return exists;
86
+ }
87
+ function copyClaudeAgents(options) {
88
+ if (path2.isAbsolute(options.target)) {
89
+ const errorMessage = [
90
+ `target \u53C2\u6570\u4E0D\u5141\u8BB8\u4F7F\u7528\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u8FD9\u53EF\u80FD\u5BFC\u81F4\u610F\u5916\u7684\u6587\u4EF6\u8986\u76D6\u3002`,
91
+ `\u5F53\u524D\u4F20\u5165\u7684\u8DEF\u5F84: "${options.target}"`,
92
+ `\u8BF7\u4F7F\u7528\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982: "src/docs/prompts/agents"`
93
+ ].join("\n");
94
+ consola2.error(errorMessage);
95
+ throw new Error(errorMessage);
96
+ }
97
+ if (!hasClaudeAgents({ rootDir: options.rootDir })) {
98
+ return;
99
+ }
100
+ const root = resolveRootDir(options.rootDir);
101
+ const source = path2.join(root, ".claude/agents");
102
+ const destination = path2.resolve(process.cwd(), options.target);
103
+ fs2.mkdirSync(path2.dirname(destination), { recursive: true });
104
+ fs2.cpSync(source, destination, { recursive: true });
105
+ consola2.success(`\u5DF2\u6210\u529F\u590D\u5236 .claude/agents \u5230 ${destination}`);
106
+ }
107
+
108
+ // src/config/add-changelog-to-doc.ts
109
+ import path4 from "path";
110
+
111
+ // src/utils/copy-changelog.ts
112
+ import fs3 from "fs";
113
+ import path3 from "path";
114
+ import consola3 from "consola";
115
+ function hasChangelogMd() {
116
+ const res = fs3.existsSync(path3.resolve(process.cwd(), "CHANGELOG.md"));
117
+ if (!res) {
118
+ consola3.log("\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E3A\uFF1A", process.cwd());
119
+ consola3.warn("\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E0D\u5B58\u5728 CHANGELOG.md \u6587\u4EF6");
120
+ }
121
+ return res;
122
+ }
123
+ function copyChangelogMd(target) {
124
+ if (!hasChangelogMd()) {
125
+ return;
126
+ }
127
+ const source = path3.resolve(process.cwd(), "CHANGELOG.md");
128
+ const destination = path3.resolve(process.cwd(), target, "CHANGELOG.md");
129
+ fs3.copyFileSync(source, destination);
130
+ }
131
+
132
+ // src/config/add-changelog-to-doc.ts
133
+ import { writeYaml2md } from "@ruan-cat/utils/node-esm";
134
+ function addChangelog2doc(options) {
135
+ const { data, target } = options;
136
+ if (!hasChangelogMd()) {
137
+ return;
138
+ }
139
+ copyChangelogMd(target);
140
+ const mdPath = path4.resolve(process.cwd(), target, "CHANGELOG.md");
141
+ writeYaml2md({
142
+ mdPath,
143
+ data
144
+ });
145
+ }
16
146
 
17
147
  // src/config/plugins.ts
18
148
  import vitepressPluginLlmstxt from "vitepress-plugin-llms";
@@ -48,6 +178,23 @@ function handlePlugins(userConfig, extraConfig) {
48
178
  userConfig.vite.plugins = getPlugins(extraConfig);
49
179
  }
50
180
 
181
+ // src/config/changelog-nav.ts
182
+ import consola4 from "consola";
183
+ import { isUndefined } from "lodash-es";
184
+ function handleChangeLog(userConfig) {
185
+ var _a;
186
+ if (!hasChangelogMd()) {
187
+ consola4.warn(` \u672A\u627E\u5230\u53D8\u66F4\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u53D8\u66F4\u65E5\u5FD7\u5BFC\u822A\u680F\u3002 `);
188
+ return;
189
+ }
190
+ const nav = (_a = userConfig == null ? void 0 : userConfig.themeConfig) == null ? void 0 : _a.nav;
191
+ if (isUndefined(nav)) {
192
+ consola4.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
193
+ throw new Error(` nav \u9ED8\u8BA4\u63D0\u4F9B\u7684\u5BFC\u822A\u680F\u914D\u7F6E\u4E3A\u7A7A\u3002\u4E0D\u7B26\u5408\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u8BF7\u68C0\u67E5\u3002 `);
194
+ }
195
+ nav.push({ text: "\u66F4\u65B0\u65E5\u5FD7", link: "/CHANGELOG.md" });
196
+ }
197
+
51
198
  // src/config/teek.ts
52
199
  import { defineTeekConfig } from "vitepress-theme-teek/config";
53
200
  import { merge, cloneDeep } from "lodash-es";
@@ -117,6 +264,8 @@ function handleTeekConfig(userConfig, extraConfig) {
117
264
  }
118
265
 
119
266
  // src/config.mts
267
+ import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
268
+ import { copyOrDownloadAsMarkdownButtons } from "vitepress-plugin-llms";
120
269
  import { MermaidPlugin } from "@leelaa/vitepress-plugin-extended";
121
270
  import { defineTeekConfig as defineTeekConfig2 } from "vitepress-theme-teek/config";
122
271
  var defaultSidebarOptions = {
@@ -145,7 +294,7 @@ var defaultSidebarOptions = {
145
294
  debugPrint: false
146
295
  };
147
296
  function getMergeSidebarOptions(options) {
148
- return merge2({}, cloneDeep2(defaultSidebarOptions), isUndefined(options) ? {} : options);
297
+ return merge2({}, cloneDeep2(defaultSidebarOptions), isUndefined2(options) ? {} : options);
149
298
  }
150
299
  function setGenerateSidebar(options) {
151
300
  return generateSidebar(getMergeSidebarOptions(options));
@@ -174,7 +323,7 @@ var defaultUserConfig = {
174
323
  sidebar: setGenerateSidebar(),
175
324
  socialLinks: [{ icon: "github", link: "https://github.com/ruan-cat" }],
176
325
  editLink: {
177
- pattern: "https://github.com/ruan-cat/monorepo/blob/dev/packages/vitepress-preset-config/src/docs/lesson/index.md",
326
+ pattern: "https://github.com/ruan-cat/monorepo/blob/dev/packages/vitepress-preset-config/src/docs/please-reset-themeConfig-editLink.md",
178
327
  text: "\u5728 github \u4E0A\u6253\u5F00\u6B64\u9875\u9762\u4EE5\u9884\u89C8\u539F\u7248 markdown \u6587\u6863"
179
328
  }
180
329
  },
@@ -222,21 +371,8 @@ var defaultUserConfig = {
222
371
  }
223
372
  }
224
373
  };
225
- function handleChangeLog(userConfig) {
226
- var _a;
227
- if (!hasChangelogMd()) {
228
- consola.warn(` \u672A\u627E\u5230\u53D8\u66F4\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u53D8\u66F4\u65E5\u5FD7\u5BFC\u822A\u680F\u3002 `);
229
- return;
230
- }
231
- const nav = (_a = userConfig == null ? void 0 : userConfig.themeConfig) == null ? void 0 : _a.nav;
232
- if (isUndefined(nav)) {
233
- consola.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
234
- throw new Error(` nav \u9ED8\u8BA4\u63D0\u4F9B\u7684\u5BFC\u822A\u680F\u914D\u7F6E\u4E3A\u7A7A\u3002\u4E0D\u7B26\u5408\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u8BF7\u68C0\u67E5\u3002 `);
235
- }
236
- nav.push({ text: "\u66F4\u65B0\u65E5\u5FD7", link: "/CHANGELOG.md" });
237
- }
238
374
  function setUserConfig(config, extraConfig) {
239
- const resUserConfig = merge2({}, cloneDeep2(defaultUserConfig), isUndefined(config) ? {} : config);
375
+ const resUserConfig = merge2({}, cloneDeep2(defaultUserConfig), isUndefined2(config) ? {} : config);
240
376
  handleChangeLog(resUserConfig);
241
377
  handlePlugins(resUserConfig, extraConfig);
242
378
  handleTeekConfig(resUserConfig, extraConfig);
@@ -244,7 +380,6 @@ function setUserConfig(config, extraConfig) {
244
380
  }
245
381
  export {
246
382
  addChangelog2doc,
247
- copyChangelogMd,
248
383
  copyClaudeAgents,
249
384
  copyReadmeMd,
250
385
  defineConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruan-cat/vitepress-preset-config",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "用于给大多数的vitepress项目提供一个预设的配置文件。",
5
5
  "homepage": "https://vitepress-preset.ruancat6312.top",
6
6
  "types": "./src/config.mts",
@@ -19,7 +19,7 @@
19
19
  "vitepress-sidebar": "^1.33.0",
20
20
  "vitepress-theme-teek": "^1.4.6",
21
21
  "vue": "^3.5.21",
22
- "@ruan-cat/utils": "^4.13.0"
22
+ "@ruan-cat/utils": "^4.14.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/lodash-es": "^4.17.12",
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+
3
+ import { copyChangelogMd, hasChangelogMd } from "../utils/copy-changelog";
4
+ import { writeYaml2md } from "@ruan-cat/utils/node-esm";
5
+
6
+ export interface AddChangelog2docOptions<T = Record<string, any>> {
7
+ /** 目标文件夹 */
8
+ target: string;
9
+
10
+ /** 被插入到md头部的数据 */
11
+ data: T;
12
+ }
13
+
14
+ /** 将变更日志添加到指定的文档目录内 并提供参数 */
15
+ export function addChangelog2doc<T>(options: AddChangelog2docOptions<T>) {
16
+ const { data, target } = options;
17
+
18
+ if (!hasChangelogMd()) {
19
+ return;
20
+ }
21
+
22
+ copyChangelogMd(target);
23
+
24
+ const mdPath = path.resolve(process.cwd(), target, "CHANGELOG.md");
25
+ writeYaml2md({
26
+ mdPath,
27
+ data,
28
+ });
29
+ }
@@ -0,0 +1,30 @@
1
+ // 为变更日志写入vitepress侧边栏排序yaml信息 并在导航栏内增加顶部入口便于快速阅读
2
+
3
+ import { type UserConfig, type DefaultTheme } from "vitepress";
4
+ import consola from "consola";
5
+ import { isUndefined } from "lodash-es";
6
+ import { hasChangelogMd } from "../utils/copy-changelog";
7
+
8
+ /**
9
+ * 设置导航栏的变更日志
10
+ * @description
11
+ * 在导航栏内添加一行 变更日志 的入口
12
+ *
13
+ * 直接修改外部传递进来的配置对象即可
14
+ * @private 内部使用即可
15
+ */
16
+ export function handleChangeLog(userConfig: UserConfig<DefaultTheme.Config>) {
17
+ if (!hasChangelogMd()) {
18
+ consola.warn(` 未找到变更日志文件,不添加变更日志导航栏。 `);
19
+ return;
20
+ }
21
+
22
+ const nav = userConfig?.themeConfig?.nav;
23
+
24
+ if (isUndefined(nav)) {
25
+ consola.error(` 当前的用户配置为: `, userConfig);
26
+ throw new Error(` nav 默认提供的导航栏配置为空。不符合默认配置,请检查。 `);
27
+ }
28
+
29
+ nav.push({ text: "更新日志", link: "/CHANGELOG.md" });
30
+ }
@@ -0,0 +1,194 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import consola from "consola";
4
+
5
+ /**
6
+ * 从当前工作目录向上查找 monorepo 根目录
7
+ * @returns monorepo 根目录的绝对路径,如果找不到则返回 null
8
+ * @description
9
+ * 通过查找 pnpm-workspace.yaml 文件来定位 monorepo 根目录。
10
+ * 从 process.cwd() 开始向上遍历,直到找到该文件或到达文件系统根目录。
11
+ */
12
+ function findMonorepoRoot(): string | null {
13
+ let currentDir = process.cwd();
14
+ const root = path.parse(currentDir).root;
15
+
16
+ while (currentDir !== root) {
17
+ const workspaceFile = path.join(currentDir, "pnpm-workspace.yaml");
18
+ if (fs.existsSync(workspaceFile)) {
19
+ return currentDir;
20
+ }
21
+ currentDir = path.dirname(currentDir);
22
+ }
23
+
24
+ // 检查根目录本身
25
+ const workspaceFile = path.join(root, "pnpm-workspace.yaml");
26
+ if (fs.existsSync(workspaceFile)) {
27
+ return root;
28
+ }
29
+
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * 解析根目录路径
35
+ * @param rootDir - 根目录路径,支持相对路径(如 `../../../`)或绝对路径
36
+ * @returns 解析后的绝对路径
37
+ * @description
38
+ * 路径解析优先级:
39
+ * 1. 如果传入 rootDir,将相对路径基于 process.cwd() 解析为绝对路径
40
+ * 2. 如果未传入 rootDir,自动向上查找包含 pnpm-workspace.yaml 的 monorepo 根目录
41
+ * 3. 如果自动查找失败,回退到 process.cwd()
42
+ */
43
+ function resolveRootDir(rootDir?: string): string {
44
+ if (rootDir) {
45
+ // 将相对路径或绝对路径解析为绝对路径
46
+ return path.resolve(process.cwd(), rootDir);
47
+ }
48
+
49
+ // 自动查找 monorepo 根目录
50
+ const monorepoRoot = findMonorepoRoot();
51
+ if (monorepoRoot) {
52
+ return monorepoRoot;
53
+ }
54
+
55
+ // 回退到当前工作目录
56
+ return process.cwd();
57
+ }
58
+
59
+ /**
60
+ * 检查指定根目录是否存在 .claude/agents 文件夹
61
+ * @param options - 配置选项
62
+ * @param options.rootDir - 可选的根目录路径,支持相对路径(如 `../../../` 表示向上三级目录)。
63
+ * 如果不传入,将自动向上查找包含 pnpm-workspace.yaml 的 monorepo 根目录。
64
+ * 相对路径会基于当前工作目录 (process.cwd()) 解析为绝对路径。
65
+ * @returns 是否存在 .claude/agents 文件夹
66
+ * @example
67
+ * // 自动检测 monorepo 根目录
68
+ * hasClaudeAgents()
69
+ *
70
+ * // 手动指定相对路径(从当前工作目录向上三级)
71
+ * hasClaudeAgents({ rootDir: '../../../' })
72
+ *
73
+ * // 手动指定绝对路径
74
+ * hasClaudeAgents({ rootDir: '/path/to/monorepo' })
75
+ */
76
+ function hasClaudeAgents(options?: { rootDir?: string }): boolean {
77
+ const root = resolveRootDir(options?.rootDir);
78
+ const claudeAgentsPath = path.join(root, ".claude/agents");
79
+ const exists = fs.existsSync(claudeAgentsPath);
80
+
81
+ if (!exists) {
82
+ consola.log("检测的根目录为:", root);
83
+ consola.warn("该根目录不存在 .claude/agents 文件夹");
84
+ }
85
+
86
+ return exists;
87
+ }
88
+
89
+ /**
90
+ * 将 .claude/agents 文件夹复制到指定位置的配置选项
91
+ */
92
+ export interface CopyClaudeAgentsOptions {
93
+ /**
94
+ * 目标文件夹路径(必须是相对路径,相对于当前工作目录)
95
+ * @description
96
+ * **重要**:此参数仅接受相对路径,不接受绝对路径(禁止以 `/` 或盘符如 `C:\` 开头)。
97
+ * 使用绝对路径会抛出错误,这是为了防止意外覆盖系统目录。
98
+ *
99
+ * 该地址是写相对路径的 不能写绝对路径,容易导致意外。
100
+ * vitepress 命令运行在 apps/admin 目录内,该地址是相对于该运行目录的。
101
+ * 比如期望将 `.claude/agents` 复制到 `apps/admin/src/docs/prompts/agents` 文件夹。
102
+ * 则写 `src/docs/prompts/agents` 即可。
103
+ *
104
+ * @throws {Error} 当传入绝对路径时抛出错误
105
+ * @example
106
+ * // ✅ 正确:相对路径
107
+ * "src/docs/prompts/agents"
108
+ * "dist/agents"
109
+ * "./public/claude"
110
+ *
111
+ * @example
112
+ * // ❌ 错误:绝对路径(会抛出错误)
113
+ * "/var/www/agents" // Unix 绝对路径
114
+ * "C:\\Users\\agents" // Windows 绝对路径
115
+ */
116
+ target: string;
117
+
118
+ /**
119
+ * 可选的根目录路径,支持相对路径(如 `../../../` 表示向上三级目录)
120
+ * @description
121
+ * - 如果不传入,将自动向上查找包含 pnpm-workspace.yaml 的 monorepo 根目录
122
+ * - 相对路径会基于当前工作目录 (process.cwd()) 解析为绝对路径
123
+ * - 绝对路径将直接使用
124
+ * @example
125
+ * // 相对路径:向上三级目录
126
+ * '../../../'
127
+ *
128
+ * // 绝对路径
129
+ * '/absolute/path/to/monorepo'
130
+ */
131
+ rootDir?: string;
132
+ }
133
+
134
+ /**
135
+ * 将 .claude/agents 文件夹复制到指定位置
136
+ * @param options - 配置选项
137
+ * @throws {Error} 当 `options.target` 为绝对路径时抛出错误
138
+ * @description
139
+ * 该函数相当于实现 `cpx .claude/agents <target>` 命令。
140
+ * 从根目录的 .claude/agents 复制到目标位置。
141
+ *
142
+ * **安全限制**:`target` 参数必须是相对路径,禁止使用绝对路径,以防止意外覆盖系统目录。
143
+ *
144
+ * @example
145
+ * // ✅ 自动检测 monorepo 根目录,复制到当前目录的 dist 文件夹
146
+ * copyClaudeAgents({ target: 'dist' })
147
+ *
148
+ * // ✅ 手动指定根目录为向上三级,复制到 build 文件夹
149
+ * copyClaudeAgents({
150
+ * target: 'build',
151
+ * rootDir: '../../../'
152
+ * })
153
+ *
154
+ * // ✅ 使用绝对路径指定根目录(rootDir 允许绝对路径)
155
+ * copyClaudeAgents({
156
+ * target: 'dist', // target 必须是相对路径
157
+ * rootDir: '/absolute/path/to/monorepo' // rootDir 可以是绝对路径
158
+ * })
159
+ *
160
+ * @example
161
+ * // ❌ 错误用法:target 使用绝对路径会抛出错误
162
+ * copyClaudeAgents({ target: '/var/www/agents' }) // 抛出 Error
163
+ * copyClaudeAgents({ target: 'C:\\Windows\\agents' }) // 抛出 Error
164
+ */
165
+ export function copyClaudeAgents(options: CopyClaudeAgentsOptions): void {
166
+ // 验证 target 不能是绝对路径
167
+ if (path.isAbsolute(options.target)) {
168
+ const errorMessage = [
169
+ `target 参数不允许使用绝对路径,这可能导致意外的文件覆盖。`,
170
+ `当前传入的路径: "${options.target}"`,
171
+ `请使用相对路径,例如: "src/docs/prompts/agents"`,
172
+ ].join("\n");
173
+
174
+ consola.error(errorMessage);
175
+ throw new Error(errorMessage);
176
+ }
177
+
178
+ // 检查源目录是否存在
179
+ if (!hasClaudeAgents({ rootDir: options.rootDir })) {
180
+ return;
181
+ }
182
+
183
+ const root = resolveRootDir(options.rootDir);
184
+ const source = path.join(root, ".claude/agents");
185
+ const destination = path.resolve(process.cwd(), options.target);
186
+
187
+ // 确保目标文件夹的父目录存在
188
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
189
+
190
+ // 递归复制文件夹
191
+ fs.cpSync(source, destination, { recursive: true });
192
+
193
+ consola.success(`已成功复制 .claude/agents 到 ${destination}`);
194
+ }
@@ -0,0 +1,60 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import consola from "consola";
4
+
5
+ // 有疑惑 这个写法都不对么? 这个写法对于 vitepress 文档构建来说 是会引发报错的
6
+ // import { isConditionsSome } from "@ruan-cat/utils/src/conditions.ts";
7
+ import { isConditionsSome } from "@ruan-cat/utils";
8
+
9
+ /** 大写字母的文件 */
10
+ const capitalReadmeMd = "README.md" as const;
11
+
12
+ /** 小写字母的文件 */
13
+ const lowerCaseReadmeMd = "readme.md" as const;
14
+
15
+ /** 检查当前运行的根目录 是否存在文件名大写的 `README.md` 文件 */
16
+ function hasCapitalReadmeMd() {
17
+ const res = fs.existsSync(path.resolve(process.cwd(), capitalReadmeMd));
18
+ if (!res) {
19
+ consola.warn(`当前项目根目录不存在 ${capitalReadmeMd} 文件`);
20
+ }
21
+ return res;
22
+ }
23
+
24
+ /** 检查当前运行的根目录 是否存在文件名小写的 `readme.md` 文件 */
25
+ function hasLowerCaseReadmeMd() {
26
+ const res = fs.existsSync(path.resolve(process.cwd(), lowerCaseReadmeMd));
27
+ if (!res) {
28
+ consola.log(`当前项目根目录不存在 ${lowerCaseReadmeMd} 文件`);
29
+ }
30
+ return res;
31
+ }
32
+
33
+ /** 检查当前运行的根目录 是否存在任意一个大小写命名的 README.md 文件 */
34
+ function hasReadmeMd() {
35
+ const res = isConditionsSome([() => hasCapitalReadmeMd(), () => hasCapitalReadmeMd()]);
36
+ return res;
37
+ }
38
+
39
+ /**
40
+ * 将 README.md 文件移动到指定要求的位置内,并重命名为 index.md
41
+ * @description
42
+ * 该函数相当于实现 `cpx README.md docs` 命令
43
+ */
44
+ export function copyReadmeMd(/** 目标文件夹 */ target: string) {
45
+ if (!hasReadmeMd()) {
46
+ return;
47
+ }
48
+
49
+ let readmeFileName: string = capitalReadmeMd;
50
+ if (hasCapitalReadmeMd()) {
51
+ readmeFileName = capitalReadmeMd;
52
+ } else if (hasLowerCaseReadmeMd()) {
53
+ readmeFileName = lowerCaseReadmeMd;
54
+ }
55
+
56
+ const source = path.resolve(process.cwd(), readmeFileName);
57
+ const destination = path.resolve(process.cwd(), target, "index.md");
58
+
59
+ fs.copyFileSync(source, destination);
60
+ }
@@ -1,4 +1,9 @@
1
1
  // 专门为 packages\vitepress-preset-config\src\config.mts 服务的文件 提供额外配置
2
2
 
3
+ export * from "./page-order-config";
4
+ export * from "./copy-readme";
5
+ export * from "./copy-claude-agents";
6
+ export * from "./add-changelog-to-doc";
3
7
  export * from "./plugins";
8
+ export * from "./changelog-nav";
4
9
  export * from "./teek";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 页面排序配置
3
+ * 预期设计为一个常量 用于给一些固定的,杂项的页面做侧边栏排序
4
+ *
5
+ * 数字越大,排序越靠后
6
+ */
7
+ export const pageOrderConfig = {
8
+ /** 提示词页面排序 */
9
+ prompts: {
10
+ order: 8000,
11
+ },
12
+
13
+ /** 变更日志页面排序 */
14
+ changelog: {
15
+ order: 9000,
16
+ },
17
+ };
package/src/config.mts CHANGED
@@ -9,20 +9,15 @@ import { merge, isUndefined, cloneDeep } from "lodash-es";
9
9
  import consola from "consola";
10
10
  import type { ExtraConfig } from "./types.ts";
11
11
 
12
- import {
13
- addChangelog2doc,
14
- hasChangelogMd,
15
- copyReadmeMd,
16
- copyClaudeAgents,
17
- copyChangelogMd,
18
- } from "@ruan-cat/utils/node-esm";
19
- export { addChangelog2doc, copyReadmeMd, copyClaudeAgents, copyChangelogMd };
12
+ // import { addChangelog2doc, copyReadmeMd, copyClaudeAgents } from "@ruan-cat/utils/node-esm";
13
+ import { addChangelog2doc, copyReadmeMd, copyClaudeAgents } from "./config/index.ts";
14
+ export { addChangelog2doc, copyReadmeMd, copyClaudeAgents };
20
15
 
21
16
  import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
22
17
 
23
18
  import llmstxt, { copyOrDownloadAsMarkdownButtons } from "vitepress-plugin-llms";
24
19
 
25
- import { defaultTeekConfig, handleTeekConfig, handlePlugins } from "./config/index.ts";
20
+ import { defaultTeekConfig, handleTeekConfig, handlePlugins, handleChangeLog } from "./config/index.ts";
26
21
 
27
22
  /** @see https://vitepress-ext.leelaa.cn/Mermaid.html#扩展-md-插件 */
28
23
  import { MermaidPlugin } from "@leelaa/vitepress-plugin-extended";
@@ -118,7 +113,7 @@ const defaultUserConfig: UserConfig<DefaultTheme.Config> = {
118
113
 
119
114
  editLink: {
120
115
  pattern:
121
- "https://github.com/ruan-cat/monorepo/blob/dev/packages/vitepress-preset-config/src/docs/lesson/index.md",
116
+ "https://github.com/ruan-cat/monorepo/blob/dev/packages/vitepress-preset-config/src/docs/please-reset-themeConfig-editLink.md",
122
117
  text: "在 github 上打开此页面以预览原版 markdown 文档",
123
118
  },
124
119
  },
@@ -180,30 +175,6 @@ const defaultUserConfig: UserConfig<DefaultTheme.Config> = {
180
175
  },
181
176
  };
182
177
 
183
- /**
184
- * 设置导航栏的变更日志
185
- * @description
186
- * 在导航栏内添加一行 变更日志 的入口
187
- *
188
- * 直接修改外部传递进来的配置对象即可
189
- * @private 内部使用即可
190
- */
191
- function handleChangeLog(userConfig: UserConfig<DefaultTheme.Config>) {
192
- if (!hasChangelogMd()) {
193
- consola.warn(` 未找到变更日志文件,不添加变更日志导航栏。 `);
194
- return;
195
- }
196
-
197
- const nav = userConfig?.themeConfig?.nav;
198
-
199
- if (isUndefined(nav)) {
200
- consola.error(` 当前的用户配置为: `, userConfig);
201
- throw new Error(` nav 默认提供的导航栏配置为空。不符合默认配置,请检查。 `);
202
- }
203
-
204
- nav.push({ text: "更新日志", link: "/CHANGELOG.md" });
205
- }
206
-
207
178
  /** 设置vitepress主配置 */
208
179
  export function setUserConfig(
209
180
  config?: UserConfig<DefaultTheme.Config>,
@@ -0,0 +1,29 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import consola from "consola";
4
+
5
+ /** 检查当前运行的根目录 是否存在 CHANGELOG.md 文件 */
6
+ export function hasChangelogMd() {
7
+ const res = fs.existsSync(path.resolve(process.cwd(), "CHANGELOG.md"));
8
+ if (!res) {
9
+ consola.log("当前项目根目录为:", process.cwd());
10
+ consola.warn("当前项目根目录不存在 CHANGELOG.md 文件");
11
+ }
12
+ return res;
13
+ }
14
+
15
+ /**
16
+ * 将 CHANGELOG.md 文件移动到指定要求的位置内
17
+ * @description
18
+ * 该函数相当于实现 `cpx CHANGELOG.md docs` 命令
19
+ */
20
+ export function copyChangelogMd(/** 目标文件夹 */ target: string) {
21
+ if (!hasChangelogMd()) {
22
+ return;
23
+ }
24
+
25
+ const source = path.resolve(process.cwd(), "CHANGELOG.md");
26
+ const destination = path.resolve(process.cwd(), target, "CHANGELOG.md");
27
+
28
+ fs.copyFileSync(source, destination);
29
+ }