openmanual 0.8.2 → 0.9.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/bin.js CHANGED
@@ -140,12 +140,21 @@ function mergeDefaults(config) {
140
140
  };
141
141
  }
142
142
 
143
+ //#endregion
144
+ //#region src/core/generator/callout-component.ts
145
+ function generateCalloutComponent() {
146
+ return `'use client';
147
+ export { Callout, CalloutTitle, CalloutDescription } from 'openmanual/components/callout';
148
+ `;
149
+ }
150
+
143
151
  //#endregion
144
152
  //#region src/core/generator/global-css.ts
145
153
  function generateGlobalCss(ctx) {
146
154
  const { config } = ctx;
147
155
  const primaryHue = config.theme?.primaryHue ?? 213;
148
156
  return `@import 'tailwindcss';
157
+ @source './node_modules/openmanual/dist/components/**/*.js';
149
158
  @import 'fumadocs-ui/style.css';
150
159
  @custom-variant dark (&:is(.dark, .dark *));
151
160
 
@@ -158,6 +167,29 @@ function generateGlobalCss(ctx) {
158
167
  --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */
159
168
  --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */
160
169
  --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */
170
+
171
+ /* Callout 类型色 */
172
+ --callout-info-bg: hsl(210, 35%, 94%);
173
+ --callout-info-border: hsl(212, 40%, 80%);
174
+ --callout-info-text: hsl(213, 45%, 35%);
175
+ --callout-warning-bg: hsl(38, 60%, 93%);
176
+ --callout-warning-border: hsl(36, 55%, 78%);
177
+ --callout-warning-text: hsl(28, 55%, 35%);
178
+ --callout-danger-bg: hsl(0, 50%, 94%);
179
+ --callout-danger-border: hsl(0, 45%, 82%);
180
+ --callout-danger-text: hsl(0, 50%, 38%);
181
+ --callout-check-bg: hsl(150, 35%, 93%);
182
+ --callout-check-border: hsl(152, 35%, 78%);
183
+ --callout-check-text: hsl(155, 40%, 32%);
184
+ --callout-tip-bg: hsl(150, 35%, 93%);
185
+ --callout-tip-border: hsl(152, 35%, 78%);
186
+ --callout-tip-text: hsl(155, 40%, 32%);
187
+ --callout-note-bg: hsl(215, 20%, 94%);
188
+ --callout-note-border: hsl(215, 22%, 82%);
189
+ --callout-note-text: hsl(215, 25%, 40%);
190
+ --callout-key-bg: hsl(30, 55%, 93%);
191
+ --callout-key-border: hsl(28, 50%, 78%);
192
+ --callout-key-text: hsl(25, 50%, 35%);
161
193
  }
162
194
  ${config.theme?.darkMode ?? true ? `
163
195
  .dark {
@@ -181,6 +213,29 @@ ${config.theme?.darkMode ?? true ? `
181
213
  --color-fd-accent-foreground: hsl(35, 12%, 88%);
182
214
  --color-fd-ring: hsl(30, 30%, 50%);
183
215
  --color-fd-overlay: hsla(25, 20%, 5%, 0.5);
216
+
217
+ /* Callout 类型色 */
218
+ --callout-info-bg: hsl(213, 25%, 16%);
219
+ --callout-info-border: hsl(213, 30%, 30%);
220
+ --callout-info-text: hsl(213, 40%, 72%);
221
+ --callout-warning-bg: hsl(32, 35%, 17%);
222
+ --callout-warning-border: hsl(30, 35%, 30%);
223
+ --callout-warning-text: hsl(35, 45%, 72%);
224
+ --callout-danger-bg: hsl(5, 25%, 17%);
225
+ --callout-danger-border: hsl(5, 30%, 30%);
226
+ --callout-danger-text: hsl(5, 40%, 72%);
227
+ --callout-check-bg: hsl(155, 22%, 16%);
228
+ --callout-check-border: hsl(155, 25%, 28%);
229
+ --callout-check-text: hsl(155, 35%, 68%);
230
+ --callout-tip-bg: hsl(155, 22%, 16%);
231
+ --callout-tip-border: hsl(155, 25%, 28%);
232
+ --callout-tip-text: hsl(155, 35%, 68%);
233
+ --callout-note-bg: hsl(215, 15%, 16%);
234
+ --callout-note-border: hsl(215, 18%, 28%);
235
+ --callout-note-text: hsl(215, 25%, 68%);
236
+ --callout-key-bg: hsl(30, 28%, 17%);
237
+ --callout-key-border: hsl(28, 25%, 30%);
238
+ --callout-key-text: hsl(25, 35%, 68%);
184
239
  }
185
240
 
186
241
  .dark body {
@@ -196,6 +251,21 @@ figure.shiki {
196
251
  figure.shiki > div {
197
252
  max-height: none;
198
253
  }
254
+
255
+ /* Mermaid 全屏操作栏按钮 hover */
256
+ .mermaid-toolbar-btn:hover {
257
+ background-color: var(--hover-bg) !important;
258
+ color: var(--hover-color) !important;
259
+ }
260
+
261
+ .mermaid-toolbar-btn:hover svg {
262
+ color: inherit;
263
+ }
264
+
265
+ /* Callout:去除 shadow */
266
+ [style*="--callout-color"] {
267
+ box-shadow: none;
268
+ }
199
269
  `;
200
270
  }
201
271
 
@@ -292,7 +362,7 @@ export default withMDX(config);
292
362
  //#endregion
293
363
  //#region src/core/generator/package-json.ts
294
364
  function getOpenManualVersion() {
295
- return "0.8.2";
365
+ return "0.9.0";
296
366
  }
297
367
  function generatePackageJson(ctx) {
298
368
  const openmanualVersion = getOpenManualVersion();
@@ -313,6 +383,7 @@ function generatePackageJson(ctx) {
313
383
  "fumadocs-core": "^16.7.7",
314
384
  "fumadocs-mdx": "^14.2.11",
315
385
  "fumadocs-ui": "^16.7.7",
386
+ "lucide-react": "^1.7.0",
316
387
  mermaid: "^11.4.0",
317
388
  next: "^16.2.1",
318
389
  "next-themes": "^0.4.6",
@@ -353,7 +424,8 @@ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
353
424
  import { Files, File, Folder } from 'fumadocs-ui/components/files';
354
425
  import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
355
426
  import { TypeTable } from 'fumadocs-ui/components/type-table';
356
- import { Mermaid } from '@/components/mermaid';${pageActionsEnabled ? "\nimport { PageActions } from '@/components/page-actions';" : ""}
427
+ import { Mermaid } from '@/components/mermaid';
428
+ import { Callout, CalloutTitle, CalloutDescription } from '@/components/callout';${pageActionsEnabled ? "\nimport { PageActions } from '@/components/page-actions';" : ""}
357
429
  ${allowedSlugsSnippet}
358
430
  export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
359
431
  const { slug } = await params;
@@ -384,7 +456,7 @@ ${pageActionsEnabled ? ` <div className="flex items-start justify-between g
384
456
  <DocsDescription>{page.data.description}</DocsDescription>
385
457
  )}`}
386
458
  <DocsBody data-content-area>
387
- <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />
459
+ <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid, Callout, CalloutTitle, CalloutDescription }} />
388
460
  </DocsBody>
389
461
  </DocsPage>
390
462
  );
@@ -438,7 +510,13 @@ import { Provider } from 'openmanual/components/provider';
438
510
  import type { ReactNode } from 'react';
439
511
 
440
512
  export function AppProvider({ children }: { children: ReactNode }) {
441
- return <Provider searchEnabled={${ctx.config.search?.enabled !== false}}>{children}</Provider>;
513
+ return (
514
+ <Provider
515
+ searchEnabled={${ctx.config.search?.enabled !== false}}
516
+ >
517
+ {children}
518
+ </Provider>
519
+ );
442
520
  }
443
521
  `;
444
522
  }
@@ -472,6 +550,17 @@ export async function GET(
472
550
  `;
473
551
  }
474
552
 
553
+ //#endregion
554
+ //#region src/core/generator/search-route.ts
555
+ function generateSearchRoute() {
556
+ return `import { source } from '@/lib/source';
557
+ import { createFromSource } from 'fumadocs-core/search/server';
558
+
559
+ export const revalidate = false;
560
+ export const { staticGET: GET } = createFromSource(source);
561
+ `;
562
+ }
563
+
475
564
  //#endregion
476
565
  //#region src/core/generator/source-config.ts
477
566
  function generateSourceConfig(_ctx) {
@@ -613,6 +702,10 @@ async function generateAll(ctx) {
613
702
  path: "lib/layout.tsx",
614
703
  content: generateLayout(ctx)
615
704
  },
705
+ {
706
+ path: "components/callout.tsx",
707
+ content: generateCalloutComponent()
708
+ },
616
709
  {
617
710
  path: "components/mermaid.tsx",
618
711
  content: generateMermaidComponent()
@@ -624,7 +717,13 @@ async function generateAll(ctx) {
624
717
  ...ctx.dev ? [{
625
718
  path: "app/api/raw/[...path]/route.ts",
626
719
  content: generateRawContentRoute()
627
- }] : [],
720
+ }, {
721
+ path: "app/api/search/route.ts",
722
+ content: generateSearchRoute()
723
+ }] : [{
724
+ path: "app/api/search/route.ts",
725
+ content: generateSearchRoute()
726
+ }],
628
727
  {
629
728
  path: "app/layout.tsx",
630
729
  content: generateRootLayout()
@@ -686,20 +785,34 @@ function generateDocsLayout(ctx) {
686
785
  const footerLine = footerText ? `\n footer: { children: '${footerText.replace(/'/g, "\\'")}' },` : "";
687
786
  const sidebar = config.sidebar;
688
787
  const hasSidebar = sidebar && sidebar.length > 0;
788
+ const iconNames = /* @__PURE__ */ new Set();
789
+ if (hasSidebar) for (const g of sidebar ?? []) {
790
+ if (g.icon) iconNames.add(g.icon);
791
+ for (const p of g.pages) if (p.icon) iconNames.add(p.icon);
792
+ }
793
+ const hasIcons = iconNames.size > 0;
794
+ const iconNameList = [...iconNames];
689
795
  const sidebarConfigSnippet = hasSidebar ? `\nconst sidebarConfig = ${JSON.stringify((sidebar ?? []).map((g) => ({
690
796
  group: g.group,
797
+ icon: g.icon,
691
798
  collapsed: g.collapsed,
692
- pages: g.pages.map((p) => ({ slug: p.slug }))
799
+ pages: g.pages.map((p) => ({
800
+ slug: p.slug,
801
+ icon: p.icon
802
+ }))
693
803
  })), null, 2)} as const;
804
+ ` : "";
805
+ const lucideImportLine = hasIcons ? `\nimport { ${iconNameList.join(", ")} } from 'lucide-react';` : "";
806
+ const iconMapSnippet = hasIcons ? `\nconst iconMap = {${iconNameList.map((name) => `\n ${name}: <${name} />,`).join("")}\n} as const;
694
807
  ` : "";
695
808
  return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
696
809
  import { baseOptions } from '@/lib/layout';
697
810
  import { source } from '@/lib/source';
698
- import type { ReactNode } from 'react';${hasSidebar ? "\nimport { restructureTree } from 'openmanual/utils/restructure-tree';" : ""}
699
- ${sidebarConfigSnippet}
811
+ import type { ReactNode } from 'react';${hasSidebar ? "\nimport { restructureTree } from 'openmanual/utils/restructure-tree';" : ""}${lucideImportLine}
812
+ ${sidebarConfigSnippet}${iconMapSnippet}
700
813
  const docsOptions = {
701
814
  ...baseOptions(),
702
- ${hasSidebar ? "tree: restructureTree(source.getPageTree(), sidebarConfig)," : "tree: source.getPageTree(),"}${githubLine}${linksLine}${footerLine}
815
+ ${hasSidebar ? hasIcons ? "tree: restructureTree(source.getPageTree(), sidebarConfig, iconMap)," : "tree: restructureTree(source.getPageTree(), sidebarConfig)," : "tree: source.getPageTree(),"}${githubLine}${linksLine}${footerLine}
703
816
  };
704
817
 
705
818
  export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
@@ -986,14 +1099,27 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
986
1099
  const tempDir = await ensureTempDir(cwd);
987
1100
  const appDir = getAppDir(cwd);
988
1101
  const contentDir = resolve(cwd, config.contentDir ?? "content");
989
- await generateAll({
1102
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1103
+ const openmanualRoot = process.env.OPENMANUAL_ROOT || resolve(__dirname, "..");
1104
+ const ctx = {
990
1105
  config,
991
1106
  projectDir: cwd,
992
1107
  appDir,
993
1108
  contentDir: config.contentDir ?? "content",
994
1109
  dev: true,
995
- ...process.env.OPENMANUAL_ROOT ? { openmanualRoot: process.env.OPENMANUAL_ROOT } : {}
996
- });
1110
+ openmanualRoot
1111
+ };
1112
+ if (process.env.OPENMANUAL_ROOT) await spawnInitialGenerate(openmanualRoot, cwd);
1113
+ else {
1114
+ await generateAll(ctx);
1115
+ await createSymlink(contentDir, resolve(appDir, "content"));
1116
+ const publicDir = resolve(cwd, "public");
1117
+ try {
1118
+ const { stat } = await import("node:fs/promises");
1119
+ await stat(publicDir);
1120
+ await createSymlink(publicDir, resolve(appDir, "public"));
1121
+ } catch {}
1122
+ }
997
1123
  try {
998
1124
  const unknownLangs = await checkCodeLangs(contentDir);
999
1125
  if (unknownLangs.length > 0) {
@@ -1002,13 +1128,6 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
1002
1128
  logger.warn("建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值");
1003
1129
  }
1004
1130
  } catch {}
1005
- await createSymlink(contentDir, resolve(appDir, "content"));
1006
- const publicDir = resolve(cwd, "public");
1007
- try {
1008
- const { stat } = await import("node:fs/promises");
1009
- await stat(publicDir);
1010
- await createSymlink(publicDir, resolve(appDir, "public"));
1011
- } catch {}
1012
1131
  logger.step("安装依赖...");
1013
1132
  await installDeps(appDir);
1014
1133
  logger.success("开发服务器启动中...");
@@ -1080,6 +1199,24 @@ const devCommand = new Command("dev").description("启动开发服务器").optio
1080
1199
  process.exit(1);
1081
1200
  }
1082
1201
  });
