@ruan-cat/vitepress-preset-config 2.14.0 → 3.0.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
@@ -158,19 +158,22 @@ interface CopyClaudeFilesOptions {
158
158
  */
159
159
  declare function copyClaudeFiles(options: CopyClaudeFilesOptions): void;
160
160
 
161
- interface AddChangelog2docOptions<T extends Record<string, any> = Record<string, any>> {
161
+ interface AddChangelog2docOptions<T extends Record<string, unknown> = Record<string, unknown>> {
162
162
  /** 目标文件夹 */
163
163
  target: string;
164
164
  /** 被插入到md头部的数据 */
165
165
  data?: T;
166
166
  }
167
167
  /** 将变更日志添加到指定的文档目录内 并提供参数 */
168
- declare function addChangelog2doc<T extends Record<string, any>>(options: AddChangelog2docOptions<T>): void;
168
+ declare function addChangelog2doc<T extends Record<string, unknown>>(options: AddChangelog2docOptions<T>): void;
169
169
 
170
170
  type VitePressSidebarOptions = Parameters<typeof generateSidebar>[0];
171
171
  /**
172
172
  * 设置自动生成侧边栏的配置
173
173
  * @see https://vitepress-sidebar.cdget.com/zhHans/guide/options
174
+ *
175
+ * 注意:在 Windows 环境下,必须使用相对路径(如 "./docs")而不是绝对路径,
176
+ * 否则 vitepress-sidebar 内部的 path.join 会导致路径拼接错误。
174
177
  */
175
178
  declare function setGenerateSidebar(options?: VitePressSidebarOptions): vitepress_sidebar_types.Sidebar;
176
179
  /** 设置vitepress主配置 */
package/dist/config.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/config.mts
2
2
  import { defineConfig } from "vitepress";
3
- import { generateSidebar } from "vitepress-sidebar";
3
+ import { generateSidebar as generateSidebar2 } from "vitepress-sidebar";
4
4
  import { vitepressDemoPlugin } from "vitepress-demo-plugin";
5
- import { merge as merge3, isUndefined as isUndefined3, cloneDeep as cloneDeep2 } from "lodash-es";
5
+ import { merge as merge4, isUndefined as isUndefined3, cloneDeep as cloneDeep3 } from "lodash-es";
6
6
 
7
7
  // src/config/page-order-config.ts
8
8
  var pageOrderConfig = {
@@ -136,8 +136,9 @@ import matter from "gray-matter";
136
136
  import fs3 from "fs";
137
137
  import path3 from "path";
138
138
  import consola3 from "consola";
139
- function hasChangelogMd() {
140
- const res = fs3.existsSync(path3.resolve(process.cwd(), "CHANGELOG.md"));
139
+ function checkChangelogExists() {
140
+ const sourceDir = path3.resolve(process.cwd(), "CHANGELOG.md");
141
+ const res = fs3.existsSync(sourceDir);
141
142
  if (!res) {
142
143
  consola3.log("\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E3A\uFF1A", process.cwd());
143
144
  consola3.warn("\u5F53\u524D\u9879\u76EE\u6839\u76EE\u5F55\u4E0D\u5B58\u5728 CHANGELOG.md \u6587\u4EF6");
@@ -145,7 +146,7 @@ function hasChangelogMd() {
145
146
  return res;
146
147
  }
147
148
  function copyChangelogMd(target) {
148
- if (!hasChangelogMd()) {
149
+ if (!checkChangelogExists()) {
149
150
  return;
150
151
  }
151
152
  const source = path3.resolve(process.cwd(), "CHANGELOG.md");
@@ -154,9 +155,13 @@ function copyChangelogMd(target) {
154
155
  }
155
156
 
156
157
  // src/config/add-changelog-to-doc.ts
158
+ function hasChangelogInRoot() {
159
+ const changelogPath = path4.resolve(process.cwd(), "CHANGELOG.md");
160
+ return fs4.existsSync(changelogPath);
161
+ }
157
162
  function addChangelog2doc(options) {
158
163
  const { data = pageOrderConfig.changelog, target } = options;
159
- if (!hasChangelogMd()) {
164
+ if (!hasChangelogInRoot()) {
160
165
  return;
161
166
  }
162
167
  copyChangelogMd(target);
@@ -212,31 +217,15 @@ function handlePlugins(userConfig, extraConfig) {
212
217
  }
213
218
 
214
219
  // src/config/changelog-nav.ts
215
- import consola5 from "consola";
220
+ import consola6 from "consola";
216
221
  import { isUndefined } from "lodash-es";
217
- import { printFormat } from "@ruan-cat/utils";
218
- function handleChangeLog(userConfig) {
219
- var _a;
220
- if (!hasChangelogMd()) {
221
- consola5.warn(` \u672A\u627E\u5230\u53D8\u66F4\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u53D8\u66F4\u65E5\u5FD7\u5BFC\u822A\u680F\u3002 `);
222
- return;
223
- }
224
- const nav = (_a = userConfig == null ? void 0 : userConfig.themeConfig) == null ? void 0 : _a.nav;
225
- if (isUndefined(nav)) {
226
- consola5.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
227
- 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 `);
228
- }
229
- nav.push({ text: "\u66F4\u65B0\u65E5\u5FD7", link: "/CHANGELOG.md" });
230
- console.log(" \u67E5\u770B\u4FA7\u8FB9\u680F\u6570\u636E ", printFormat(userConfig == null ? void 0 : userConfig.themeConfig.sidebar));
231
- }
232
222
 
233
- // src/config/prompts-nav.ts
223
+ // src/utils/vitepress-project.ts
234
224
  import fs5 from "fs";
235
225
  import path5 from "path";
236
- import consola6 from "consola";
237
- import { isUndefined as isUndefined2, merge } from "lodash-es";
238
- import matter2 from "gray-matter";
226
+ import consola5 from "consola";
239
227
  var PROMPTS_INDEX_MD_PATH = "prompts/index.md";
228
+ var CHANGELOG_MD_FILENAME = "CHANGELOG.md";
240
229
  function getProjectRootFromArgs() {
241
230
  const args = process.argv;
242
231
  const vitepressIndex = args.findIndex((arg) => arg.includes("vitepress"));
@@ -254,36 +243,60 @@ function getProjectRootFromArgs() {
254
243
  }
255
244
  continue;
256
245
  }
257
- return path5.resolve(process.cwd(), arg);
246
+ let inputPath = path5.resolve(process.cwd(), arg);
247
+ let currentDir = inputPath;
248
+ const root = path5.parse(currentDir).root;
249
+ while (currentDir !== root) {
250
+ if (fs5.existsSync(path5.join(currentDir, ".vitepress"))) {
251
+ consola5.log("\u4ECE\u547D\u4EE4\u884C\u53C2\u6570\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", currentDir);
252
+ return currentDir;
253
+ }
254
+ currentDir = path5.dirname(currentDir);
255
+ }
256
+ if (fs5.existsSync(path5.join(root, ".vitepress"))) {
257
+ consola5.log("\u4ECE\u547D\u4EE4\u884C\u53C2\u6570\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", root);
258
+ return root;
259
+ }
260
+ return null;
258
261
  }
259
262
  return null;
260
263
  }
261
- function getVitepressProjectRoot() {
262
- const projectRootFromArgs = getProjectRootFromArgs();
263
- if (projectRootFromArgs) {
264
- const vitepressDir2 = path5.join(projectRootFromArgs, ".vitepress");
265
- if (fs5.existsSync(vitepressDir2)) {
266
- consola6.log("\u4ECE\u547D\u4EE4\u884C\u53C2\u6570\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", projectRootFromArgs);
267
- return projectRootFromArgs;
268
- }
269
- }
270
- let currentDir = process.cwd();
271
- const root = path5.parse(currentDir).root;
264
+ function findProjectRootFromDir(startDir) {
265
+ const root = path5.parse(startDir).root;
266
+ let currentDir = startDir;
272
267
  while (currentDir !== root) {
273
- const vitepressDir2 = path5.join(currentDir, ".vitepress");
274
- if (fs5.existsSync(vitepressDir2)) {
275
- consola6.log("\u901A\u8FC7\u5411\u4E0A\u67E5\u627E\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", currentDir);
268
+ const vitepressDir = path5.join(currentDir, ".vitepress");
269
+ if (fs5.existsSync(vitepressDir)) {
276
270
  return currentDir;
277
271
  }
278
272
  currentDir = path5.dirname(currentDir);
279
273
  }
280
- const vitepressDir = path5.join(root, ".vitepress");
281
- if (fs5.existsSync(vitepressDir)) {
282
- consola6.log("\u5728\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u627E\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", root);
274
+ const rootVitepressDir = path5.join(root, ".vitepress");
275
+ if (fs5.existsSync(rootVitepressDir)) {
283
276
  return root;
284
277
  }
278
+ return null;
279
+ }
280
+ function getVitepressProjectRoot() {
281
+ const projectRootFromArgs = getProjectRootFromArgs();
282
+ if (projectRootFromArgs) {
283
+ const foundRoot = findProjectRootFromDir(projectRootFromArgs);
284
+ if (foundRoot) {
285
+ consola5.log("\u4ECE\u547D\u4EE4\u884C\u53C2\u6570\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", foundRoot);
286
+ return foundRoot;
287
+ }
288
+ }
289
+ const foundFromCwd = findProjectRootFromDir(process.cwd());
290
+ if (foundFromCwd) {
291
+ consola5.log("\u901A\u8FC7\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u83B7\u53D6\u5230 VitePress \u9879\u76EE\u6839\u76EE\u5F55\uFF1A", foundFromCwd);
292
+ return foundFromCwd;
293
+ }
294
+ if (projectRootFromArgs) {
295
+ consola5.warn("\u672A\u627E\u5230 .vitepress \u76EE\u5F55\uFF0C\u56DE\u9000\u5230\u547D\u4EE4\u884C\u53C2\u6570\u8DEF\u5F84\uFF1A", projectRootFromArgs);
296
+ return projectRootFromArgs;
297
+ }
285
298
  const fallbackDir = process.cwd();
286
- consola6.warn("\u672A\u627E\u5230 .vitepress \u76EE\u5F55\uFF0C\u56DE\u9000\u5230\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1A", fallbackDir);
299
+ consola5.warn("\u672A\u627E\u5230 .vitepress \u76EE\u5F55\uFF0C\u56DE\u9000\u5230\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1A", fallbackDir);
287
300
  return fallbackDir;
288
301
  }
289
302
  function getVitepressSourceDirectory(userConfig) {
@@ -291,67 +304,98 @@ function getVitepressSourceDirectory(userConfig) {
291
304
  const srcDir = userConfig.srcDir;
292
305
  if (srcDir) {
293
306
  const sourceDirectory = path5.resolve(projectRoot, srcDir);
294
- consola6.log("\u4ECE\u914D\u7F6E\u4E2D\u83B7\u53D6\u5230 srcDir:", srcDir);
295
- consola6.log("VitePress \u6E90\u76EE\u5F55\u4E3A\uFF1A", sourceDirectory);
307
+ consola5.log("\u4ECE\u914D\u7F6E\u4E2D\u83B7\u53D6\u5230 srcDir:", srcDir);
308
+ consola5.log("VitePress \u6E90\u76EE\u5F55\u4E3A\uFF1A", sourceDirectory);
296
309
  return sourceDirectory;
297
310
  }
298
- consola6.log("\u914D\u7F6E\u4E2D\u672A\u6307\u5B9A srcDir\uFF0C\u6E90\u76EE\u5F55\u7B49\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\uFF1A", projectRoot);
311
+ consola5.log("\u914D\u7F6E\u4E2D\u672A\u6307\u5B9A srcDir\uFF0C\u6E90\u76EE\u5F55\u7B49\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\uFF1A", projectRoot);
299
312
  return projectRoot;
300
313
  }
301
314
  function hasPromptsIndexMd(userConfig) {
302
315
  const sourceDir = getVitepressSourceDirectory(userConfig);
303
316
  const res = fs5.existsSync(path5.resolve(sourceDir, PROMPTS_INDEX_MD_PATH));
304
317
  if (!res) {
305
- consola6.log("\u5F53\u524D\u9879\u76EE\u7684vitepress\u6E90\u76EE\u5F55\u4E3A\uFF1A", sourceDir);
306
- consola6.warn(`\u5F53\u524D\u9879\u76EE\u7684vitepress\u6E90\u76EE\u5F55\u4E0D\u5B58\u5728 ${PROMPTS_INDEX_MD_PATH} \u6587\u4EF6`);
318
+ consola5.log("\u5F53\u524D\u9879\u76EE\u7684 vitepress \u6E90\u76EE\u5F55\u4E3A\uFF1A", sourceDir);
319
+ consola5.warn(`\u5F53\u524D\u9879\u76EE\u7684 vitepress \u6E90\u76EE\u5F55\u4E0D\u5B58\u5728 ${PROMPTS_INDEX_MD_PATH} \u6587\u4EF6`);
320
+ }
321
+ return res;
322
+ }
323
+ function hasChangelogMd(userConfig) {
324
+ const sourceDir = getVitepressSourceDirectory(userConfig);
325
+ const res = fs5.existsSync(path5.resolve(sourceDir, CHANGELOG_MD_FILENAME));
326
+ if (!res) {
327
+ consola5.log("\u5F53\u524D\u9879\u76EE\u7684 vitepress \u6E90\u76EE\u5F55\u4E3A\uFF1A", sourceDir);
328
+ consola5.warn(`\u5F53\u524D\u9879\u76EE\u7684 vitepress \u6E90\u76EE\u5F55\u4E0D\u5B58\u5728 ${CHANGELOG_MD_FILENAME} \u6587\u4EF6`);
307
329
  }
308
330
  return res;
309
331
  }
332
+
333
+ // src/config/changelog-nav.ts
334
+ function handleChangeLog(userConfig) {
335
+ var _a;
336
+ if (!hasChangelogMd(userConfig)) {
337
+ consola6.warn(` \u672A\u627E\u5230\u53D8\u66F4\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u53D8\u66F4\u65E5\u5FD7\u5BFC\u822A\u680F\u3002 `);
338
+ return;
339
+ }
340
+ const nav = (_a = userConfig == null ? void 0 : userConfig.themeConfig) == null ? void 0 : _a.nav;
341
+ if (isUndefined(nav)) {
342
+ consola6.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
343
+ 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 `);
344
+ }
345
+ nav.push({ text: "\u66F4\u65B0\u65E5\u5FD7", link: "/CHANGELOG.md" });
346
+ }
347
+
348
+ // src/config/prompts-nav.ts
349
+ import fs6 from "fs";
350
+ import path6 from "path";
351
+ import consola7 from "consola";
352
+ import { isUndefined as isUndefined2, merge } from "lodash-es";
353
+ import matter2 from "gray-matter";
310
354
  function writeYaml2PromptsIndexMd(userConfig, data) {
311
355
  const newData = data ?? pageOrderConfig.prompts;
312
356
  const sourceDir = getVitepressSourceDirectory(userConfig);
313
- const mdPath = path5.resolve(sourceDir, PROMPTS_INDEX_MD_PATH);
357
+ const mdPath = path6.resolve(sourceDir, PROMPTS_INDEX_MD_PATH);
314
358
  try {
315
359
  let fileContent = "";
316
360
  let existingData = {};
317
- if (fs5.existsSync(mdPath)) {
318
- fileContent = fs5.readFileSync(mdPath, "utf-8");
361
+ if (fs6.existsSync(mdPath)) {
362
+ fileContent = fs6.readFileSync(mdPath, "utf-8");
319
363
  const parsed = matter2(fileContent);
320
364
  if (parsed.data && Object.keys(parsed.data).length > 0) {
321
365
  existingData = parsed.data;
322
- consola6.log("\u68C0\u6D4B\u5230\u5DF2\u6709 YAML frontmatter\uFF0C\u5C06\u8FDB\u884C\u6570\u636E\u5408\u5E76");
366
+ consola7.log("\u68C0\u6D4B\u5230\u5DF2\u6709 YAML frontmatter\uFF0C\u5C06\u8FDB\u884C\u6570\u636E\u5408\u5E76");
323
367
  } else {
324
- consola6.log("\u6587\u4EF6\u5B58\u5728\u4F46\u6CA1\u6709 YAML frontmatter\uFF0C\u5C06\u5199\u5165\u65B0\u7684 frontmatter");
368
+ consola7.log("\u6587\u4EF6\u5B58\u5728\u4F46\u6CA1\u6709 YAML frontmatter\uFF0C\u5C06\u5199\u5165\u65B0\u7684 frontmatter");
325
369
  }
326
370
  const mergedData = merge({}, existingData, newData);
327
371
  const newContent = matter2.stringify(parsed.content, mergedData);
328
- fs5.writeFileSync(mdPath, newContent, "utf-8");
329
- consola6.success(`\u5DF2\u5C06YAML\u6570\u636E\u5199\u5165\u5230 ${mdPath}`);
372
+ fs6.writeFileSync(mdPath, newContent, "utf-8");
373
+ consola7.success(`\u5DF2\u5C06YAML\u6570\u636E\u5199\u5165\u5230 ${mdPath}`);
330
374
  } else {
331
- consola6.warn(`\u6587\u4EF6 ${mdPath} \u4E0D\u5B58\u5728\uFF0C\u5C06\u521B\u5EFA\u65B0\u6587\u4EF6`);
332
- const dir = path5.dirname(mdPath);
333
- if (!fs5.existsSync(dir)) {
334
- fs5.mkdirSync(dir, { recursive: true });
375
+ consola7.warn(`\u6587\u4EF6 ${mdPath} \u4E0D\u5B58\u5728\uFF0C\u5C06\u521B\u5EFA\u65B0\u6587\u4EF6`);
376
+ const dir = path6.dirname(mdPath);
377
+ if (!fs6.existsSync(dir)) {
378
+ fs6.mkdirSync(dir, { recursive: true });
335
379
  }
336
380
  const newContent = matter2.stringify("", newData);
337
- fs5.writeFileSync(mdPath, newContent, "utf-8");
338
- consola6.success(`\u5DF2\u521B\u5EFA\u6587\u4EF6\u5E76\u5199\u5165YAML\u6570\u636E\u5230 ${mdPath}`);
381
+ fs6.writeFileSync(mdPath, newContent, "utf-8");
382
+ consola7.success(`\u5DF2\u521B\u5EFA\u6587\u4EF6\u5E76\u5199\u5165YAML\u6570\u636E\u5230 ${mdPath}`);
339
383
  }
340
384
  } catch (error) {
341
- consola6.error(`\u5199\u5165 YAML \u6570\u636E\u5230 ${mdPath} \u65F6\u53D1\u751F\u9519\u8BEF:`, error);
385
+ consola7.error(`\u5199\u5165 YAML \u6570\u636E\u5230 ${mdPath} \u65F6\u53D1\u751F\u9519\u8BEF:`, error);
342
386
  throw error;
343
387
  }
344
388
  }
345
389
  function handlePrompts(userConfig) {
346
390
  var _a;
347
391
  if (!hasPromptsIndexMd(userConfig)) {
348
- consola6.warn(` \u672A\u627E\u5230\u63D0\u793A\u8BCD\u7D22\u5F15\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u63D0\u793A\u8BCD\u5BFC\u822A\u680F\u3002 `);
392
+ consola7.warn(` \u672A\u627E\u5230\u63D0\u793A\u8BCD\u7D22\u5F15\u6587\u4EF6\uFF0C\u4E0D\u6DFB\u52A0\u63D0\u793A\u8BCD\u5BFC\u822A\u680F\u3002 `);
349
393
  return;
350
394
  }
351
395
  writeYaml2PromptsIndexMd(userConfig);
352
396
  const nav = (_a = userConfig == null ? void 0 : userConfig.themeConfig) == null ? void 0 : _a.nav;
353
397
  if (isUndefined2(nav)) {
354
- consola6.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
398
+ consola7.error(` \u5F53\u524D\u7684\u7528\u6237\u914D\u7F6E\u4E3A\uFF1A `, userConfig);
355
399
  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 `);
356
400
  }
357
401
  nav.push({ text: "\u63D0\u793A\u8BCD", link: `/${PROMPTS_INDEX_MD_PATH}` });
@@ -425,12 +469,106 @@ function handleTeekConfig(userConfig, extraConfig) {
425
469
  userConfig.extends = defineTeekConfig(merge2({}, cloneDeep(defaultTeekConfig), teekConfig));
426
470
  }
427
471
 
472
+ // src/config/multi-sidebar.ts
473
+ import { generateSidebar } from "vitepress-sidebar";
474
+ import path7 from "path";
475
+ import consola8 from "consola";
476
+ import { merge as merge3, cloneDeep as cloneDeep2 } from "lodash-es";
477
+ var defaultSidebarOptions = {
478
+ // 侧边栏需要折叠
479
+ collapsed: true,
480
+ // 用文件的 h1 标题作为侧边栏标题
481
+ useTitleFromFileHeading: true,
482
+ // 用index文件的标题作为折叠栏的标题
483
+ useFolderTitleFromIndexFile: true,
484
+ // 折叠栏链接到index文件
485
+ useFolderLinkFromIndexFile: true,
486
+ // 用order字段做菜单排序
487
+ sortMenusByFrontmatterOrder: true,
488
+ // 不使用名称排序
489
+ sortMenusByName: false,
490
+ useFolderLinkFromSameNameSubFile: true,
491
+ // 不输出调试信息了
492
+ debugPrint: false
493
+ };
494
+ function getMergeSidebarOptions(options) {
495
+ return merge3({}, cloneDeep2(defaultSidebarOptions), options ?? {});
496
+ }
497
+ function getSourceDirRelativePathFromCwd(userConfig) {
498
+ const projectRoot = getVitepressProjectRoot();
499
+ const sourceDir = getVitepressSourceDirectory(userConfig);
500
+ const relativePath = path7.relative(process.cwd(), sourceDir);
501
+ consola8.log("\u9879\u76EE\u6839\u76EE\u5F55:", projectRoot);
502
+ consola8.log("\u6E90\u76EE\u5F55:", sourceDir);
503
+ consola8.log("cwd:", process.cwd());
504
+ consola8.log("\u6E90\u76EE\u5F55\u76F8\u5BF9\u4E8E cwd \u7684\u8DEF\u5F84:", relativePath);
505
+ if (!relativePath || relativePath.startsWith("..")) {
506
+ return ".";
507
+ }
508
+ return relativePath.replace(/\\/g, "/");
509
+ }
510
+ function prefixSidebarLinks(items, prefix) {
511
+ return items.map((item) => {
512
+ const newItem = { ...item };
513
+ if (newItem.link && typeof newItem.link === "string") {
514
+ const link = newItem.link.startsWith("/") ? newItem.link : `/${newItem.link}`;
515
+ newItem.link = `${prefix}${link}`;
516
+ }
517
+ if (Array.isArray(newItem.items)) {
518
+ newItem.items = prefixSidebarLinks(newItem.items, prefix);
519
+ }
520
+ return newItem;
521
+ });
522
+ }
523
+ function setupMultiSidebar(userConfig) {
524
+ const hasPrompts = hasPromptsIndexMd(userConfig);
525
+ const hasChangelog = hasChangelogMd(userConfig);
526
+ if (!hasPrompts && !hasChangelog) {
527
+ consola8.log("\u672A\u68C0\u6D4B\u5230 prompts \u76EE\u5F55\u6216 CHANGELOG.md\uFF0C\u4E0D\u9700\u8981\u591A\u4FA7\u8FB9\u680F");
528
+ return;
529
+ }
530
+ const themeConfig = userConfig.themeConfig;
531
+ const extraSidebars = {};
532
+ if (hasPrompts) {
533
+ const sourceDirRelativePathFromCwd = getSourceDirRelativePathFromCwd(userConfig);
534
+ const promptsFullPath = sourceDirRelativePathFromCwd === "." ? "prompts" : `${sourceDirRelativePathFromCwd}/prompts`;
535
+ consola8.log("promptsFullPath:", promptsFullPath);
536
+ const promptsSidebar = generateSidebar(
537
+ getMergeSidebarOptions({
538
+ documentRootPath: promptsFullPath
539
+ })
540
+ );
541
+ extraSidebars["/prompts/"] = Array.isArray(promptsSidebar) ? prefixSidebarLinks(promptsSidebar, "/prompts") : promptsSidebar;
542
+ }
543
+ if (hasChangelog) {
544
+ extraSidebars["/CHANGELOG"] = [];
545
+ }
546
+ let businessSidebar = themeConfig.sidebar;
547
+ Object.defineProperty(themeConfig, "sidebar", {
548
+ configurable: true,
549
+ enumerable: true,
550
+ get() {
551
+ const result = { ...extraSidebars };
552
+ if (Array.isArray(businessSidebar)) {
553
+ result["/"] = businessSidebar;
554
+ } else if (typeof businessSidebar === "object" && businessSidebar !== null) {
555
+ Object.assign(result, businessSidebar);
556
+ }
557
+ return result;
558
+ },
559
+ set(value) {
560
+ businessSidebar = value;
561
+ }
562
+ });
563
+ consola8.success("\u591A\u4FA7\u8FB9\u680F defineProperty \u62E6\u622A\u5DF2\u8BBE\u7F6E");
564
+ }
565
+
428
566
  // src/config.mts
429
567
  import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
430
568
  import { copyOrDownloadAsMarkdownButtons } from "vitepress-plugin-llms";
431
569
  import { MermaidPlugin } from "@leelaa/vitepress-plugin-extended";
432
570
  import { defineTeekConfig as defineTeekConfig2 } from "vitepress-theme-teek/config";
433
- var defaultSidebarOptions = {
571
+ var defaultSidebarOptions2 = {
434
572
  // documentRootPath: "./src",
435
573
  // 侧边栏需要折叠
436
574
  collapsed: true,
@@ -455,11 +593,15 @@ var defaultSidebarOptions = {
455
593
  // 不输出调试信息了
456
594
  debugPrint: false
457
595
  };
458
- function getMergeSidebarOptions(options) {
459
- return merge3({}, cloneDeep2(defaultSidebarOptions), isUndefined3(options) ? {} : options);
596
+ function getMergeSidebarOptions2(options) {
597
+ return merge4({}, cloneDeep3(defaultSidebarOptions2), isUndefined3(options) ? {} : options);
460
598
  }
461
599
  function setGenerateSidebar(options) {
462
- return generateSidebar(getMergeSidebarOptions(options));
600
+ const merged = getMergeSidebarOptions2(options);
601
+ if (!Array.isArray(merged)) {
602
+ merged.excludeByGlobPattern = [...merged.excludeByGlobPattern ?? [], "**/prompts/**", "**/CHANGELOG.md"];
603
+ }
604
+ return generateSidebar2(merged);
463
605
  }
464
606
  var defaultUserConfig = {
465
607
  /**
@@ -481,8 +623,8 @@ var defaultUserConfig = {
481
623
  label: "\u672C\u9875\u76EE\u5F55",
482
624
  level: "deep"
483
625
  },
484
- // 自动化侧边栏
485
- sidebar: setGenerateSidebar(),
626
+ // 自动化侧边栏(多侧边栏)
627
+ // sidebar 在 setUserConfig 中动态生成
486
628
  socialLinks: [{ icon: "github", link: "https://github.com/ruan-cat" }],
487
629
  editLink: {
488
630
  pattern: "https://github.com/ruan-cat/monorepo/blob/dev/packages/vitepress-preset-config/src/docs/please-reset-themeConfig-editLink.md",
@@ -534,11 +676,12 @@ var defaultUserConfig = {
534
676
  }
535
677
  };
536
678
  function setUserConfig(config, extraConfig) {
537
- const resUserConfig = merge3({}, cloneDeep2(defaultUserConfig), isUndefined3(config) ? {} : config);
679
+ const resUserConfig = merge4({}, cloneDeep3(defaultUserConfig), isUndefined3(config) ? {} : config);
538
680
  handlePrompts(resUserConfig);
539
681
  handleChangeLog(resUserConfig);
540
682
  handlePlugins(resUserConfig, extraConfig);
541
683
  handleTeekConfig(resUserConfig, extraConfig);
684
+ setupMultiSidebar(resUserConfig);
542
685
  return resUserConfig;
543
686
  }
544
687
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruan-cat/vitepress-preset-config",
3
- "version": "2.14.0",
3
+ "version": "3.0.0",
4
4
  "description": "用于给大多数的vitepress项目提供一个预设的配置文件。",
5
5
  "homepage": "https://vitepress-preset.ruancat6312.top",
6
6
  "types": "./src/config.mts",
@@ -3,10 +3,10 @@ import fs from "node:fs";
3
3
  import consola from "consola";
4
4
  import matter from "gray-matter";
5
5
 
6
- import { copyChangelogMd, hasChangelogMd } from "../utils/copy-changelog";
6
+ import { copyChangelogMd } from "../utils/copy-changelog";
7
7
  import { pageOrderConfig } from "./page-order-config";
8
8
 
9
- export interface AddChangelog2docOptions<T extends Record<string, any> = Record<string, any>> {
9
+ export interface AddChangelog2docOptions<T extends Record<string, unknown> = Record<string, unknown>> {
10
10
  /** 目标文件夹 */
11
11
  target: string;
12
12
 
@@ -14,11 +14,17 @@ export interface AddChangelog2docOptions<T extends Record<string, any> = Record<
14
14
  data?: T;
15
15
  }
16
16
 
17
+ /** 检查项目根目录是否存在 CHANGELOG.md */
18
+ function hasChangelogInRoot(): boolean {
19
+ const changelogPath = path.resolve(process.cwd(), "CHANGELOG.md");
20
+ return fs.existsSync(changelogPath);
21
+ }
22
+
17
23
  /** 将变更日志添加到指定的文档目录内 并提供参数 */
18
- export function addChangelog2doc<T extends Record<string, any>>(options: AddChangelog2docOptions<T>) {
24
+ export function addChangelog2doc<T extends Record<string, unknown>>(options: AddChangelog2docOptions<T>) {
19
25
  const { data = pageOrderConfig.changelog, target } = options;
20
26
 
21
- if (!hasChangelogMd()) {
27
+ if (!hasChangelogInRoot()) {
22
28
  return;
23
29
  }
24
30
 
@@ -3,8 +3,8 @@
3
3
  import { type UserConfig, type DefaultTheme } from "vitepress";
4
4
  import consola from "consola";
5
5
  import { isUndefined } from "lodash-es";
6
- import { hasChangelogMd } from "../utils/copy-changelog";
7
- import { printFormat } from "@ruan-cat/utils";
6
+
7
+ import { hasChangelogMd } from "../utils/vitepress-project.ts";
8
8
 
9
9
  /**
10
10
  * 设置导航栏的变更日志
@@ -15,7 +15,7 @@ import { printFormat } from "@ruan-cat/utils";
15
15
  * @private 内部使用即可
16
16
  */
17
17
  export function handleChangeLog(userConfig: UserConfig<DefaultTheme.Config>) {
18
- if (!hasChangelogMd()) {
18
+ if (!hasChangelogMd(userConfig)) {
19
19
  consola.warn(` 未找到变更日志文件,不添加变更日志导航栏。 `);
20
20
  return;
21
21
  }
@@ -28,8 +28,4 @@ export function handleChangeLog(userConfig: UserConfig<DefaultTheme.Config>) {
28
28
  }
29
29
 
30
30
  nav.push({ text: "更新日志", link: "/CHANGELOG.md" });
31
-
32
- // TODO: 持续完成特殊侧边栏的处理
33
- // @ts-ignore
34
- console.log(" 查看侧边栏数据 ", printFormat(userConfig?.themeConfig.sidebar));
35
31
  }
@@ -8,3 +8,4 @@ export * from "./plugins";
8
8
  export * from "./changelog-nav";
9
9
  export * from "./prompts-nav";
10
10
  export * from "./teek";
11
+ export * from "./multi-sidebar";
@@ -0,0 +1,161 @@
1
+ // 多侧边栏配置 - 使用 Object.defineProperty 拦截实现透明多侧边栏
2
+
3
+ import { type UserConfig, type DefaultTheme } from "vitepress";
4
+ import { generateSidebar } from "vitepress-sidebar";
5
+ import path from "node:path";
6
+ import consola from "consola";
7
+ import { merge, cloneDeep } from "lodash-es";
8
+
9
+ import {
10
+ getVitepressProjectRoot,
11
+ getVitepressSourceDirectory,
12
+ hasPromptsIndexMd,
13
+ hasChangelogMd,
14
+ } from "../utils/vitepress-project.ts";
15
+
16
+ /** 从 generateSidebar 函数参数中提取类型 */
17
+ type VitePressSidebarOptions = Parameters<typeof generateSidebar>[0];
18
+
19
+ /** 默认侧边栏配置选项 */
20
+ export const defaultSidebarOptions: VitePressSidebarOptions = {
21
+ // 侧边栏需要折叠
22
+ collapsed: true,
23
+
24
+ // 用文件的 h1 标题作为侧边栏标题
25
+ useTitleFromFileHeading: true,
26
+
27
+ // 用index文件的标题作为折叠栏的标题
28
+ useFolderTitleFromIndexFile: true,
29
+
30
+ // 折叠栏链接到index文件
31
+ useFolderLinkFromIndexFile: true,
32
+
33
+ // 用order字段做菜单排序
34
+ sortMenusByFrontmatterOrder: true,
35
+
36
+ // 不使用名称排序
37
+ sortMenusByName: false,
38
+
39
+ useFolderLinkFromSameNameSubFile: true,
40
+
41
+ // 不输出调试信息了
42
+ debugPrint: false,
43
+ };
44
+
45
+ /**
46
+ * 获取合并后的侧边栏配置
47
+ */
48
+ export function getMergeSidebarOptions(options?: VitePressSidebarOptions) {
49
+ return merge({}, cloneDeep(defaultSidebarOptions), options ?? {});
50
+ }
51
+
52
+ /**
53
+ * 获取源目录相对于 process.cwd() 的路径
54
+ * @description 用于 vitepress-sidebar 的配置
55
+ */
56
+ export function getSourceDirRelativePathFromCwd(userConfig: UserConfig<DefaultTheme.Config>): string {
57
+ const projectRoot = getVitepressProjectRoot();
58
+ const sourceDir = getVitepressSourceDirectory(userConfig);
59
+ const relativePath = path.relative(process.cwd(), sourceDir);
60
+
61
+ consola.log("项目根目录:", projectRoot);
62
+ consola.log("源目录:", sourceDir);
63
+ consola.log("cwd:", process.cwd());
64
+ consola.log("源目录相对于 cwd 的路径:", relativePath);
65
+
66
+ if (!relativePath || relativePath.startsWith("..")) {
67
+ return ".";
68
+ }
69
+
70
+ return relativePath.replace(/\\/g, "/");
71
+ }
72
+
73
+ /**
74
+ * 递归为侧边栏项的 link 添加路径前缀
75
+ * @description
76
+ * generateSidebar 以子目录为根扫描时,生成的 link 缺少父路径前缀。
77
+ * 例如扫描 prompts 目录时,link 为 "/make-dynamic-routes",
78
+ * 需要补充为 "/prompts/make-dynamic-routes"。
79
+ */
80
+ function prefixSidebarLinks(items: any[], prefix: string): any[] {
81
+ return items.map((item) => {
82
+ const newItem = { ...item };
83
+ if (newItem.link && typeof newItem.link === "string") {
84
+ // 处理带和不带前导斜杠的情况
85
+ const link = newItem.link.startsWith("/") ? newItem.link : `/${newItem.link}`;
86
+ newItem.link = `${prefix}${link}`;
87
+ }
88
+ if (Array.isArray(newItem.items)) {
89
+ newItem.items = prefixSidebarLinks(newItem.items, prefix);
90
+ }
91
+ return newItem;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * 设置多侧边栏拦截
97
+ * @description
98
+ * 使用 Object.defineProperty 在 themeConfig.sidebar 上设置 getter/setter:
99
+ * - setter: 消费者赋值时,存储为业务侧边栏内容
100
+ * - getter: VitePress 读取时,返回包含 prompts/CHANGELOG 的多侧边栏对象
101
+ *
102
+ * 对消费者完全透明,无需修改任何消费者代码。
103
+ */
104
+ export function setupMultiSidebar(userConfig: UserConfig<DefaultTheme.Config>): void {
105
+ const hasPrompts = hasPromptsIndexMd(userConfig);
106
+ const hasChangelog = hasChangelogMd(userConfig);
107
+
108
+ if (!hasPrompts && !hasChangelog) {
109
+ consola.log("未检测到 prompts 目录或 CHANGELOG.md,不需要多侧边栏");
110
+ return;
111
+ }
112
+
113
+ const themeConfig = userConfig.themeConfig!;
114
+
115
+ // 预生成额外侧边栏
116
+ const extraSidebars: Record<string, any> = {};
117
+
118
+ if (hasPrompts) {
119
+ const sourceDirRelativePathFromCwd = getSourceDirRelativePathFromCwd(userConfig);
120
+ const promptsFullPath =
121
+ sourceDirRelativePathFromCwd === "." ? "prompts" : `${sourceDirRelativePathFromCwd}/prompts`;
122
+
123
+ consola.log("promptsFullPath:", promptsFullPath);
124
+
125
+ const promptsSidebar = generateSidebar(
126
+ getMergeSidebarOptions({
127
+ documentRootPath: promptsFullPath,
128
+ }),
129
+ );
130
+
131
+ extraSidebars["/prompts/"] = Array.isArray(promptsSidebar)
132
+ ? prefixSidebarLinks(promptsSidebar, "/prompts")
133
+ : promptsSidebar;
134
+ }
135
+
136
+ if (hasChangelog) {
137
+ extraSidebars["/CHANGELOG"] = [];
138
+ }
139
+
140
+ // 设置 getter/setter 拦截
141
+ let businessSidebar: any = themeConfig.sidebar;
142
+
143
+ Object.defineProperty(themeConfig, "sidebar", {
144
+ configurable: true,
145
+ enumerable: true,
146
+ get() {
147
+ const result: Record<string, any> = { ...extraSidebars };
148
+ if (Array.isArray(businessSidebar)) {
149
+ result["/"] = businessSidebar;
150
+ } else if (typeof businessSidebar === "object" && businessSidebar !== null) {
151
+ Object.assign(result, businessSidebar);
152
+ }
153
+ return result;
154
+ },
155
+ set(value: any) {
156
+ businessSidebar = value;
157
+ },
158
+ });
159
+
160
+ consola.success("多侧边栏 defineProperty 拦截已设置");
161
+ }
@@ -6,143 +6,7 @@ import { isUndefined, merge } from "lodash-es";
6
6
  import matter from "gray-matter";
7
7
 
8
8
  import { pageOrderConfig } from "./page-order-config";
9
-
10
- /** 提示词索引文件的相对路径 */
11
- const PROMPTS_INDEX_MD_PATH = "prompts/index.md" as const;
12
-
13
- /**
14
- * 从命令行参数中获取 VitePress 项目根目录
15
- * @description
16
- * 解析命令行参数,查找 vitepress dev/build 命令后的第一个路径参数
17
- * @private 目前仅设计为内部使用
18
- */
19
- function getProjectRootFromArgs(): string | null {
20
- const args = process.argv;
21
-
22
- // 查找 vitepress 命令的索引
23
- const vitepressIndex = args.findIndex((arg) => arg.includes("vitepress"));
24
-
25
- if (vitepressIndex === -1) {
26
- return null;
27
- }
28
-
29
- // 查找 dev 或 build 命令后的第一个非选项参数(不以 - 或 -- 开头)
30
- for (let i = vitepressIndex + 1; i < args.length; i++) {
31
- const arg = args[i];
32
-
33
- // 跳过命令本身(dev, build等)和选项参数
34
- if (arg === "dev" || arg === "build" || arg === "serve" || arg === "preview") {
35
- continue;
36
- }
37
-
38
- // 跳过选项和选项值
39
- if (arg.startsWith("-")) {
40
- // 如果是 --port 这样的选项,也跳过下一个值
41
- if (arg.startsWith("--") && !arg.includes("=")) {
42
- i++; // 跳过选项值
43
- }
44
- continue;
45
- }
46
-
47
- // 找到路径参数,解析为绝对路径
48
- return path.resolve(process.cwd(), arg);
49
- }
50
-
51
- return null;
52
- }
53
-
54
- /**
55
- * 获得 vitepress 项目的 `项目根目录 (project root)`
56
- * @description
57
- * 检查出当前运行vitepress build 命令时,所使用的项目根目录,并对外返回该目录。
58
- * @see https://vitepress.dev/zh/guide/routing#root-and-source-directory
59
- * @private 目前仅设计为内部使用
60
- */
61
- function getVitepressProjectRoot(): string {
62
- // 首先尝试从命令行参数获取
63
- const projectRootFromArgs = getProjectRootFromArgs();
64
- if (projectRootFromArgs) {
65
- const vitepressDir = path.join(projectRootFromArgs, ".vitepress");
66
- if (fs.existsSync(vitepressDir)) {
67
- consola.log("从命令行参数获取到 VitePress 项目根目录:", projectRootFromArgs);
68
- return projectRootFromArgs;
69
- }
70
- }
71
-
72
- // 如果命令行参数没有提供或无效,尝试向上查找 .vitepress 目录
73
- let currentDir = process.cwd();
74
- const root = path.parse(currentDir).root;
75
-
76
- while (currentDir !== root) {
77
- const vitepressDir = path.join(currentDir, ".vitepress");
78
- if (fs.existsSync(vitepressDir)) {
79
- consola.log("通过向上查找获取到 VitePress 项目根目录:", currentDir);
80
- return currentDir;
81
- }
82
- currentDir = path.dirname(currentDir);
83
- }
84
-
85
- // 检查根目录本身
86
- const vitepressDir = path.join(root, ".vitepress");
87
- if (fs.existsSync(vitepressDir)) {
88
- consola.log("在文件系统根目录找到 VitePress 项目根目录:", root);
89
- return root;
90
- }
91
-
92
- // 如果找不到 .vitepress 目录,回退到当前工作目录
93
- const fallbackDir = process.cwd();
94
- consola.warn("未找到 .vitepress 目录,回退到当前工作目录:", fallbackDir);
95
- return fallbackDir;
96
- }
97
-
98
- /**
99
- * 获取vitepress项目的源目录
100
- * @description
101
- * 获取vitepress项目的源目录 并对外返回该目录。
102
- *
103
- * 源目录的计算规则:
104
- * 1. 如果配置中指定了 srcDir,则源目录 = 项目根目录 + srcDir
105
- * 2. 如果没有指定 srcDir,则源目录 = 项目根目录
106
- *
107
- * @param userConfig - VitePress 用户配置对象
108
- * @see https://vitepress.dev/zh/guide/routing#source-directory
109
- * @private 目前仅设计为内部使用
110
- */
111
- function getVitepressSourceDirectory(userConfig: UserConfig<DefaultTheme.Config>): string {
112
- // 获取项目根目录
113
- const projectRoot = getVitepressProjectRoot();
114
-
115
- // 读取配置中的 srcDir(如果有)
116
- const srcDir = (userConfig as any).srcDir as string | undefined;
117
-
118
- // 如果配置了 srcDir,将其与项目根目录拼接;否则返回项目根目录
119
- if (srcDir) {
120
- const sourceDirectory = path.resolve(projectRoot, srcDir);
121
- consola.log("从配置中获取到 srcDir:", srcDir);
122
- consola.log("VitePress 源目录为:", sourceDirectory);
123
- return sourceDirectory;
124
- }
125
-
126
- // 默认情况下,源目录等于项目根目录
127
- consola.log("配置中未指定 srcDir,源目录等于项目根目录:", projectRoot);
128
- return projectRoot;
129
- }
130
-
131
- /**
132
- * 检查当前运行的 `源目录` 是否存在 `prompts/index.md` 文件
133
- * @description
134
- * 检查当前运行的 `源目录` 是否存在 `prompts/index.md` 文件,并对外返回检查结果。
135
- * @private 目前仅设计为内部使用·
136
- */
137
- function hasPromptsIndexMd(userConfig: UserConfig<DefaultTheme.Config>) {
138
- const sourceDir = getVitepressSourceDirectory(userConfig);
139
- const res = fs.existsSync(path.resolve(sourceDir, PROMPTS_INDEX_MD_PATH));
140
- if (!res) {
141
- consola.log("当前项目的vitepress源目录为:", sourceDir);
142
- consola.warn(`当前项目的vitepress源目录不存在 ${PROMPTS_INDEX_MD_PATH} 文件`);
143
- }
144
- return res;
145
- }
9
+ import { getVitepressSourceDirectory, hasPromptsIndexMd, PROMPTS_INDEX_MD_PATH } from "../utils/vitepress-project.ts";
146
10
 
147
11
  /**
148
12
  * 将YAML数据写入到提示词索引文件内
package/src/config.mts CHANGED
@@ -15,7 +15,14 @@ import { transformerTwoslash } from "@shikijs/vitepress-twoslash";
15
15
 
16
16
  import llmstxt, { copyOrDownloadAsMarkdownButtons } from "vitepress-plugin-llms";
17
17
 
18
- import { defaultTeekConfig, handleTeekConfig, handlePlugins, handleChangeLog, handlePrompts } from "./config/index.ts";
18
+ import {
19
+ defaultTeekConfig,
20
+ handleTeekConfig,
21
+ handlePlugins,
22
+ handleChangeLog,
23
+ handlePrompts,
24
+ setupMultiSidebar,
25
+ } from "./config/index.ts";
19
26
 
20
27
  /** @see https://vitepress-ext.leelaa.cn/Mermaid.html#扩展-md-插件 */
21
28
  import { MermaidPlugin } from "@leelaa/vitepress-plugin-extended";
@@ -71,9 +78,16 @@ function getMergeSidebarOptions(options?: VitePressSidebarOptions) {
71
78
  /**
72
79
  * 设置自动生成侧边栏的配置
73
80
  * @see https://vitepress-sidebar.cdget.com/zhHans/guide/options
81
+ *
82
+ * 注意:在 Windows 环境下,必须使用相对路径(如 "./docs")而不是绝对路径,
83
+ * 否则 vitepress-sidebar 内部的 path.join 会导致路径拼接错误。
74
84
  */
75
85
  export function setGenerateSidebar(options?: VitePressSidebarOptions) {
76
- return generateSidebar(getMergeSidebarOptions(options));
86
+ const merged = getMergeSidebarOptions(options);
87
+ if (!Array.isArray(merged)) {
88
+ merged.excludeByGlobPattern = [...(merged.excludeByGlobPattern ?? []), "**/prompts/**", "**/CHANGELOG.md"];
89
+ }
90
+ return generateSidebar(merged);
77
91
  }
78
92
 
79
93
  /** 默认用户配置 */
@@ -104,8 +118,8 @@ const defaultUserConfig: UserConfig<DefaultTheme.Config> = {
104
118
  level: "deep",
105
119
  },
106
120
 
107
- // 自动化侧边栏
108
- sidebar: setGenerateSidebar(),
121
+ // 自动化侧边栏(多侧边栏)
122
+ // sidebar 在 setUserConfig 中动态生成
109
123
 
110
124
  socialLinks: [{ icon: "github", link: "https://github.com/ruan-cat" }],
111
125
 
@@ -193,5 +207,8 @@ export function setUserConfig(
193
207
  // 设置 Teek 主题配置
194
208
  handleTeekConfig(resUserConfig, extraConfig);
195
209
 
210
+ // 设置多侧边栏拦截
211
+ setupMultiSidebar(resUserConfig);
212
+
196
213
  return resUserConfig;
197
214
  }
@@ -2,9 +2,14 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import consola from "consola";
4
4
 
5
- /** 检查当前运行的根目录 是否存在 CHANGELOG.md 文件 */
6
- export function hasChangelogMd() {
7
- const res = fs.existsSync(path.resolve(process.cwd(), "CHANGELOG.md"));
5
+ /**
6
+ * 检查当前运行的根目录是否存在 CHANGELOG.md 文件
7
+ * @description
8
+ * 此函数检查的是项目根目录(process.cwd()),而非 VitePress 的 source directory
9
+ */
10
+ function checkChangelogExists(): boolean {
11
+ const sourceDir = path.resolve(process.cwd(), "CHANGELOG.md");
12
+ const res = fs.existsSync(sourceDir);
8
13
  if (!res) {
9
14
  consola.log("当前项目根目录为:", process.cwd());
10
15
  consola.warn("当前项目根目录不存在 CHANGELOG.md 文件");
@@ -16,9 +21,10 @@ export function hasChangelogMd() {
16
21
  * 将 CHANGELOG.md 文件移动到指定要求的位置内
17
22
  * @description
18
23
  * 该函数相当于实现 `cpx CHANGELOG.md docs` 命令
24
+ * @param target - 目标文件夹
19
25
  */
20
- export function copyChangelogMd(/** 目标文件夹 */ target: string) {
21
- if (!hasChangelogMd()) {
26
+ export function copyChangelogMd(target: string) {
27
+ if (!checkChangelogExists()) {
22
28
  return;
23
29
  }
24
30
 
@@ -0,0 +1,196 @@
1
+ /**
2
+ * VitePress 项目工具函数
3
+ * @description 提供 VitePress 项目相关的公共工具函数,用于获取项目根目录、源目录等
4
+ */
5
+
6
+ import { type UserConfig, type DefaultTheme } from "vitepress";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import consola from "consola";
10
+
11
+ /** 提示词索引文件的相对路径 */
12
+ export const PROMPTS_INDEX_MD_PATH = "prompts/index.md" as const;
13
+
14
+ /** CHANGELOG.md 文件名 */
15
+ export const CHANGELOG_MD_FILENAME = "CHANGELOG.md" as const;
16
+
17
+ /**
18
+ * 从命令行参数中获取 VitePress 项目根目录
19
+ * @description
20
+ * 解析命令行参数,查找 vitepress dev/build 命令后的第一个路径参数
21
+ * 返回项目根目录(.vitepress 所在目录),而非源目录
22
+ *
23
+ * 支持的场景:
24
+ * 1. vitepress dev - 使用当前目录
25
+ * 2. vitepress dev docs - docs 是源目录,项目根目录在父目录
26
+ * 3. vitepress dev src/docs - src/docs 是源目录,项目根目录需要向上查找
27
+ */
28
+ export function getProjectRootFromArgs(): string | null {
29
+ const args = process.argv;
30
+ const vitepressIndex = args.findIndex((arg) => arg.includes("vitepress"));
31
+
32
+ if (vitepressIndex === -1) {
33
+ return null;
34
+ }
35
+
36
+ for (let i = vitepressIndex + 1; i < args.length; i++) {
37
+ const arg = args[i];
38
+
39
+ // 跳过命令关键字
40
+ if (arg === "dev" || arg === "build" || arg === "serve" || arg === "preview") {
41
+ continue;
42
+ }
43
+
44
+ // 跳过选项参数
45
+ if (arg.startsWith("-")) {
46
+ if (arg.startsWith("--") && !arg.includes("=")) {
47
+ i++; // 跳过选项值
48
+ }
49
+ continue;
50
+ }
51
+
52
+ // 解析传入的路径(可能是源目录或项目根目录)
53
+ let inputPath = path.resolve(process.cwd(), arg);
54
+
55
+ // 向上查找直到找到 .vitepress 目录或到达根目录
56
+ let currentDir = inputPath;
57
+ const root = path.parse(currentDir).root;
58
+
59
+ while (currentDir !== root) {
60
+ if (fs.existsSync(path.join(currentDir, ".vitepress"))) {
61
+ consola.log("从命令行参数获取到 VitePress 项目根目录:", currentDir);
62
+ return currentDir;
63
+ }
64
+ currentDir = path.dirname(currentDir);
65
+ }
66
+
67
+ // 检查根目录本身
68
+ if (fs.existsSync(path.join(root, ".vitepress"))) {
69
+ consola.log("从命令行参数获取到 VitePress 项目根目录:", root);
70
+ return root;
71
+ }
72
+
73
+ // 找不到,返回 null
74
+ return null;
75
+ }
76
+
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * 从指定目录向上查找 VitePress 项目根目录
82
+ * @description 递归向上查找包含 .vitepress 目录的目录
83
+ */
84
+ function findProjectRootFromDir(startDir: string): string | null {
85
+ const root = path.parse(startDir).root;
86
+ let currentDir = startDir;
87
+
88
+ while (currentDir !== root) {
89
+ const vitepressDir = path.join(currentDir, ".vitepress");
90
+ if (fs.existsSync(vitepressDir)) {
91
+ return currentDir;
92
+ }
93
+ currentDir = path.dirname(currentDir);
94
+ }
95
+
96
+ // 检查根目录本身
97
+ const rootVitepressDir = path.join(root, ".vitepress");
98
+ if (fs.existsSync(rootVitepressDir)) {
99
+ return root;
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ /**
106
+ * 获得 vitepress 项目的 `项目根目录 (project root)`
107
+ * @description
108
+ * 检查出当前运行 vitepress build 命令时,所使用的项目根目录,并对外返回该目录。
109
+ * @see https://vitepress.dev/zh/guide/routing#root-and-source-directory
110
+ */
111
+ export function getVitepressProjectRoot(): string {
112
+ // 首先尝试从命令行参数提供的路径开始查找
113
+ const projectRootFromArgs = getProjectRootFromArgs();
114
+ if (projectRootFromArgs) {
115
+ const foundRoot = findProjectRootFromDir(projectRootFromArgs);
116
+ if (foundRoot) {
117
+ consola.log("从命令行参数获取到 VitePress 项目根目录:", foundRoot);
118
+ return foundRoot;
119
+ }
120
+ }
121
+
122
+ // 如果命令行参数无效,使用当前工作目录查找
123
+ const foundFromCwd = findProjectRootFromDir(process.cwd());
124
+ if (foundFromCwd) {
125
+ consola.log("通过当前工作目录获取到 VitePress 项目根目录:", foundFromCwd);
126
+ return foundFromCwd;
127
+ }
128
+
129
+ // 如果找不到 .vitepress 目录,回退到命令行参数提供的路径(如果存在)
130
+ if (projectRootFromArgs) {
131
+ consola.warn("未找到 .vitepress 目录,回退到命令行参数路径:", projectRootFromArgs);
132
+ return projectRootFromArgs;
133
+ }
134
+
135
+ // 最后回退到当前工作目录
136
+ const fallbackDir = process.cwd();
137
+ consola.warn("未找到 .vitepress 目录,回退到当前工作目录:", fallbackDir);
138
+ return fallbackDir;
139
+ }
140
+
141
+ /**
142
+ * 获取 vitepress 项目的源目录
143
+ * @description
144
+ * 获取 vitepress 项目的源目录并对外返回该目录。
145
+ *
146
+ * 源目录的计算规则:
147
+ * 1. 如果配置中指定了 srcDir,则源目录 = 项目根目录 + srcDir
148
+ * 2. 如果没有指定 srcDir,则源目录 = 项目根目录
149
+ *
150
+ * @param userConfig - VitePress 用户配置对象
151
+ * @see https://vitepress.dev/zh/guide/routing#source-directory
152
+ */
153
+ export function getVitepressSourceDirectory(userConfig: UserConfig<DefaultTheme.Config>): string {
154
+ const projectRoot = getVitepressProjectRoot();
155
+ const srcDir = (userConfig as Record<string, unknown>).srcDir as string | undefined;
156
+
157
+ if (srcDir) {
158
+ const sourceDirectory = path.resolve(projectRoot, srcDir);
159
+ consola.log("从配置中获取到 srcDir:", srcDir);
160
+ consola.log("VitePress 源目录为:", sourceDirectory);
161
+ return sourceDirectory;
162
+ }
163
+
164
+ consola.log("配置中未指定 srcDir,源目录等于项目根目录:", projectRoot);
165
+ return projectRoot;
166
+ }
167
+
168
+ /**
169
+ * 检查源目录是否存在 prompts/index.md 文件
170
+ * @description
171
+ * 检查当前文档的运行目录内是否存在 prompts/index.md 文件
172
+ */
173
+ export function hasPromptsIndexMd(userConfig: UserConfig<DefaultTheme.Config>): boolean {
174
+ const sourceDir = getVitepressSourceDirectory(userConfig);
175
+ const res = fs.existsSync(path.resolve(sourceDir, PROMPTS_INDEX_MD_PATH));
176
+ if (!res) {
177
+ consola.log("当前项目的 vitepress 源目录为:", sourceDir);
178
+ consola.warn(`当前项目的 vitepress 源目录不存在 ${PROMPTS_INDEX_MD_PATH} 文件`);
179
+ }
180
+ return res;
181
+ }
182
+
183
+ /**
184
+ * 检查源目录是否存在 CHANGELOG.md 文件
185
+ * @description
186
+ * 检查当前文档的运行目录内是否存在 CHANGELOG.md 文件
187
+ */
188
+ export function hasChangelogMd(userConfig: UserConfig<DefaultTheme.Config>): boolean {
189
+ const sourceDir = getVitepressSourceDirectory(userConfig);
190
+ const res = fs.existsSync(path.resolve(sourceDir, CHANGELOG_MD_FILENAME));
191
+ if (!res) {
192
+ consola.log("当前项目的 vitepress 源目录为:", sourceDir);
193
+ consola.warn(`当前项目的 vitepress 源目录不存在 ${CHANGELOG_MD_FILENAME} 文件`);
194
+ }
195
+ return res;
196
+ }