1202
+ function spawnInitialGenerate(openmanualRoot, cwd) {
1203
+ const child = spawn("node", [
1204
+ resolve(openmanualRoot, "dist/bin.js"),
1205
+ "_regenerate",
1206
+ "--cwd",
1207
+ cwd
1208
+ ], {
1209
+ stdio: "inherit",
1210
+ env: { ...process.env }
1211
+ });
1212
+ return new Promise((promiseResolve, promiseReject) => {
1213
+ child.on("exit", (code) => {
1214
+ if (code === 0) promiseResolve();
1215
+ else promiseReject(/* @__PURE__ */ new Error(`初始生成失败 (exit code: ${code})`));
1216
+ });
1217
+ child.on("error", promiseReject);
1218
+ });
1219
+ }
1083
1220
  function spawnRegenerate(openmanualRoot, cwd, nextChild) {
1084
1221
  if (nextChild.exitCode !== null) {
1085
1222
  logger.warn("Next.js 进程已退出,跳过重新生成");
@@ -1192,7 +1329,7 @@ const regenerateCommand = new Command("_regenerate").description("内部命令
1192
1329
  //#endregion
1193
1330
  //#region src/cli/bin.ts
1194
1331
  function getVersion() {
1195
- return "0.8.2";
1332
+ return "0.9.0";
1196
1333
  }
1197
1334
  const program = new Command();
1198
1335
  const commandName = basename(process.argv[1] ?? "openmanual");
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.ts","../src/core/generator/global-css.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/next-config.ts","../src/core/generator/package-json.ts","../src/core/generator/page.ts","../src/core/generator/page-actions-component.ts","../src/core/generator/postcss-config.ts","../src/core/generator/provider.ts","../src/core/generator/raw-content-route.ts","../src/core/generator/source-config.ts","../src/core/generator/tsconfig.ts","../src/core/generator/index.ts","../src/utils/copy-raw-markdown.ts","../src/utils/install-deps.ts","../src/utils/logger.ts","../src/utils/temp-dir.ts","../src/cli/commands/build.ts","../src/utils/check-code-langs.ts","../src/cli/commands/dev.ts","../src/cli/commands/preview.ts","../src/cli/commands/regenerate.ts","../src/cli/bin.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function buildTitleMap(config: OpenManualConfig): Record<string, string> {\n const map: Record<string, string> = {};\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n map[page.slug] = page.title;\n }\n }\n }\n return map;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n };\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateGlobalCss(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const primaryHue = config.theme?.primaryHue ?? 213;\n const darkMode = config.theme?.darkMode ?? true;\n\n const darkBlock = darkMode\n ? `\n.dark {\n --primary-hue: ${primaryHue};\n\n /* 温暖的深色皮革背景 */\n --color-fd-background: hsl(30, 18%, 10%);\n --color-fd-foreground: hsl(35, 15%, 90%);\n --color-fd-muted: hsl(30, 14%, 14%);\n --color-fd-muted-foreground: hsla(30, 10%, 65%, 0.8);\n --color-fd-popover: hsl(30, 16%, 13%);\n --color-fd-popover-foreground: hsl(35, 12%, 87%);\n --color-fd-card: hsl(30, 15%, 12%);\n --color-fd-card-foreground: hsl(35, 15%, 93%);\n --color-fd-border: hsla(30, 12%, 35%, 25%);\n --color-fd-primary: hsl(35, 20%, 92%);\n --color-fd-primary-foreground: hsl(30, 25%, 10%);\n --color-fd-secondary: hsl(30, 12%, 16%);\n --color-fd-secondary-foreground: hsl(35, 10%, 88%);\n --color-fd-accent: hsla(30, 15%, 30%, 35%);\n --color-fd-accent-foreground: hsl(35, 12%, 88%);\n --color-fd-ring: hsl(30, 30%, 50%);\n --color-fd-overlay: hsla(25, 20%, 5%, 0.5);\n}\n\n.dark body {\n background: linear-gradient(hsla(30, 30%, 15%, 0.4), transparent 20rem, transparent);\n}\n`\n : '';\n\n return `@import 'tailwindcss';\n@import 'fumadocs-ui/style.css';\n@custom-variant dark (&:is(.dark, .dark *));\n\n:root {\n --primary-hue: ${primaryHue};\n\n /* 护眼暖色阅读背景 */\n --color-fd-background: hsl(40, 22%, 96.5%); /* #faf9f6 纸张白 */\n --color-fd-foreground: hsl(0, 0%, 17.3%); /* #2c2c2c 柔黑 */\n --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */\n --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */\n --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */\n}\n${darkBlock}\n\n/* 代码块:去除 shadow,使用朴素边框;去除 max-height 限制 */\nfigure.shiki {\n box-shadow: none;\n}\n\nfigure.shiki > div {\n max-height: none;\n}\n`;\n}\n","import type { LogoConfig, OpenManualConfig } from '../config/schema.js';\n\nconst IMAGE_EXTENSIONS = ['.svg', '.png', '.jpg', '.jpeg', '.webp'];\n\nexport function isImagePath(value: string): boolean {\n if (value.startsWith('/')) return true;\n return IMAGE_EXTENSIONS.some((ext) => value.toLowerCase().endsWith(ext));\n}\n\nexport function resolveLogoPaths(logo: LogoConfig): { light: string; dark: string } {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo };\n }\n return { light: logo.light, dark: logo.dark };\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const logo = config.navbar?.logo ?? config.name;\n\n let logoProps: string;\n if (typeof logo === 'string' && isImagePath(logo)) {\n logoProps = `type=\"image\" src=\"${logo}\" alt=\"${config.name}\"`;\n } else if (typeof logo === 'object') {\n const { light, dark } = logo;\n if (light === dark) {\n logoProps = `type=\"image\" src=\"${light}\" alt=\"${config.name}\"`;\n } else {\n logoProps = `type=\"image\" srcLight=\"${light}\" srcDark=\"${dark}\" alt=\"${config.name}\"`;\n }\n } else {\n logoProps = `type=\"text\" text=\"${logo}\"`;\n }\n\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\nimport type { ReactNode } from 'react';\nimport { NavLogo } from 'openmanual/components/nav-layout';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: <NavLogo ${logoProps} /> as ReactNode,\n },\n };\n}\n`;\n}\n","export function generateLibSource(): string {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n});\n`;\n}\n","export function generateMermaidComponent(): string {\n return `'use client';\nexport { Mermaid } from 'openmanual/components/mermaid';\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig; dev?: boolean }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n\n // dev 模式下不设置 output: 'export'(不兼容 API 路由和 rewrites)\n const outputLine = !ctx.dev && siteUrl ? `\\n output: 'export',` : '';\n // dev 模式下添加 rewrites 将 .md 请求代理到 API 路由\n const rewritesBlock = ctx.dev\n ? `\\n async rewrites() {\\n return [{ source: '/:path(.+)\\\\\\\\.md', destination: '/api/raw/:path' }];\\n },`\n : '';\n\n return `import { createMDX } from 'fumadocs-mdx/next';\n\nconst withMDX = createMDX();\n\n/** @type {import('next').NextConfig} */\nconst config = {\n reactStrictMode: true,${outputLine}\n serverExternalPackages: ['mermaid'],\n images: {\n unoptimized: true,\n },${rewritesBlock}\n};\n\nexport default withMDX(config);\n`;\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { OpenManualConfig } from '../config/schema.js';\n\ndeclare const __VERSION__: string | undefined;\n\nfunction getOpenManualVersion(): string {\n if (typeof __VERSION__ !== 'undefined') {\n return __VERSION__;\n }\n // fallback: 测试环境或直接 tsx 运行时使用\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n}\n\nexport function generatePackageJson(ctx: {\n config: OpenManualConfig;\n projectDir: string;\n appDir?: string;\n dev?: boolean;\n openmanualRoot?: string;\n}): string {\n const openmanualVersion = getOpenManualVersion();\n\n let openmanualDep: string;\n if (ctx.openmanualRoot && ctx.appDir) {\n const relPath = relative(ctx.appDir, ctx.openmanualRoot);\n openmanualDep = `file:${relPath}`;\n } else {\n openmanualDep = `^${openmanualVersion}`;\n }\n\n const pkg = {\n name: 'openmanual-app',\n type: 'module',\n private: true,\n scripts: {\n dev: 'next dev',\n build: 'next build',\n start: 'next start',\n },\n dependencies: {\n '@tailwindcss/postcss': '^4.1.15',\n 'fumadocs-core': '^16.7.7',\n 'fumadocs-mdx': '^14.2.11',\n 'fumadocs-ui': '^16.7.7',\n mermaid: '^11.4.0',\n next: '^16.2.1',\n 'next-themes': '^0.4.6',\n openmanual: openmanualDep,\n postcss: '^8.5.8',\n react: '^19.1.0',\n 'react-dom': '^19.1.0',\n tailwindcss: '^4.1.15',\n zod: '^4.0.0',\n },\n devDependencies: {\n '@types/react': '^19.1.0',\n '@types/react-dom': '^19.1.0',\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n","import { collectConfiguredSlugs, type OpenManualConfig } from '../config/schema.js';\n\nexport function generatePage(_ctx: { config: OpenManualConfig }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;\n\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set(${JSON.stringify([...collectConfiguredSlugs(_ctx.config)])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug ? slug.join('/') : 'index';\n return allowedSlugs.has(key);\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug)) {\n notFound();\n }\n`\n : '';\n\n const filterInStaticParams = isStrict\n ? `\nexport function generateStaticParams() {\n let params = source.generateParams();\n params = params.filter((p: { slug: string[] }) => isAllowed(p.slug));\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const pageTitleArea = pageActionsEnabled\n ? ` <div className=\"flex items-start justify-between gap-4\">\n <div>\n <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}\n </div>\n <PageActions />\n </div>`\n : ` <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}`;\n\n return `import { source } from '@/lib/source';\nimport { notFound } from 'next/navigation';\nimport { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';\nimport defaultMdxComponents from 'fumadocs-ui/mdx';\nimport { Steps, Step } from 'fumadocs-ui/components/steps';\nimport { Tabs, Tab } from 'fumadocs-ui/components/tabs';\nimport { Files, File, Folder } from 'fumadocs-ui/components/files';\nimport { Accordion, Accordions } from 'fumadocs-ui/components/accordion';\nimport { TypeTable } from 'fumadocs-ui/components/type-table';\nimport { Mermaid } from '@/components/mermaid';${pageActionsImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n const page = source.getPage(slug);\n${filterInPage}\n if (!page) {\n notFound();\n }\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc}>\n${pageTitleArea}\n <DocsBody data-content-area>\n <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />\n </DocsBody>\n </DocsPage>\n );\n}\n${filterInStaticParams}\n`;\n}\n","export function generatePageActionsComponent(): string {\n return `'use client';\nexport { PageActions } from 'openmanual/components/page-actions';\n`;\n}\n","export function generatePostcssConfig(): string {\n return `/** @type {import('postcss-load-config').Config} */\nconst config = {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n};\n\nexport default config;\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n\n return `'use client';\nimport { Provider } from 'openmanual/components/provider';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children }: { children: ReactNode }) {\n return <Provider searchEnabled={${searchEnabled}}>{children}</Provider>;\n}\n`;\n}\n","export function generateRawContentRoute(): string {\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nexport async function GET(\n _request: Request,\n { params }: { params: Promise<{ path: string[] }> },\n) {\n const { path: segments } = await params;\n const slug = segments.join('/');\n for (const ext of ['.mdx', '.md']) {\n try {\n const filePath = join(process.cwd(), 'content', \\`\\${slug}\\${ext}\\`);\n const content = await readFile(filePath, 'utf-8');\n return new NextResponse(content, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n });\n } catch {\n /* try next extension */\n }\n }\n return new NextResponse('Not found', { status: 404 });\n}\n`;\n}\n","import { buildTitleMap, collectConfiguredSlugs, type OpenManualConfig } from '../config/schema.js';\n\nexport function generateSourceConfig(_ctx: { config: OpenManualConfig }): string {\n const titleMap = buildTitleMap(_ctx.config);\n const titleMapEntries = Object.entries(titleMap)\n .map(([slug, title]) => ` '${slug}': '${title.replace(/'/g, \"\\\\'\")}'`)\n .join(',\\n');\n const titleMapStr = titleMapEntries ? `{\\n${titleMapEntries}\\n}` : '{}';\n\n const isStrict = _ctx.config.contentPolicy !== 'all';\n\n const allowedSlugsSnippet = isStrict\n ? `\n\nconst allowedSlugs = new Set(${JSON.stringify([...collectConfiguredSlugs(_ctx.config)])});\n\nfunction slugFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n return relative.replace(/\\\\.(md|mdx)$/i, '');\n}\n`\n : '';\n\n const filterSnippet = isStrict\n ? `\n .refine((_data) => {\n const slug = slugFromPath(ctx.path);\n if (allowedSlugs.size > 0 && !allowedSlugs.has(slug)) {\n return false;\n }\n return true;\n })`\n : '';\n\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\nimport { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';\nimport { z } from 'zod';\n\nconst titleMap: Record<string, string> = ${titleMapStr};${allowedSlugsSnippet}\nfunction titleFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n const slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;\n}\n\nexport const docs = defineDocs({\n dir: 'content',\n docs: {\n schema: (ctx) =>\n z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n full: z.boolean().optional(),\n }).transform((data) => ({\n ...data,\n title: data.title ?? titleFromPath(ctx.path),\n }))${filterSnippet},\n },\n});\n\nexport default defineConfig({\n mdxOptions: {\n remarkPlugins: [remarkMdxMermaid],\n rehypeCodeOptions: {\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n defaultColor: false,\n fallbackLanguage: 'text',\n },\n },\n});\n`;\n}\n","export function generateTsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n lib: ['dom', 'dom.iterable', 'esnext'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n jsx: 'react-jsx',\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n isolatedModules: true,\n incremental: true,\n plugins: [{ name: 'next' }],\n paths: {\n '@/*': ['./*'],\n },\n },\n include: [\n '**/*.ts',\n '**/*.tsx',\n 'next-env.d.ts',\n '.next/types/**/*.ts',\n '.next/dev/types/**/*.ts',\n ],\n exclude: ['node_modules'],\n },\n null,\n 2\n )}\\n`;\n}\n","import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateNextConfig } from './next-config.js';\nimport { generatePackageJson } from './package-json.js';\nimport { generatePage } from './page.js';\nimport { generatePageActionsComponent } from './page-actions-component.js';\nimport { generatePostcssConfig } from './postcss-config.js';\nimport { generateProvider } from './provider.js';\nimport { generateRawContentRoute } from './raw-content-route.js';\nimport { generateSourceConfig } from './source-config.js';\nimport { generateTsconfig } from './tsconfig.js';\n\nexport interface GenerateContext {\n config: OpenManualConfig;\n /** Absolute path to user's project root */\n projectDir: string;\n /** Absolute path to .openmanual/app */\n appDir: string;\n /** Content directory relative to project root */\n contentDir: string;\n /** 开发模式标志,dev 模式下不设置 output: 'export',生成 API 路由和 rewrites */\n dev?: boolean;\n /** openmanual 项目根目录,dev 模式下用于 file: 链接到本地构建产物 */\n openmanualRoot?: string;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const files: Array<{ path: string; content: string }> = [\n {\n path: 'source.config.ts',\n content: generateSourceConfig(ctx),\n },\n {\n path: 'next.config.mjs',\n content: generateNextConfig(ctx),\n },\n {\n path: 'global.css',\n content: generateGlobalCss(ctx),\n },\n {\n path: 'package.json',\n content: generatePackageJson(ctx),\n },\n {\n path: 'tsconfig.json',\n content: generateTsconfig(),\n },\n {\n path: 'postcss.config.mjs',\n content: generatePostcssConfig(),\n },\n {\n path: 'lib/source.ts',\n content: generateLibSource(),\n },\n {\n path: 'lib/layout.tsx',\n content: generateLayout(ctx),\n },\n {\n path: 'components/mermaid.tsx',\n content: generateMermaidComponent(),\n },\n {\n path: 'components/page-actions.tsx',\n content: generatePageActionsComponent(),\n },\n // 仅在 dev 模式生成 API 路由(生产构建中 output: 'export' 不兼容 API 路由)\n ...(ctx.dev\n ? [{ path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute() }]\n : []),\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n\n for (const file of files) {\n const fullPath = join(ctx.appDir, file.path);\n const dir = join(fullPath, '..');\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, file.content, 'utf-8');\n }\n\n // Generate logo SVG in public/ when logo is an image path\n const logo = ctx.config.navbar?.logo;\n if (logo && typeof logo === 'string' && isImagePath(logo)) {\n await ensureLogoFile(ctx, logo, 'light');\n } else if (logo && typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (isImagePath(light)) {\n await ensureLogoFile(ctx, light, 'light');\n }\n if (isImagePath(dark) && dark !== light) {\n await ensureLogoFile(ctx, dark, 'dark');\n }\n }\n\n // Generate meta.json for each sidebar group directory\n await generateMetaFiles(ctx);\n}\n\nfunction generateRootLayout(): string {\n return `import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <AppLayout>\n <AppProvider>{children}</AppProvider>\n </AppLayout>\n );\n}\n`;\n}\n\nfunction generateDocsLayout(ctx: GenerateContext): string {\n const { config } = ctx;\n const githubLink = config.navbar?.github ?? '';\n const navLinks = config.navbar?.links ?? [];\n const footerText = config.footer?.text ?? '';\n\n const linksArray = navLinks.map((l) => ({\n text: l.label,\n url: l.href,\n external: true,\n }));\n\n const githubLine = githubLink ? `\\n github: '${githubLink}',` : '';\n\n const linksLine = linksArray.length > 0 ? `\\n links: ${JSON.stringify(linksArray)},` : '';\n\n const footerLine = footerText\n ? `\\n footer: { children: '${footerText.replace(/'/g, \"\\\\'\")}' },`\n : '';\n\n // Build sidebar config for tree restructuring (only needed fields)\n const sidebar = config.sidebar;\n const hasSidebar = sidebar && sidebar.length > 0;\n\n const sidebarConfigSnippet = hasSidebar\n ? `\\nconst sidebarConfig = ${JSON.stringify(\n (sidebar ?? []).map((g) => ({\n group: g.group,\n collapsed: g.collapsed,\n pages: g.pages.map((p) => ({ slug: p.slug })),\n })),\n null,\n 2\n )} as const;\n`\n : '';\n\n const treeLine = hasSidebar\n ? 'tree: restructureTree(source.getPageTree(), sidebarConfig),'\n : 'tree: source.getPageTree(),';\n\n const restructureTreeImport = hasSidebar\n ? \"\\nimport { restructureTree } from 'openmanual/utils/restructure-tree';\"\n : '';\n\n return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout';\nimport { source } from '@/lib/source';\nimport type { ReactNode } from 'react';${restructureTreeImport}\n${sidebarConfigSnippet}\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${githubLine}${linksLine}${footerLine}\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\nexport function generateOpenManualLogoSvg(\n name: string,\n variant: 'light' | 'dark' = 'light'\n): string {\n const textColor = variant === 'dark' ? '#E8E0D4' : '#000000';\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 190 32\" width=\"190\" height=\"32\">\n <text x=\"0\" y=\"25\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\" font-size=\"32\" font-weight=\"700\">\n <tspan fill=\"#2B7A4B\" font-size=\"34\">${name.charAt(0)}</tspan><tspan fill=\"${textColor}\">${name.slice(1)}</tspan>\n </text>\n</svg>\n`;\n}\n\nasync function ensureLogoFile(\n ctx: GenerateContext,\n logoPath: string,\n variant: 'light' | 'dark'\n): Promise<void> {\n const userLogoPath = join(ctx.projectDir, 'public', logoPath.replace(/^\\//, ''));\n try {\n await access(userLogoPath);\n } catch {\n const publicDir = join(ctx.appDir, 'public');\n await mkdir(publicDir, { recursive: true });\n const fullPath = join(publicDir, logoPath.replace(/^\\//, ''));\n await mkdir(join(fullPath, '..'), { recursive: true });\n await writeFile(fullPath, generateOpenManualLogoSvg(ctx.config.name, variant), 'utf-8');\n }\n}\n\n/**\n * Generate meta.json for each sidebar group directory so that\n * fumadocs displays the configured Chinese group name instead of\n * auto-capitalizing the English directory name.\n */\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\n const sidebar = ctx.config.sidebar;\n if (!sidebar || sidebar.length === 0) return;\n\n const contentAbsDir = join(ctx.projectDir, ctx.contentDir);\n\n for (const group of sidebar) {\n // Extract directory prefix from the first page slug that contains \"/\"\n const dirPrefix = group.pages\n .map((p) => p.slug)\n .find((slug) => slug.includes('/'))\n ?.split('/')[0];\n\n if (!dirPrefix) continue; // Root-level pages, no meta.json needed\n\n const dirPath = join(contentAbsDir, dirPrefix);\n const metaPath = join(dirPath, 'meta.json');\n\n // Skip if meta.json already exists\n try {\n await access(metaPath);\n continue;\n } catch {\n // File doesn't exist, proceed to create it\n }\n\n await mkdir(dirPath, { recursive: true });\n await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\\n`, 'utf-8');\n }\n}\n","import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';\nimport { join, parse } from 'node:path';\n\n/**\n * Recursively copy .mdx/.md files from contentDir to targetDir,\n * renaming extensions to .md. Non-markdown files are skipped.\n */\nexport async function copyRawMarkdown(contentDir: string, targetDir: string): Promise<void> {\n await mkdir(targetDir, { recursive: true });\n\n const entries = await readdir(contentDir);\n for (const entry of entries) {\n const srcPath = join(contentDir, entry);\n const srcStat = await stat(srcPath);\n\n if (srcStat.isDirectory()) {\n await copyRawMarkdown(srcPath, join(targetDir, entry));\n } else {\n const ext = parse(entry).ext.toLowerCase();\n if (ext === '.mdx' || ext === '.md') {\n const { name } = parse(entry);\n const destPath = join(targetDir, `${name}.md`);\n await copyFile(srcPath, destPath);\n }\n }\n }\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport async function installDeps(appDir: string): Promise<void> {\n const nodeModules = resolve(appDir, 'node_modules');\n\n // Skip install if node_modules already exists\n if (existsSync(nodeModules)) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn('pnpm', ['install', '--no-frozen-lockfile', '--ignore-workspace'], {\n cwd: appDir,\n stdio: 'pipe',\n env: { ...process.env },\n });\n\n let stderr = '';\n child.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`pnpm install failed: ${stderr}`));\n }\n });\n });\n}\n","const COLORS = {\n reset: '\\x1b[0m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString('zh-CN', { hour12: false });\n}\n\nexport const logger = {\n info(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}info${COLORS.reset} ${msg}`\n );\n },\n\n success(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.green}done${COLORS.reset} ${msg}`\n );\n },\n\n warn(msg: string): void {\n console.warn(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}warn${COLORS.reset} ${msg}`\n );\n },\n\n error(msg: string): void {\n console.error(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.red}error${COLORS.reset} ${msg}`\n );\n },\n\n step(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.bold}→${COLORS.reset} ${msg}`\n );\n },\n};\n","import { existsSync } from 'node:fs';\nimport { lstat, mkdir, rm, symlink } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst TEMP_DIR_NAME = '.openmanual';\n\nexport function getTempDir(cwd: string): string {\n return join(cwd, TEMP_DIR_NAME);\n}\n\nexport function getAppDir(cwd: string): string {\n return join(getTempDir(cwd), 'app');\n}\n\nexport async function ensureTempDir(cwd: string): Promise<string> {\n const tempDir = getTempDir(cwd);\n const appDir = getAppDir(cwd);\n\n await mkdir(tempDir, { recursive: true });\n await mkdir(join(appDir, 'app'), { recursive: true });\n\n return tempDir;\n}\n\nexport async function cleanTempDir(cwd: string): Promise<void> {\n const tempDir = getTempDir(cwd);\n if (existsSync(tempDir)) {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\nexport async function createSymlink(target: string, linkPath: string): Promise<void> {\n const resolvedTarget = resolve(target);\n const resolvedLink = resolve(linkPath);\n\n try {\n await lstat(resolvedLink);\n // Remove existing symlink or directory\n await rm(resolvedLink, { recursive: true, force: true });\n } catch {\n // link doesn't exist, that's fine\n }\n\n await symlink(resolvedTarget, resolvedLink, 'junction');\n}\n","import { spawn } from 'node:child_process';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { copyRawMarkdown } from '../../utils/copy-raw-markdown.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { cleanTempDir, createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const buildCommand = new Command('build').description('构建静态站点').action(async () => {\n const cwd = process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n await ensureTempDir(cwd);\n\n // 从 CLI 位置推导 openmanual 包根目录(dist/bin.js → 上溯 1 级到包根目录)\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const openmanualRoot = resolve(__dirname, '..');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n openmanualRoot,\n };\n\n await generateAll(ctx);\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.step('构建静态站点...');\n const buildResult = spawn('npx', ['next', 'build'], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n await new Promise<void>((resolve, reject) => {\n buildResult.on('error', reject);\n buildResult.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Build failed with code ${code}`));\n }\n });\n });\n\n // Copy output to user's output dir\n const outputDir = resolve(cwd, config.outputDir ?? 'dist');\n await mkdir(outputDir, { recursive: true });\n\n const nextOutput = resolve(appDir, 'out');\n try {\n await cp(nextOutput, outputDir, { recursive: true });\n logger.success(`静态站点已输出到: ${outputDir}`);\n } catch {\n // If no 'out' dir, check .next/static\n logger.warn('未找到静态导出产物,请检查 next.config.mjs 中 output: \"export\" 配置');\n }\n\n logger.step('复制原始 Markdown 文件...');\n await copyRawMarkdown(contentDir, outputDir);\n\n logger.step('清理临时文件...');\n await cleanTempDir(cwd);\n\n logger.success('构建完成!');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n});\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\n\nexport interface UnknownLang {\n file: string;\n line: number;\n lang: string;\n}\n\nexport async function checkCodeLangs(contentDir: string): Promise<UnknownLang[]> {\n const { bundledLanguages } = await import('shiki');\n const supportedLangs = new Set(Object.keys(bundledLanguages));\n supportedLangs.add('text');\n supportedLangs.add('txt');\n supportedLangs.add('plaintext');\n supportedLangs.add('plain');\n supportedLangs.add('ansi');\n\n const files = await collectMdFiles(contentDir);\n const results: UnknownLang[] = [];\n\n for (const file of files) {\n const content = await readFile(file, 'utf-8');\n const lines = content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line) continue;\n const match = line.match(/^```(\\S+)/);\n if (match) {\n const lang = match[1];\n if (!lang) continue;\n if (!supportedLangs.has(lang)) {\n results.push({\n file: relative(contentDir, file).split(sep).join('/'),\n line: i + 1,\n lang,\n });\n }\n }\n }\n }\n\n return results;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectMdFiles(fullPath)));\n } else if (entry.isFile() && /\\.(md|mdx)$/i.test(entry.name)) {\n files.push(fullPath);\n }\n }\n\n return files;\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { extname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { checkCodeLangs } from '../../utils/check-code-langs.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const devCommand = new Command('dev')\n .description('启动开发服务器')\n .option('-p, --port <port>', '端口号', '3000')\n .option('--watch', '监听框架源码变更并自动重新生成', false)\n .option('--cwd <path>', '项目目录(watch 模式下使用)')\n .action(async (options) => {\n const cwd = options.cwd ? resolve(options.cwd) : process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const tempDir = await ensureTempDir(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n dev: true,\n ...(process.env.OPENMANUAL_ROOT ? { openmanualRoot: process.env.OPENMANUAL_ROOT } : {}),\n };\n\n await generateAll(ctx);\n\n // Check for unsupported code block languages\n try {\n const unknownLangs = await checkCodeLangs(contentDir);\n if (unknownLangs.length > 0) {\n logger.warn('以下文件使用了不认识的代码块语言:');\n for (const item of unknownLangs) {\n logger.warn(` ${item.file}:${item.line} - \"${item.lang}\"`);\n }\n logger.warn('建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值');\n }\n } catch {\n // skip check if shiki is not available\n }\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.success('开发服务器启动中...');\n logger.info(`内容目录: ${contentDir}`);\n logger.info(`临时目录: ${tempDir}`);\n logger.info(`端口: ${options.port}`);\n\n const nextChild = spawn('npx', ['next', 'dev', '--port', options.port], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n nextChild.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n nextChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n process.exit(code);\n }\n });\n\n // Watch mode: monitor framework source and config changes\n let watcher: InstanceType<typeof import('chokidar').FSWatcher> | undefined;\n let regenTimer: ReturnType<typeof setTimeout> | undefined;\n\n if (options.watch) {\n const openmanualRoot = process.env.OPENMANUAL_ROOT;\n if (!openmanualRoot) {\n logger.warn('OPENMANUAL_ROOT 未设置,无法监听框架源码变更');\n } else {\n const chokidar = await import('chokidar');\n const srcDir = resolve(openmanualRoot, 'src');\n const configFile = resolve(cwd, 'openmanual.json');\n\n watcher = chokidar.watch(srcDir, {\n ignoreInitial: true,\n ignored: [\n '**/__tests__/**',\n '**/*.test.ts',\n (path: string) => {\n const ext = extname(path);\n if (!ext) return false; // 无扩展名 = 目录,不忽略\n return ext !== '.ts' && ext !== '.tsx';\n },\n ],\n });\n watcher.add(configFile);\n\n watcher.on('all', (event, filePath) => {\n if (event === 'add' || event === 'change' || event === 'unlink') {\n logger.info(`检测到变更: ${filePath}`);\n clearTimeout(regenTimer);\n regenTimer = setTimeout(() => {\n spawnRegenerate(openmanualRoot, cwd, nextChild);\n }, 300);\n }\n });\n\n logger.success('Watch 模式已启用,监听框架源码和配置变更');\n }\n }\n\n // Handle graceful shutdown\n const cleanup = () => {\n clearTimeout(regenTimer);\n watcher?.close();\n nextChild.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n\nfunction spawnRegenerate(openmanualRoot: string, cwd: string, nextChild: ChildProcess): void {\n if (nextChild.exitCode !== null) {\n logger.warn('Next.js 进程已退出,跳过重新生成');\n return;\n }\n\n logger.step('重新生成文件...');\n\n const binPath = resolve(openmanualRoot, 'dist/bin.js');\n const child = spawn('node', [binPath, '_regenerate', '--cwd', cwd], {\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n child.on('exit', (code) => {\n if (code === 0) {\n logger.success('文件重新生成完成');\n } else {\n logger.error(`重新生成失败 (exit code: ${code})`);\n }\n });\n\n child.on('error', (err) => {\n logger.error(`重新生成进程错误: ${err.message}`);\n });\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const previewCommand = new Command('preview')\n .description('预览构建产物')\n .option('-p, --port <port>', '端口号', '8080')\n .option('-d, --dir <dir>', '产物目录')\n .action(async (options) => {\n const cwd = process.cwd();\n\n try {\n let outputDir = options.dir;\n if (!outputDir) {\n const config = await loadConfig(cwd);\n outputDir = resolve(cwd, config.outputDir ?? 'dist');\n }\n\n if (!existsSync(outputDir)) {\n logger.error(`产物目录不存在: ${outputDir}`);\n logger.info('请先运行 openmanual build');\n process.exit(1);\n }\n\n logger.info(`预览目录: ${outputDir}`);\n logger.info(`预览地址: http://localhost:${options.port}`);\n\n const child = spawn('npx', ['serve', outputDir, '-p', options.port], {\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n child.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n const cleanup = () => {\n child.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n","import { rm } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const regenerateCommand = new Command('_regenerate')\n .description('内部命令:重新生成文件')\n .helpOption(false)\n .option('--cwd <path>', '项目目录')\n .action(async (options) => {\n const cwd = options.cwd ?? process.cwd();\n\n try {\n const config = await loadConfig(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n ...(process.env.OPENMANUAL_ROOT ? { openmanualRoot: process.env.OPENMANUAL_ROOT } : {}),\n };\n\n await ensureTempDir(cwd);\n\n // 清理旧的生成物,避免残留文件导致冲突\n const entriesToClean = [\n 'app',\n 'lib',\n 'source.config.ts',\n 'next.config.mjs',\n 'global.css',\n 'package.json',\n 'tsconfig.json',\n 'postcss.config.mjs',\n ];\n for (const entry of entriesToClean) {\n await rm(join(appDir, entry), { recursive: true, force: true });\n }\n\n await generateAll(ctx);\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n console.log('[openmanual] regenerate:ok');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`[openmanual] regenerate:fail ${message}`);\n process.exit(1);\n }\n });\n","import { readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { buildCommand } from './commands/build.js';\nimport { devCommand } from './commands/dev.js';\nimport { previewCommand } from './commands/preview.js';\nimport { regenerateCommand } from './commands/regenerate.js';\n\ndeclare const __VERSION__: string;\n\nfunction getVersion(): string {\n if (typeof __VERSION__ !== 'undefined') {\n return __VERSION__;\n }\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n } catch {\n // tsx dev 模式下 __dirname 是 src/cli,需要多上一层\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n } catch {\n return '0.0.0';\n }\n }\n}\n\nconst program = new Command();\nconst commandName = basename(process.argv[1] ?? 'openmanual');\n\nprogram\n .name(commandName)\n .description('AI 友好的开源文档系统框架')\n .version(getVersion(), '-v, --version');\n\nprogram.addCommand(devCommand);\nprogram.addCommand(buildCommand);\nprogram.addCommand(previewCommand);\nprogram.addCommand(regenerateCommand, { hidden: true });\n\nprogram.parse();\n"],"mappings":"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"}
1
+ {"version":3,"file":"bin.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.ts","../src/core/generator/callout-component.ts","../src/core/generator/global-css.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/next-config.ts","../src/core/generator/package-json.ts","../src/core/generator/page.ts","../src/core/generator/page-actions-component.ts","../src/core/generator/postcss-config.ts","../src/core/generator/provider.ts","../src/core/generator/raw-content-route.ts","../src/core/generator/search-route.ts","../src/core/generator/source-config.ts","../src/core/generator/tsconfig.ts","../src/core/generator/index.ts","../src/utils/copy-raw-markdown.ts","../src/utils/install-deps.ts","../src/utils/logger.ts","../src/utils/temp-dir.ts","../src/cli/commands/build.ts","../src/utils/check-code-langs.ts","../src/cli/commands/dev.ts","../src/cli/commands/preview.ts","../src/cli/commands/regenerate.ts","../src/cli/bin.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function buildTitleMap(config: OpenManualConfig): Record<string, string> {\n const map: Record<string, string> = {};\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n map[page.slug] = page.title;\n }\n }\n }\n return map;\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n };\n}\n","export function generateCalloutComponent(): string {\n return `'use client';\nexport { Callout, CalloutTitle, CalloutDescription } from 'openmanual/components/callout';\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateGlobalCss(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const primaryHue = config.theme?.primaryHue ?? 213;\n const darkMode = config.theme?.darkMode ?? true;\n\n const darkBlock = darkMode\n ? `\n.dark {\n --primary-hue: ${primaryHue};\n\n /* 温暖的深色皮革背景 */\n --color-fd-background: hsl(30, 18%, 10%);\n --color-fd-foreground: hsl(35, 15%, 90%);\n --color-fd-muted: hsl(30, 14%, 14%);\n --color-fd-muted-foreground: hsla(30, 10%, 65%, 0.8);\n --color-fd-popover: hsl(30, 16%, 13%);\n --color-fd-popover-foreground: hsl(35, 12%, 87%);\n --color-fd-card: hsl(30, 15%, 12%);\n --color-fd-card-foreground: hsl(35, 15%, 93%);\n --color-fd-border: hsla(30, 12%, 35%, 25%);\n --color-fd-primary: hsl(35, 20%, 92%);\n --color-fd-primary-foreground: hsl(30, 25%, 10%);\n --color-fd-secondary: hsl(30, 12%, 16%);\n --color-fd-secondary-foreground: hsl(35, 10%, 88%);\n --color-fd-accent: hsla(30, 15%, 30%, 35%);\n --color-fd-accent-foreground: hsl(35, 12%, 88%);\n --color-fd-ring: hsl(30, 30%, 50%);\n --color-fd-overlay: hsla(25, 20%, 5%, 0.5);\n\n /* Callout 类型色 */\n --callout-info-bg: hsl(213, 25%, 16%);\n --callout-info-border: hsl(213, 30%, 30%);\n --callout-info-text: hsl(213, 40%, 72%);\n --callout-warning-bg: hsl(32, 35%, 17%);\n --callout-warning-border: hsl(30, 35%, 30%);\n --callout-warning-text: hsl(35, 45%, 72%);\n --callout-danger-bg: hsl(5, 25%, 17%);\n --callout-danger-border: hsl(5, 30%, 30%);\n --callout-danger-text: hsl(5, 40%, 72%);\n --callout-check-bg: hsl(155, 22%, 16%);\n --callout-check-border: hsl(155, 25%, 28%);\n --callout-check-text: hsl(155, 35%, 68%);\n --callout-tip-bg: hsl(155, 22%, 16%);\n --callout-tip-border: hsl(155, 25%, 28%);\n --callout-tip-text: hsl(155, 35%, 68%);\n --callout-note-bg: hsl(215, 15%, 16%);\n --callout-note-border: hsl(215, 18%, 28%);\n --callout-note-text: hsl(215, 25%, 68%);\n --callout-key-bg: hsl(30, 28%, 17%);\n --callout-key-border: hsl(28, 25%, 30%);\n --callout-key-text: hsl(25, 35%, 68%);\n}\n\n.dark body {\n background: linear-gradient(hsla(30, 30%, 15%, 0.4), transparent 20rem, transparent);\n}\n`\n : '';\n\n return `@import 'tailwindcss';\n@source './node_modules/openmanual/dist/components/**/*.js';\n@import 'fumadocs-ui/style.css';\n@custom-variant dark (&:is(.dark, .dark *));\n\n:root {\n --primary-hue: ${primaryHue};\n\n /* 护眼暖色阅读背景 */\n --color-fd-background: hsl(40, 22%, 96.5%); /* #faf9f6 纸张白 */\n --color-fd-foreground: hsl(0, 0%, 17.3%); /* #2c2c2c 柔黑 */\n --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */\n --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */\n --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */\n\n /* Callout 类型色 */\n --callout-info-bg: hsl(210, 35%, 94%);\n --callout-info-border: hsl(212, 40%, 80%);\n --callout-info-text: hsl(213, 45%, 35%);\n --callout-warning-bg: hsl(38, 60%, 93%);\n --callout-warning-border: hsl(36, 55%, 78%);\n --callout-warning-text: hsl(28, 55%, 35%);\n --callout-danger-bg: hsl(0, 50%, 94%);\n --callout-danger-border: hsl(0, 45%, 82%);\n --callout-danger-text: hsl(0, 50%, 38%);\n --callout-check-bg: hsl(150, 35%, 93%);\n --callout-check-border: hsl(152, 35%, 78%);\n --callout-check-text: hsl(155, 40%, 32%);\n --callout-tip-bg: hsl(150, 35%, 93%);\n --callout-tip-border: hsl(152, 35%, 78%);\n --callout-tip-text: hsl(155, 40%, 32%);\n --callout-note-bg: hsl(215, 20%, 94%);\n --callout-note-border: hsl(215, 22%, 82%);\n --callout-note-text: hsl(215, 25%, 40%);\n --callout-key-bg: hsl(30, 55%, 93%);\n --callout-key-border: hsl(28, 50%, 78%);\n --callout-key-text: hsl(25, 50%, 35%);\n}\n${darkBlock}\n\n/* 代码块:去除 shadow,使用朴素边框;去除 max-height 限制 */\nfigure.shiki {\n box-shadow: none;\n}\n\nfigure.shiki > div {\n max-height: none;\n}\n\n/* Mermaid 全屏操作栏按钮 hover */\n.mermaid-toolbar-btn:hover {\n background-color: var(--hover-bg) !important;\n color: var(--hover-color) !important;\n}\n\n.mermaid-toolbar-btn:hover svg {\n color: inherit;\n}\n\n/* Callout:去除 shadow */\n[style*=\"--callout-color\"] {\n box-shadow: none;\n}\n`;\n}\n","import type { LogoConfig, OpenManualConfig } from '../config/schema.js';\n\nconst IMAGE_EXTENSIONS = ['.svg', '.png', '.jpg', '.jpeg', '.webp'];\n\nexport function isImagePath(value: string): boolean {\n if (value.startsWith('/')) return true;\n return IMAGE_EXTENSIONS.some((ext) => value.toLowerCase().endsWith(ext));\n}\n\nexport function resolveLogoPaths(logo: LogoConfig): { light: string; dark: string } {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo };\n }\n return { light: logo.light, dark: logo.dark };\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const logo = config.navbar?.logo ?? config.name;\n\n let logoProps: string;\n if (typeof logo === 'string' && isImagePath(logo)) {\n logoProps = `type=\"image\" src=\"${logo}\" alt=\"${config.name}\"`;\n } else if (typeof logo === 'object') {\n const { light, dark } = logo;\n if (light === dark) {\n logoProps = `type=\"image\" src=\"${light}\" alt=\"${config.name}\"`;\n } else {\n logoProps = `type=\"image\" srcLight=\"${light}\" srcDark=\"${dark}\" alt=\"${config.name}\"`;\n }\n } else {\n logoProps = `type=\"text\" text=\"${logo}\"`;\n }\n\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\nimport type { ReactNode } from 'react';\nimport { NavLogo } from 'openmanual/components/nav-layout';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: <NavLogo ${logoProps} /> as ReactNode,\n },\n };\n}\n`;\n}\n","export function generateLibSource(): string {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n});\n`;\n}\n","export function generateMermaidComponent(): string {\n return `'use client';\nexport { Mermaid } from 'openmanual/components/mermaid';\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig; dev?: boolean }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n\n // dev 模式下不设置 output: 'export'(不兼容 API 路由和 rewrites)\n const outputLine = !ctx.dev && siteUrl ? `\\n output: 'export',` : '';\n // dev 模式下添加 rewrites 将 .md 请求代理到 API 路由\n const rewritesBlock = ctx.dev\n ? `\\n async rewrites() {\\n return [{ source: '/:path(.+)\\\\\\\\.md', destination: '/api/raw/:path' }];\\n },`\n : '';\n\n return `import { createMDX } from 'fumadocs-mdx/next';\n\nconst withMDX = createMDX();\n\n/** @type {import('next').NextConfig} */\nconst config = {\n reactStrictMode: true,${outputLine}\n serverExternalPackages: ['mermaid'],\n images: {\n unoptimized: true,\n },${rewritesBlock}\n};\n\nexport default withMDX(config);\n`;\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { OpenManualConfig } from '../config/schema.js';\n\ndeclare const __VERSION__: string | undefined;\n\nfunction getOpenManualVersion(): string {\n if (typeof __VERSION__ !== 'undefined') {\n return __VERSION__;\n }\n // fallback: 测试环境或直接 tsx 运行时使用\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n}\n\nexport function generatePackageJson(ctx: {\n config: OpenManualConfig;\n projectDir: string;\n appDir?: string;\n dev?: boolean;\n openmanualRoot?: string;\n}): string {\n const openmanualVersion = getOpenManualVersion();\n\n let openmanualDep: string;\n if (ctx.openmanualRoot && ctx.appDir) {\n const relPath = relative(ctx.appDir, ctx.openmanualRoot);\n openmanualDep = `file:${relPath}`;\n } else {\n openmanualDep = `^${openmanualVersion}`;\n }\n\n const pkg = {\n name: 'openmanual-app',\n type: 'module',\n private: true,\n scripts: {\n dev: 'next dev',\n build: 'next build',\n start: 'next start',\n },\n dependencies: {\n '@tailwindcss/postcss': '^4.1.15',\n 'fumadocs-core': '^16.7.7',\n 'fumadocs-mdx': '^14.2.11',\n 'fumadocs-ui': '^16.7.7',\n 'lucide-react': '^1.7.0',\n mermaid: '^11.4.0',\n next: '^16.2.1',\n 'next-themes': '^0.4.6',\n openmanual: openmanualDep,\n postcss: '^8.5.8',\n react: '^19.1.0',\n 'react-dom': '^19.1.0',\n tailwindcss: '^4.1.15',\n zod: '^4.0.0',\n },\n devDependencies: {\n '@types/react': '^19.1.0',\n '@types/react-dom': '^19.1.0',\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n","import { collectConfiguredSlugs, type OpenManualConfig } from '../config/schema.js';\n\nexport function generatePage(_ctx: { config: OpenManualConfig }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;\n\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set(${JSON.stringify([...collectConfiguredSlugs(_ctx.config)])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug ? slug.join('/') : 'index';\n return allowedSlugs.has(key);\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug)) {\n notFound();\n }\n`\n : '';\n\n const filterInStaticParams = isStrict\n ? `\nexport function generateStaticParams() {\n let params = source.generateParams();\n params = params.filter((p: { slug: string[] }) => isAllowed(p.slug));\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const pageTitleArea = pageActionsEnabled\n ? ` <div className=\"flex items-start justify-between gap-4\">\n <div>\n <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}\n </div>\n <PageActions />\n </div>`\n : ` <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}`;\n\n return `import { source } from '@/lib/source';\nimport { notFound } from 'next/navigation';\nimport { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';\nimport defaultMdxComponents from 'fumadocs-ui/mdx';\nimport { Steps, Step } from 'fumadocs-ui/components/steps';\nimport { Tabs, Tab } from 'fumadocs-ui/components/tabs';\nimport { Files, File, Folder } from 'fumadocs-ui/components/files';\nimport { Accordion, Accordions } from 'fumadocs-ui/components/accordion';\nimport { TypeTable } from 'fumadocs-ui/components/type-table';\nimport { Mermaid } from '@/components/mermaid';\nimport { Callout, CalloutTitle, CalloutDescription } from '@/components/callout';${pageActionsImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n const page = source.getPage(slug);\n${filterInPage}\n if (!page) {\n notFound();\n }\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc}>\n${pageTitleArea}\n <DocsBody data-content-area>\n <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid, Callout, CalloutTitle, CalloutDescription }} />\n </DocsBody>\n </DocsPage>\n );\n}\n${filterInStaticParams}\n`;\n}\n","export function generatePageActionsComponent(): string {\n return `'use client';\nexport { PageActions } from 'openmanual/components/page-actions';\n`;\n}\n","export function generatePostcssConfig(): string {\n return `/** @type {import('postcss-load-config').Config} */\nconst config = {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n};\n\nexport default config;\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n\n return `'use client';\nimport { Provider } from 'openmanual/components/provider';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children }: { children: ReactNode }) {\n return (\n <Provider\n searchEnabled={${searchEnabled}}\n >\n {children}\n </Provider>\n );\n}\n`;\n}\n","export function generateRawContentRoute(): string {\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nexport async function GET(\n _request: Request,\n { params }: { params: Promise<{ path: string[] }> },\n) {\n const { path: segments } = await params;\n const slug = segments.join('/');\n for (const ext of ['.mdx', '.md']) {\n try {\n const filePath = join(process.cwd(), 'content', \\`\\${slug}\\${ext}\\`);\n const content = await readFile(filePath, 'utf-8');\n return new NextResponse(content, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n });\n } catch {\n /* try next extension */\n }\n }\n return new NextResponse('Not found', { status: 404 });\n}\n`;\n}\n","export function generateSearchRoute(): string {\n return `import { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n\nexport const revalidate = false;\nexport const { staticGET: GET } = createFromSource(source);\n`;\n}\n","import { buildTitleMap, collectConfiguredSlugs, type OpenManualConfig } from '../config/schema.js';\n\nexport function generateSourceConfig(_ctx: { config: OpenManualConfig }): string {\n const titleMap = buildTitleMap(_ctx.config);\n const titleMapEntries = Object.entries(titleMap)\n .map(([slug, title]) => ` '${slug}': '${title.replace(/'/g, \"\\\\'\")}'`)\n .join(',\\n');\n const titleMapStr = titleMapEntries ? `{\\n${titleMapEntries}\\n}` : '{}';\n\n const isStrict = _ctx.config.contentPolicy !== 'all';\n\n const allowedSlugsSnippet = isStrict\n ? `\n\nconst allowedSlugs = new Set(${JSON.stringify([...collectConfiguredSlugs(_ctx.config)])});\n\nfunction slugFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n return relative.replace(/\\\\.(md|mdx)$/i, '');\n}\n`\n : '';\n\n const filterSnippet = isStrict\n ? `\n .refine((_data) => {\n const slug = slugFromPath(ctx.path);\n if (allowedSlugs.size > 0 && !allowedSlugs.has(slug)) {\n return false;\n }\n return true;\n })`\n : '';\n\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\nimport { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';\nimport { z } from 'zod';\n\nconst titleMap: Record<string, string> = ${titleMapStr};${allowedSlugsSnippet}\nfunction titleFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n const slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;\n}\n\nexport const docs = defineDocs({\n dir: 'content',\n docs: {\n schema: (ctx) =>\n z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n full: z.boolean().optional(),\n }).transform((data) => ({\n ...data,\n title: data.title ?? titleFromPath(ctx.path),\n }))${filterSnippet},\n },\n});\n\nexport default defineConfig({\n mdxOptions: {\n remarkPlugins: [remarkMdxMermaid],\n rehypeCodeOptions: {\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n defaultColor: false,\n fallbackLanguage: 'text',\n },\n },\n});\n`;\n}\n","export function generateTsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n lib: ['dom', 'dom.iterable', 'esnext'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n jsx: 'react-jsx',\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n isolatedModules: true,\n incremental: true,\n plugins: [{ name: 'next' }],\n paths: {\n '@/*': ['./*'],\n },\n },\n include: [\n '**/*.ts',\n '**/*.tsx',\n 'next-env.d.ts',\n '.next/types/**/*.ts',\n '.next/dev/types/**/*.ts',\n ],\n exclude: ['node_modules'],\n },\n null,\n 2\n )}\\n`;\n}\n","import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { generateCalloutComponent } from './callout-component.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateNextConfig } from './next-config.js';\nimport { generatePackageJson } from './package-json.js';\nimport { generatePage } from './page.js';\nimport { generatePageActionsComponent } from './page-actions-component.js';\nimport { generatePostcssConfig } from './postcss-config.js';\nimport { generateProvider } from './provider.js';\nimport { generateRawContentRoute } from './raw-content-route.js';\nimport { generateSearchRoute } from './search-route.js';\nimport { generateSourceConfig } from './source-config.js';\nimport { generateTsconfig } from './tsconfig.js';\n\nexport interface GenerateContext {\n config: OpenManualConfig;\n /** Absolute path to user's project root */\n projectDir: string;\n /** Absolute path to .openmanual/app */\n appDir: string;\n /** Content directory relative to project root */\n contentDir: string;\n /** 开发模式标志,dev 模式下不设置 output: 'export',生成 API 路由和 rewrites */\n dev?: boolean;\n /** openmanual 项目根目录,dev 模式下用于 file: 链接到本地构建产物 */\n openmanualRoot?: string;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const files: Array<{ path: string; content: string }> = [\n {\n path: 'source.config.ts',\n content: generateSourceConfig(ctx),\n },\n {\n path: 'next.config.mjs',\n content: generateNextConfig(ctx),\n },\n {\n path: 'global.css',\n content: generateGlobalCss(ctx),\n },\n {\n path: 'package.json',\n content: generatePackageJson(ctx),\n },\n {\n path: 'tsconfig.json',\n content: generateTsconfig(),\n },\n {\n path: 'postcss.config.mjs',\n content: generatePostcssConfig(),\n },\n {\n path: 'lib/source.ts',\n content: generateLibSource(),\n },\n {\n path: 'lib/layout.tsx',\n content: generateLayout(ctx),\n },\n {\n path: 'components/callout.tsx',\n content: generateCalloutComponent(),\n },\n {\n path: 'components/mermaid.tsx',\n content: generateMermaidComponent(),\n },\n {\n path: 'components/page-actions.tsx',\n content: generatePageActionsComponent(),\n },\n // API 路由:raw content 仅 dev 模式(动态读取文件系统);搜索路由两种模式都生成(staticGET 兼容静态导出)\n ...(ctx.dev\n ? [\n { path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute() },\n { path: 'app/api/search/route.ts', content: generateSearchRoute() },\n ]\n : [{ path: 'app/api/search/route.ts', content: generateSearchRoute() }]),\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n\n for (const file of files) {\n const fullPath = join(ctx.appDir, file.path);\n const dir = join(fullPath, '..');\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, file.content, 'utf-8');\n }\n\n // Generate logo SVG in public/ when logo is an image path\n const logo = ctx.config.navbar?.logo;\n if (logo && typeof logo === 'string' && isImagePath(logo)) {\n await ensureLogoFile(ctx, logo, 'light');\n } else if (logo && typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (isImagePath(light)) {\n await ensureLogoFile(ctx, light, 'light');\n }\n if (isImagePath(dark) && dark !== light) {\n await ensureLogoFile(ctx, dark, 'dark');\n }\n }\n\n // Generate meta.json for each sidebar group directory\n await generateMetaFiles(ctx);\n}\n\nfunction generateRootLayout(): string {\n return `import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <AppLayout>\n <AppProvider>{children}</AppProvider>\n </AppLayout>\n );\n}\n`;\n}\n\nfunction generateDocsLayout(ctx: GenerateContext): string {\n const { config } = ctx;\n const githubLink = config.navbar?.github ?? '';\n const navLinks = config.navbar?.links ?? [];\n const footerText = config.footer?.text ?? '';\n\n const linksArray = navLinks.map((l) => ({\n text: l.label,\n url: l.href,\n external: true,\n }));\n\n const githubLine = githubLink ? `\\n github: '${githubLink}',` : '';\n\n const linksLine = linksArray.length > 0 ? `\\n links: ${JSON.stringify(linksArray)},` : '';\n\n const footerLine = footerText\n ? `\\n footer: { children: '${footerText.replace(/'/g, \"\\\\'\")}' },`\n : '';\n\n // Build sidebar config for tree restructuring (including icon names)\n const sidebar = config.sidebar;\n const hasSidebar = sidebar && sidebar.length > 0;\n\n // Collect all unique icon names from sidebar config\n const iconNames = new Set<string>();\n if (hasSidebar) {\n for (const g of sidebar ?? []) {\n if (g.icon) iconNames.add(g.icon);\n for (const p of g.pages) {\n if (p.icon) iconNames.add(p.icon);\n }\n }\n }\n const hasIcons = iconNames.size > 0;\n const iconNameList = [...iconNames];\n\n const sidebarConfigSnippet = hasSidebar\n ? `\\nconst sidebarConfig = ${JSON.stringify(\n (sidebar ?? []).map((g) => ({\n group: g.group,\n icon: g.icon,\n collapsed: g.collapsed,\n pages: g.pages.map((p) => ({ slug: p.slug, icon: p.icon })),\n })),\n null,\n 2\n )} as const;\n`\n : '';\n\n // Generate lucide-react import statement\n const lucideImportLine = hasIcons\n ? `\\nimport { ${iconNameList.join(', ')} } from 'lucide-react';`\n : '';\n\n // Generate iconMap mapping icon names to React elements\n const iconMapSnippet = hasIcons\n ? `\\nconst iconMap = {${iconNameList.map((name) => `\\n ${name}: <${name} />,`).join('')}\\n} as const;\n`\n : '';\n\n const treeLine = hasSidebar\n ? hasIcons\n ? 'tree: restructureTree(source.getPageTree(), sidebarConfig, iconMap),'\n : 'tree: restructureTree(source.getPageTree(), sidebarConfig),'\n : 'tree: source.getPageTree(),';\n\n const restructureTreeImport = hasSidebar\n ? \"\\nimport { restructureTree } from 'openmanual/utils/restructure-tree';\"\n : '';\n\n return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout';\nimport { source } from '@/lib/source';\nimport type { ReactNode } from 'react';${restructureTreeImport}${lucideImportLine}\n${sidebarConfigSnippet}${iconMapSnippet}\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${githubLine}${linksLine}${footerLine}\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\nexport function generateOpenManualLogoSvg(\n name: string,\n variant: 'light' | 'dark' = 'light'\n): string {\n const textColor = variant === 'dark' ? '#E8E0D4' : '#000000';\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 190 32\" width=\"190\" height=\"32\">\n <text x=\"0\" y=\"25\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\" font-size=\"32\" font-weight=\"700\">\n <tspan fill=\"#2B7A4B\" font-size=\"34\">${name.charAt(0)}</tspan><tspan fill=\"${textColor}\">${name.slice(1)}</tspan>\n </text>\n</svg>\n`;\n}\n\nasync function ensureLogoFile(\n ctx: GenerateContext,\n logoPath: string,\n variant: 'light' | 'dark'\n): Promise<void> {\n const userLogoPath = join(ctx.projectDir, 'public', logoPath.replace(/^\\//, ''));\n try {\n await access(userLogoPath);\n } catch {\n const publicDir = join(ctx.appDir, 'public');\n await mkdir(publicDir, { recursive: true });\n const fullPath = join(publicDir, logoPath.replace(/^\\//, ''));\n await mkdir(join(fullPath, '..'), { recursive: true });\n await writeFile(fullPath, generateOpenManualLogoSvg(ctx.config.name, variant), 'utf-8');\n }\n}\n\n/**\n * Generate meta.json for each sidebar group directory so that\n * fumadocs displays the configured Chinese group name instead of\n * auto-capitalizing the English directory name.\n */\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\n const sidebar = ctx.config.sidebar;\n if (!sidebar || sidebar.length === 0) return;\n\n const contentAbsDir = join(ctx.projectDir, ctx.contentDir);\n\n for (const group of sidebar) {\n // Extract directory prefix from the first page slug that contains \"/\"\n const dirPrefix = group.pages\n .map((p) => p.slug)\n .find((slug) => slug.includes('/'))\n ?.split('/')[0];\n\n if (!dirPrefix) continue; // Root-level pages, no meta.json needed\n\n const dirPath = join(contentAbsDir, dirPrefix);\n const metaPath = join(dirPath, 'meta.json');\n\n // Skip if meta.json already exists\n try {\n await access(metaPath);\n continue;\n } catch {\n // File doesn't exist, proceed to create it\n }\n\n await mkdir(dirPath, { recursive: true });\n await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\\n`, 'utf-8');\n }\n}\n","import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';\nimport { join, parse } from 'node:path';\n\n/**\n * Recursively copy .mdx/.md files from contentDir to targetDir,\n * renaming extensions to .md. Non-markdown files are skipped.\n */\nexport async function copyRawMarkdown(contentDir: string, targetDir: string): Promise<void> {\n await mkdir(targetDir, { recursive: true });\n\n const entries = await readdir(contentDir);\n for (const entry of entries) {\n const srcPath = join(contentDir, entry);\n const srcStat = await stat(srcPath);\n\n if (srcStat.isDirectory()) {\n await copyRawMarkdown(srcPath, join(targetDir, entry));\n } else {\n const ext = parse(entry).ext.toLowerCase();\n if (ext === '.mdx' || ext === '.md') {\n const { name } = parse(entry);\n const destPath = join(targetDir, `${name}.md`);\n await copyFile(srcPath, destPath);\n }\n }\n }\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport async function installDeps(appDir: string): Promise<void> {\n const nodeModules = resolve(appDir, 'node_modules');\n\n // Skip install if node_modules already exists\n if (existsSync(nodeModules)) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn('pnpm', ['install', '--no-frozen-lockfile', '--ignore-workspace'], {\n cwd: appDir,\n stdio: 'pipe',\n env: { ...process.env },\n });\n\n let stderr = '';\n child.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`pnpm install failed: ${stderr}`));\n }\n });\n });\n}\n","const COLORS = {\n reset: '\\x1b[0m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString('zh-CN', { hour12: false });\n}\n\nexport const logger = {\n info(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}info${COLORS.reset} ${msg}`\n );\n },\n\n success(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.green}done${COLORS.reset} ${msg}`\n );\n },\n\n warn(msg: string): void {\n console.warn(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}warn${COLORS.reset} ${msg}`\n );\n },\n\n error(msg: string): void {\n console.error(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.red}error${COLORS.reset} ${msg}`\n );\n },\n\n step(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.bold}→${COLORS.reset} ${msg}`\n );\n },\n};\n","import { existsSync } from 'node:fs';\nimport { lstat, mkdir, rm, symlink } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst TEMP_DIR_NAME = '.openmanual';\n\nexport function getTempDir(cwd: string): string {\n return join(cwd, TEMP_DIR_NAME);\n}\n\nexport function getAppDir(cwd: string): string {\n return join(getTempDir(cwd), 'app');\n}\n\nexport async function ensureTempDir(cwd: string): Promise<string> {\n const tempDir = getTempDir(cwd);\n const appDir = getAppDir(cwd);\n\n await mkdir(tempDir, { recursive: true });\n await mkdir(join(appDir, 'app'), { recursive: true });\n\n return tempDir;\n}\n\nexport async function cleanTempDir(cwd: string): Promise<void> {\n const tempDir = getTempDir(cwd);\n if (existsSync(tempDir)) {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\nexport async function createSymlink(target: string, linkPath: string): Promise<void> {\n const resolvedTarget = resolve(target);\n const resolvedLink = resolve(linkPath);\n\n try {\n await lstat(resolvedLink);\n // Remove existing symlink or directory\n await rm(resolvedLink, { recursive: true, force: true });\n } catch {\n // link doesn't exist, that's fine\n }\n\n await symlink(resolvedTarget, resolvedLink, 'junction');\n}\n","import { spawn } from 'node:child_process';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { copyRawMarkdown } from '../../utils/copy-raw-markdown.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { cleanTempDir, createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const buildCommand = new Command('build').description('构建静态站点').action(async () => {\n const cwd = process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n await ensureTempDir(cwd);\n\n // 从 CLI 位置推导 openmanual 包根目录(dist/bin.js → 上溯 1 级到包根目录)\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const openmanualRoot = resolve(__dirname, '..');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n openmanualRoot,\n };\n\n await generateAll(ctx);\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.step('构建静态站点...');\n const buildResult = spawn('npx', ['next', 'build'], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n await new Promise<void>((resolve, reject) => {\n buildResult.on('error', reject);\n buildResult.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Build failed with code ${code}`));\n }\n });\n });\n\n // Copy output to user's output dir\n const outputDir = resolve(cwd, config.outputDir ?? 'dist');\n await mkdir(outputDir, { recursive: true });\n\n const nextOutput = resolve(appDir, 'out');\n try {\n await cp(nextOutput, outputDir, { recursive: true });\n logger.success(`静态站点已输出到: ${outputDir}`);\n } catch {\n // If no 'out' dir, check .next/static\n logger.warn('未找到静态导出产物,请检查 next.config.mjs 中 output: \"export\" 配置');\n }\n\n logger.step('复制原始 Markdown 文件...');\n await copyRawMarkdown(contentDir, outputDir);\n\n logger.step('清理临时文件...');\n await cleanTempDir(cwd);\n\n logger.success('构建完成!');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n});\n","import { readdir, readFile } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\n\nexport interface UnknownLang {\n file: string;\n line: number;\n lang: string;\n}\n\nexport async function checkCodeLangs(contentDir: string): Promise<UnknownLang[]> {\n const { bundledLanguages } = await import('shiki');\n const supportedLangs = new Set(Object.keys(bundledLanguages));\n supportedLangs.add('text');\n supportedLangs.add('txt');\n supportedLangs.add('plaintext');\n supportedLangs.add('plain');\n supportedLangs.add('ansi');\n\n const files = await collectMdFiles(contentDir);\n const results: UnknownLang[] = [];\n\n for (const file of files) {\n const content = await readFile(file, 'utf-8');\n const lines = content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!line) continue;\n const match = line.match(/^```(\\S+)/);\n if (match) {\n const lang = match[1];\n if (!lang) continue;\n if (!supportedLangs.has(lang)) {\n results.push({\n file: relative(contentDir, file).split(sep).join('/'),\n line: i + 1,\n lang,\n });\n }\n }\n }\n }\n\n return results;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectMdFiles(fullPath)));\n } else if (entry.isFile() && /\\.(md|mdx)$/i.test(entry.name)) {\n files.push(fullPath);\n }\n }\n\n return files;\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { dirname, extname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { checkCodeLangs } from '../../utils/check-code-langs.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const devCommand = new Command('dev')\n .description('启动开发服务器')\n .option('-p, --port <port>', '端口号', '3000')\n .option('--watch', '监听框架源码变更并自动重新生成', false)\n .option('--cwd <path>', '项目目录(watch 模式下使用)')\n .action(async (options) => {\n const cwd = options.cwd ? resolve(options.cwd) : process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const tempDir = await ensureTempDir(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n // 从 CLI 位置推导 openmanual 包根目录(dist/bin.js → 上溯 1 级到包根目录)\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const openmanualRoot = process.env.OPENMANUAL_ROOT || resolve(__dirname, '..');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n dev: true,\n openmanualRoot,\n };\n\n if (process.env.OPENMANUAL_ROOT) {\n await spawnInitialGenerate(openmanualRoot, cwd);\n } else {\n await generateAll(ctx);\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n }\n\n // Check for unsupported code block languages\n try {\n const unknownLangs = await checkCodeLangs(contentDir);\n if (unknownLangs.length > 0) {\n logger.warn('以下文件使用了不认识的代码块语言:');\n for (const item of unknownLangs) {\n logger.warn(` ${item.file}:${item.line} - \"${item.lang}\"`);\n }\n logger.warn('建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值');\n }\n } catch {\n // skip check if shiki is not available\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.success('开发服务器启动中...');\n logger.info(`内容目录: ${contentDir}`);\n logger.info(`临时目录: ${tempDir}`);\n logger.info(`端口: ${options.port}`);\n\n const nextChild = spawn('npx', ['next', 'dev', '--port', options.port], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n nextChild.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n nextChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n process.exit(code);\n }\n });\n\n // Watch mode: monitor framework source and config changes\n let watcher: InstanceType<typeof import('chokidar').FSWatcher> | undefined;\n let regenTimer: ReturnType<typeof setTimeout> | undefined;\n\n if (options.watch) {\n const openmanualRoot = process.env.OPENMANUAL_ROOT;\n if (!openmanualRoot) {\n logger.warn('OPENMANUAL_ROOT 未设置,无法监听框架源码变更');\n } else {\n const chokidar = await import('chokidar');\n const srcDir = resolve(openmanualRoot, 'src');\n const configFile = resolve(cwd, 'openmanual.json');\n\n watcher = chokidar.watch(srcDir, {\n ignoreInitial: true,\n ignored: [\n '**/__tests__/**',\n '**/*.test.ts',\n (path: string) => {\n const ext = extname(path);\n if (!ext) return false; // 无扩展名 = 目录,不忽略\n return ext !== '.ts' && ext !== '.tsx';\n },\n ],\n });\n watcher.add(configFile);\n\n watcher.on('all', (event, filePath) => {\n if (event === 'add' || event === 'change' || event === 'unlink') {\n logger.info(`检测到变更: ${filePath}`);\n clearTimeout(regenTimer);\n regenTimer = setTimeout(() => {\n spawnRegenerate(openmanualRoot, cwd, nextChild);\n }, 300);\n }\n });\n\n logger.success('Watch 模式已启用,监听框架源码和配置变更');\n }\n }\n\n // Handle graceful shutdown\n const cleanup = () => {\n clearTimeout(regenTimer);\n watcher?.close();\n nextChild.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n\nfunction spawnInitialGenerate(openmanualRoot: string, cwd: string): Promise<void> {\n const binPath = resolve(openmanualRoot, 'dist/bin.js');\n const child = spawn('node', [binPath, '_regenerate', '--cwd', cwd], {\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n return new Promise<void>((promiseResolve, promiseReject) => {\n child.on('exit', (code) => {\n if (code === 0) {\n promiseResolve();\n } else {\n promiseReject(new Error(`初始生成失败 (exit code: ${code})`));\n }\n });\n\n child.on('error', promiseReject);\n });\n}\n\nfunction spawnRegenerate(openmanualRoot: string, cwd: string, nextChild: ChildProcess): void {\n if (nextChild.exitCode !== null) {\n logger.warn('Next.js 进程已退出,跳过重新生成');\n return;\n }\n\n logger.step('重新生成文件...');\n\n const binPath = resolve(openmanualRoot, 'dist/bin.js');\n const child = spawn('node', [binPath, '_regenerate', '--cwd', cwd], {\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n child.on('exit', (code) => {\n if (code === 0) {\n logger.success('文件重新生成完成');\n } else {\n logger.error(`重新生成失败 (exit code: ${code})`);\n }\n });\n\n child.on('error', (err) => {\n logger.error(`重新生成进程错误: ${err.message}`);\n });\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const previewCommand = new Command('preview')\n .description('预览构建产物')\n .option('-p, --port <port>', '端口号', '8080')\n .option('-d, --dir <dir>', '产物目录')\n .action(async (options) => {\n const cwd = process.cwd();\n\n try {\n let outputDir = options.dir;\n if (!outputDir) {\n const config = await loadConfig(cwd);\n outputDir = resolve(cwd, config.outputDir ?? 'dist');\n }\n\n if (!existsSync(outputDir)) {\n logger.error(`产物目录不存在: ${outputDir}`);\n logger.info('请先运行 openmanual build');\n process.exit(1);\n }\n\n logger.info(`预览目录: ${outputDir}`);\n logger.info(`预览地址: http://localhost:${options.port}`);\n\n const child = spawn('npx', ['serve', outputDir, '-p', options.port], {\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n child.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n const cleanup = () => {\n child.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n","import { rm } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const regenerateCommand = new Command('_regenerate')\n .description('内部命令:重新生成文件')\n .helpOption(false)\n .option('--cwd <path>', '项目目录')\n .action(async (options) => {\n const cwd = options.cwd ?? process.cwd();\n\n try {\n const config = await loadConfig(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n ...(process.env.OPENMANUAL_ROOT ? { openmanualRoot: process.env.OPENMANUAL_ROOT } : {}),\n };\n\n await ensureTempDir(cwd);\n\n // 清理旧的生成物,避免残留文件导致冲突\n const entriesToClean = [\n 'app',\n 'lib',\n 'source.config.ts',\n 'next.config.mjs',\n 'global.css',\n 'package.json',\n 'tsconfig.json',\n 'postcss.config.mjs',\n ];\n for (const entry of entriesToClean) {\n await rm(join(appDir, entry), { recursive: true, force: true });\n }\n\n await generateAll(ctx);\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n console.log('[openmanual] regenerate:ok');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`[openmanual] regenerate:fail ${message}`);\n process.exit(1);\n }\n });\n","import { readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport { buildCommand } from './commands/build.js';\nimport { devCommand } from './commands/dev.js';\nimport { previewCommand } from './commands/preview.js';\nimport { regenerateCommand } from './commands/regenerate.js';\n\ndeclare const __VERSION__: string;\n\nfunction getVersion(): string {\n if (typeof __VERSION__ !== 'undefined') {\n return __VERSION__;\n }\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n } catch {\n // tsx dev 模式下 __dirname 是 src/cli,需要多上一层\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n return pkg.version;\n } catch {\n return '0.0.0';\n }\n }\n}\n\nconst program = new Command();\nconst commandName = basename(process.argv[1] ?? 'openmanual');\n\nprogram\n .name(commandName)\n .description('AI 友好的开源文档系统框架')\n .version(getVersion(), '-v, --version');\n\nprogram.addCommand(devCommand);\nprogram.addCommand(buildCommand);\nprogram.addCommand(previewCommand);\nprogram.addCommand(regenerateCommand, { hidden: true });\n\nprogram.parse();\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ComponentProps, ReactNode } from "react";
3
+
4
+ //#region src/components/callout.d.ts
5
+ type CalloutType = 'note' | 'info' | 'warning' | 'tip' | 'check' | 'danger' | 'key' | 'warn' | 'error' | 'success' | 'idea';
6
+ interface CalloutContainerProps extends ComponentProps<'div'> {
7
+ /**
8
+ * @defaultValue info
9
+ */
10
+ type?: CalloutType;
11
+ /**
12
+ * Force an icon
13
+ */
14
+ icon?: ReactNode;
15
+ }
16
+ declare function Callout({
17
+ children,
18
+ title,
19
+ ...props
20
+ }: {
21
+ title?: ReactNode;
22
+ } & Omit<CalloutContainerProps, 'title'>): _$react_jsx_runtime0.JSX.Element;
23
+ declare function CalloutContainer({
24
+ type: inputType,
25
+ icon,
26
+ children,
27
+ className,
28
+ style,
29
+ ...props
30
+ }: CalloutContainerProps): _$react_jsx_runtime0.JSX.Element;
31
+ declare function CalloutTitle({
32
+ children,
33
+ className,
34
+ ...props
35
+ }: ComponentProps<'p'>): _$react_jsx_runtime0.JSX.Element;
36
+ declare function CalloutDescription({
37
+ children,
38
+ className,
39
+ ...props
40
+ }: ComponentProps<'p'>): _$react_jsx_runtime0.JSX.Element;
41
+ //#endregion
42
+ export { Callout, CalloutContainer, CalloutContainerProps, CalloutDescription, CalloutTitle, CalloutType };
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Info, KeyRound, Lightbulb, MessageCircleCheck, MessageCircleWarning, OctagonAlert, TriangleAlert } from "lucide-react";
4
+ import { twMerge } from "tailwind-merge";
5
+ //#region src/components/callout.tsx
6
+ const iconClass = "size-5 -me-0.5 text-(--callout-color)";
7
+ function resolveAlias(type) {
8
+ return {
9
+ warn: "warning",
10
+ error: "danger",
11
+ success: "check",
12
+ idea: "tip"
13
+ }[type] ?? type;
14
+ }
15
+ function Callout({ children, title, ...props }) {
16
+ return /* @__PURE__ */ jsxs(CalloutContainer, {
17
+ ...props,
18
+ children: [title && /* @__PURE__ */ jsx(CalloutTitle, { children: title }), /* @__PURE__ */ jsx(CalloutDescription, { children })]
19
+ });
20
+ }
21
+ function CalloutContainer({ type: inputType = "info", icon, children, className, style, ...props }) {
22
+ const type = resolveAlias(inputType);
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ className: twMerge("flex gap-2 my-4 rounded-xl border p-3 text-sm text-(--callout-color)", className),
25
+ style: {
26
+ "--callout-color": `var(--callout-${type}-text)`,
27
+ backgroundColor: `var(--callout-${type}-bg)`,
28
+ borderColor: `var(--callout-${type}-border)`,
29
+ ...style
30
+ },
31
+ ...props,
32
+ children: [icon ?? {
33
+ note: /* @__PURE__ */ jsx(MessageCircleWarning, { className: iconClass }),
34
+ info: /* @__PURE__ */ jsx(Info, { className: iconClass }),
35
+ warning: /* @__PURE__ */ jsx(TriangleAlert, { className: iconClass }),
36
+ tip: /* @__PURE__ */ jsx(Lightbulb, { className: iconClass }),
37
+ check: /* @__PURE__ */ jsx(MessageCircleCheck, { className: iconClass }),
38
+ danger: /* @__PURE__ */ jsx(OctagonAlert, { className: iconClass }),
39
+ key: /* @__PURE__ */ jsx(KeyRound, { className: iconClass })
40
+ }[type], /* @__PURE__ */ jsx("div", {
41
+ className: "flex flex-col gap-2 min-w-0 flex-1",
42
+ children
43
+ })]
44
+ });
45
+ }
46
+ function CalloutTitle({ children, className, ...props }) {
47
+ return /* @__PURE__ */ jsx("p", {
48
+ className: twMerge("font-medium my-0!", className),
49
+ ...props,
50
+ children
51
+ });
52
+ }
53
+ function CalloutDescription({ children, className, ...props }) {
54
+ return /* @__PURE__ */ jsx("div", {
55
+ className: twMerge("prose-no-margin empty:hidden", className),
56
+ ...props,
57
+ children
58
+ });
59
+ }
60
+ //#endregion
61
+ export { Callout, CalloutContainer, CalloutDescription, CalloutTitle };
@@ -1,7 +1,9 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { LocateFixed, RotateCcw, X, ZoomIn, ZoomOut } from "lucide-react";
4
+ import Panzoom from "@panzoom/panzoom";
3
5
  import { useTheme } from "next-themes";
4
- import { use, useEffect, useId, useState } from "react";
6
+ import { use, useCallback, useEffect, useId, useRef, useState } from "react";
5
7
  //#region src/components/mermaid.tsx
6
8
  function Mermaid({ chart }) {
7
9
  const [mounted, setMounted] = useState(false);
@@ -22,6 +24,20 @@ function cachePromise(key, setPromise) {
22
24
  function MermaidContent({ chart }) {
23
25
  const id = useId();
24
26
  const { resolvedTheme } = useTheme();
27
+ const [dialogState, setDialogState] = useState("closed");
28
+ const contentRef = useRef(null);
29
+ const overlayRef = useRef(null);
30
+ const prefersReducedMotion = useRef(false);
31
+ const pzRef = useRef(null);
32
+ useEffect(() => {
33
+ const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
34
+ prefersReducedMotion.current = mql.matches;
35
+ const handler = (e) => {
36
+ prefersReducedMotion.current = e.matches;
37
+ };
38
+ mql.addEventListener("change", handler);
39
+ return () => mql.removeEventListener("change", handler);
40
+ }, []);
25
41
  const mermaid = use(cachePromise("mermaid", () => import("mermaid").then((m) => m.default)));
26
42
  mermaid.initialize({
27
43
  startOnLoad: false,
@@ -33,12 +49,183 @@ function MermaidContent({ chart }) {
33
49
  const { svg, bindFunctions } = use(cachePromise(`${chart}-${resolvedTheme}`, () => {
34
50
  return mermaid.render(id, chart.replaceAll("\\n", "\n"));
35
51
  }));
36
- return /* @__PURE__ */ jsx("div", {
37
- ref: (container) => {
38
- if (container) bindFunctions?.(container);
52
+ const handleOpen = useCallback(() => {
53
+ if (dialogState === "opening" || dialogState === "open") return;
54
+ setDialogState("opening");
55
+ }, [dialogState]);
56
+ const handleClose = useCallback(() => {
57
+ if (dialogState === "closed" || dialogState === "closing") return;
58
+ setDialogState("closing");
59
+ }, [dialogState]);
60
+ useEffect(() => {
61
+ if (dialogState === "opening") requestAnimationFrame(() => {
62
+ requestAnimationFrame(() => {
63
+ setDialogState("open");
64
+ });
65
+ });
66
+ }, [dialogState]);
67
+ useEffect(() => {
68
+ if (dialogState !== "closing") return;
69
+ const fallbackTimer = setTimeout(() => {
70
+ setDialogState("closed");
71
+ }, 250);
72
+ return () => clearTimeout(fallbackTimer);
73
+ }, [dialogState]);
74
+ const handleOverlayTransitionEnd = useCallback((e) => {
75
+ if (e.target !== overlayRef.current) return;
76
+ if (dialogState === "closing") setDialogState("closed");
77
+ }, [dialogState]);
78
+ const handleReset = useCallback(() => {
79
+ pzRef.current?.reset();
80
+ }, []);
81
+ const handleZoomIn = useCallback(() => {
82
+ pzRef.current?.zoomIn();
83
+ }, []);
84
+ const handleZoomOut = useCallback(() => {
85
+ pzRef.current?.zoomOut();
86
+ }, []);
87
+ const handleCenter = useCallback(() => {
88
+ pzRef.current?.reset();
89
+ pzRef.current?.pan(0, 0);
90
+ }, []);
91
+ useEffect(() => {
92
+ if (dialogState === "closed" || !contentRef.current) return;
93
+ const pz = Panzoom(contentRef.current, {
94
+ maxScale: 5,
95
+ minScale: .1,
96
+ contain: "outside"
97
+ });
98
+ pzRef.current = pz;
99
+ contentRef.current.parentElement?.addEventListener("wheel", pz.zoomWithWheel);
100
+ return () => {
101
+ pz.destroy();
102
+ contentRef.current?.parentElement?.removeEventListener("wheel", pz.zoomWithWheel);
103
+ };
104
+ }, [dialogState]);
105
+ useEffect(() => {
106
+ if (dialogState === "closed") return;
107
+ const handleEscape = (e) => {
108
+ if (e.key === "Escape") handleClose();
109
+ };
110
+ document.addEventListener("keydown", handleEscape);
111
+ document.body.style.overflow = "hidden";
112
+ return () => {
113
+ document.removeEventListener("keydown", handleEscape);
114
+ document.body.style.overflow = "";
115
+ };
116
+ }, [dialogState, handleClose]);
117
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
118
+ type: "button",
119
+ className: "block w-full cursor-zoom-in",
120
+ onClick: handleOpen,
121
+ children: /* @__PURE__ */ jsx("div", {
122
+ className: "w-full cursor-zoom-in pointer-events-none",
123
+ ref: (container) => {
124
+ if (container) bindFunctions?.(container);
125
+ },
126
+ dangerouslySetInnerHTML: { __html: svg }
127
+ })
128
+ }), /* @__PURE__ */ jsx("div", {
129
+ ref: overlayRef,
130
+ role: "dialog",
131
+ "aria-modal": dialogState !== "closed" ? "true" : void 0,
132
+ className: `fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-200 ease-out ${dialogState === "closed" ? "pointer-events-none" : ""}`,
133
+ style: {
134
+ backgroundColor: "var(--color-fd-background)",
135
+ opacity: dialogState === "closed" || dialogState === "closing" ? 0 : 1,
136
+ transitionDuration: prefersReducedMotion.current ? "0.01ms" : void 0
39
137
  },
40
- dangerouslySetInnerHTML: { __html: svg }
41
- });
138
+ onClick: handleClose,
139
+ onTransitionEnd: handleOverlayTransitionEnd,
140
+ onKeyDown: (e) => {
141
+ if (e.key === "Escape") handleClose();
142
+ },
143
+ children: /* @__PURE__ */ jsxs("div", {
144
+ role: "document",
145
+ className: "relative overflow-hidden w-full h-full",
146
+ style: {
147
+ width: "100%",
148
+ height: "100%",
149
+ transform: dialogState === "closed" || dialogState === "closing" ? "scale(0.9)" : dialogState === "opening" ? "scale(0.9)" : "scale(1)",
150
+ opacity: dialogState === "closed" || dialogState === "closing" ? 0 : 1,
151
+ transition: dialogState === "closed" ? "none" : `transform ${prefersReducedMotion.current ? "0.01ms" : "200ms"} ease-out, opacity ${prefersReducedMotion.current ? "0.01ms" : "200ms"} ease-out`
152
+ },
153
+ onClick: (e) => e.stopPropagation(),
154
+ onKeyDown: (e) => e.stopPropagation(),
155
+ children: [/* @__PURE__ */ jsxs("div", {
156
+ className: "absolute top-4 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1 rounded-lg px-2 py-1.5 backdrop-blur-md",
157
+ style: { backgroundColor: "var(--color-fd-foreground)" },
158
+ children: [
159
+ /* @__PURE__ */ jsx("button", {
160
+ type: "button",
161
+ onClick: handleZoomIn,
162
+ className: "mermaid-toolbar-btn inline-flex items-center justify-center rounded-md p-1.5 transition-colors cursor-pointer",
163
+ style: {
164
+ color: "var(--color-fd-background)",
165
+ "--hover-bg": "var(--color-fd-background)",
166
+ "--hover-color": "var(--color-fd-foreground)"
167
+ },
168
+ title: "放大",
169
+ children: /* @__PURE__ */ jsx(ZoomIn, { size: 16 })
170
+ }),
171
+ /* @__PURE__ */ jsx("button", {
172
+ type: "button",
173
+ onClick: handleZoomOut,
174
+ className: "mermaid-toolbar-btn inline-flex items-center justify-center rounded-md p-1.5 transition-colors cursor-pointer",
175
+ style: {
176
+ color: "var(--color-fd-background)",
177
+ "--hover-bg": "var(--color-fd-background)",
178
+ "--hover-color": "var(--color-fd-foreground)"
179
+ },
180
+ title: "缩小",
181
+ children: /* @__PURE__ */ jsx(ZoomOut, { size: 16 })
182
+ }),
183
+ /* @__PURE__ */ jsx("button", {
184
+ type: "button",
185
+ onClick: handleReset,
186
+ className: "mermaid-toolbar-btn inline-flex items-center justify-center rounded-md p-1.5 transition-colors cursor-pointer",
187
+ style: {
188
+ color: "var(--color-fd-background)",
189
+ "--hover-bg": "var(--color-fd-background)",
190
+ "--hover-color": "var(--color-fd-foreground)"
191
+ },
192
+ title: "重置缩放",
193
+ children: /* @__PURE__ */ jsx(RotateCcw, { size: 16 })
194
+ }),
195
+ /* @__PURE__ */ jsx("button", {
196
+ type: "button",
197
+ onClick: handleCenter,
198
+ className: "mermaid-toolbar-btn inline-flex items-center justify-center rounded-md p-1.5 transition-colors cursor-pointer",
199
+ style: {
200
+ color: "var(--color-fd-background)",
201
+ "--hover-bg": "var(--color-fd-background)",
202
+ "--hover-color": "var(--color-fd-foreground)"
203
+ },
204
+ title: "定位到中心",
205
+ children: /* @__PURE__ */ jsx(LocateFixed, { size: 16 })
206
+ }),
207
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-white/20" }),
208
+ /* @__PURE__ */ jsx("button", {
209
+ type: "button",
210
+ onClick: handleClose,
211
+ className: "mermaid-toolbar-btn inline-flex items-center justify-center rounded-md p-1.5 transition-colors cursor-pointer",
212
+ style: {
213
+ color: "var(--color-fd-background)",
214
+ "--hover-bg": "var(--color-fd-background)",
215
+ "--hover-color": "var(--color-fd-foreground)"
216
+ },
217
+ title: "关闭",
218
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
219
+ })
220
+ ]
221
+ }), /* @__PURE__ */ jsx("div", {
222
+ ref: contentRef,
223
+ role: "img",
224
+ className: "flex w-full h-full items-center justify-center origin-top-left",
225
+ dangerouslySetInnerHTML: { __html: svg }
226
+ })]
227
+ })
228
+ })] });
42
229
  }
43
230
  //#endregion
44
231
  export { Mermaid };
@@ -4,7 +4,10 @@ import { RootProvider } from "fumadocs-ui/provider/next";
4
4
  //#region src/components/provider.tsx
5
5
  function Provider({ searchEnabled = true, children }) {
6
6
  return /* @__PURE__ */ jsx(RootProvider, {
7
- search: { enabled: searchEnabled },
7
+ search: {
8
+ enabled: searchEnabled,
9
+ options: { type: "static" }
10
+ },
8
11
  children
9
12
  });
10
13
  }
@@ -3,12 +3,14 @@ import * as PageTree from "fumadocs-core/page-tree";
3
3
  //#region src/utils/restructure-tree.d.ts
4
4
  interface SidebarConfigEntry {
5
5
  group: string;
6
+ icon?: string;
6
7
  collapsed?: boolean;
7
8
  pages: readonly {
8
9
  slug: string;
10
+ icon?: string;
9
11
  }[];
10
12
  }
11
13
  declare function slugToUrl(slug: string): string;
12
- declare function restructureTree(tree: PageTree.Root, sidebarConfig: readonly SidebarConfigEntry[]): PageTree.Root;
14
+ declare function restructureTree(tree: PageTree.Root, sidebarConfig: readonly SidebarConfigEntry[], iconMap?: Record<string, React.ReactNode>): PageTree.Root;
13
15
  //#endregion
14
16
  export { SidebarConfigEntry, restructureTree, slugToUrl };
@@ -2,7 +2,7 @@
2
2
  function slugToUrl(slug) {
3
3
  return slug === "index" ? "/" : `/${slug}`;
4
4
  }
5
- function restructureTree(tree, sidebarConfig) {
5
+ function restructureTree(tree, sidebarConfig, iconMap) {
6
6
  const consumed = /* @__PURE__ */ new Set();
7
7
  const newChildren = [];
8
8
  const children = tree.children ?? [];
@@ -13,13 +13,17 @@ function restructureTree(tree, sidebarConfig) {
13
13
  const idx = children.findIndex((c, i) => !consumed.has(i) && c.type === "page" && c.url === url);
14
14
  if (idx >= 0) {
15
15
  const node = children[idx];
16
- if (node) folderChildren.push(node);
16
+ if (node) folderChildren.push(page.icon && iconMap?.[page.icon] ? {
17
+ ...node,
18
+ icon: iconMap[page.icon]
19
+ } : node);
17
20
  consumed.add(idx);
18
21
  }
19
22
  }
20
23
  if (folderChildren.length > 0) newChildren.push({
21
24
  type: "folder",
22
25
  name: group.group,
26
+ icon: group.icon && iconMap ? iconMap[group.icon] : void 0,
23
27
  defaultOpen: !group.collapsed,
24
28
  children: folderChildren
25
29
  });
@@ -29,10 +33,26 @@ function restructureTree(tree, sidebarConfig) {
29
33
  const idx = children.findIndex((child, i) => !consumed.has(i) && child.type === "folder" && child.children?.some((c) => c.type === "page" && c.url?.startsWith(`/${dirPrefix}/`)));
30
34
  if (idx >= 0) {
31
35
  consumed.add(idx);
36
+ const originalFolder = children[idx];
37
+ const childrenWithIcons = (originalFolder.children ?? []).map((child) => {
38
+ if (child.type === "page") {
39
+ const matchedPage = group.pages.find((p) => {
40
+ const url = slugToUrl(p.slug);
41
+ return child.url === url;
42
+ });
43
+ if (matchedPage?.icon && iconMap?.[matchedPage.icon]) return {
44
+ ...child,
45
+ icon: iconMap[matchedPage.icon]
46
+ };
47
+ }
48
+ return child;
49
+ });
32
50
  newChildren.push({
33
- ...children[idx],
51
+ ...originalFolder,
34
52
  name: group.group,
35
- defaultOpen: !group.collapsed
53
+ icon: group.icon && iconMap ? iconMap[group.icon] : void 0,
54
+ defaultOpen: !group.collapsed,
55
+ children: childrenWithIcons
36
56
  });
37
57
  }
38
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmanual",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "author": "shenjingnan <sjn.code@gmail.com>",
5
5
  "description": "AI 友好的开源文档系统框架",
6
6
  "type": "module",
@@ -51,6 +51,7 @@
51
51
  "url": "https://github.com/shenjingnan/openmanual.git"
52
52
  },
53
53
  "dependencies": {
54
+ "@panzoom/panzoom": "^4.6.2",
54
55
  "chokidar": "^5.0.0",
55
56
  "commander": "^14.0.3",
56
57
  "fast-glob": "^3.3.3",
@@ -64,8 +65,10 @@
64
65
  "zod": "^4.0.0"
65
66
  },
66
67
  "peerDependencies": {
68
+ "lucide-react": ">=0.400.0",
67
69
  "mermaid": ">=11.0.0",
68
- "next-themes": ">=0.4.0"
70
+ "next-themes": ">=0.4.0",
71
+ "tailwind-merge": ">=2.0.0"
69
72
  },
70
73
  "devDependencies": {
71
74
  "@biomejs/biome": "^2.4.9",
@@ -86,7 +89,7 @@
86
89
  },
87
90
  "scripts": {
88
91
  "build": "tsdown",
89
- "dev": "concurrently -n tsdown,dev -c blue,green \"tsdown --watch\" \"sleep 2 && OPENMANUAL_ROOT=$(pwd) node dist/bin.js dev --watch --cwd docs\"",
92
+ "dev": "tsdown --watch",
90
93
  "dev:cli": "tsx watch src/cli/bin.ts",
91
94
  "test": "vitest run",
92
95
  "test:watch": "vitest",