openmanual 0.11.0 → 0.13.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
@@ -74,11 +74,6 @@ function collectConfiguredSlugs(config) {
74
74
  if (config.sidebar) for (const group of config.sidebar) for (const page of group.pages) slugs.add(page.slug);
75
75
  return slugs;
76
76
  }
77
- function buildTitleMap(config) {
78
- const map = {};
79
- if (config.sidebar) for (const group of config.sidebar) for (const page of group.pages) map[page.slug] = page.title;
80
- return map;
81
- }
82
77
  function isI18nEnabled(config) {
83
78
  return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;
84
79
  }
@@ -487,7 +482,7 @@ export default withMDX(config);
487
482
  //#endregion
488
483
  //#region src/core/generator/package-json.ts
489
484
  function getOpenManualVersion() {
490
- return "0.11.0";
485
+ return "0.13.0";
491
486
  }
492
487
  function generatePackageJson(ctx) {
493
488
  const openmanualVersion = getOpenManualVersion();
@@ -504,6 +499,7 @@ function generatePackageJson(ctx) {
504
499
  start: "next start"
505
500
  },
506
501
  dependencies: {
502
+ "@orama/orama": "^3.1.0",
507
503
  "@tailwindcss/postcss": "^4.1.15",
508
504
  "fumadocs-core": "^16.7.7",
509
505
  "fumadocs-mdx": "^14.2.11",
@@ -1149,76 +1145,11 @@ export const { staticGET: GET } = createFromSource(source);
1149
1145
  //#endregion
1150
1146
  //#region src/core/generator/source-config.ts
1151
1147
  function generateSourceConfig(_ctx) {
1152
- const titleMap = buildTitleMap(_ctx.config);
1153
- const titleMapEntries = Object.entries(titleMap).map(([slug, title]) => ` '${slug}': '${title.replace(/'/g, "\\'")}'`).join(",\n");
1154
- const titleMapStr = titleMapEntries ? `{\n${titleMapEntries}\n}` : "{}";
1155
- const isStrict = _ctx.config.contentPolicy !== "all";
1156
- const isI18n = isI18nEnabled(_ctx.config);
1157
- const useDirParser = isDirParser(_ctx.config);
1158
1148
  return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
1159
1149
  import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
1160
- import { z } from 'zod';
1161
-
1162
- const titleMap: Record<string, string> = ${titleMapStr};${isStrict ? `
1163
-
1164
- const allowedSlugs = new Set(${JSON.stringify([...collectConfiguredSlugs(_ctx.config)])});
1165
-
1166
- function slugFromPath(path: string): string {
1167
- const normalized = path.replace(/\\\\/g, '/');
1168
- const idx = normalized.indexOf('content/');
1169
- const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;${useDirParser ? `
1170
- // dir parser: 剥离语言目录前缀 content/en/guide/configuration.mdx -> guide/configuration
1171
- const parts = relative.split('/');
1172
- if (parts.length > 1 && /^[a-z]{2}(-[A-Z]{2})?$/i.test(parts[0])) {
1173
- return parts.slice(1).join('/').replace(/\\.(md|mdx)$/i, '');
1174
- }
1175
- return relative.replace(/\\.(md|mdx)$/i, '');` : `
1176
- let slug = relative.replace(/\\.(md|mdx)$/i, '');
1177
- ${isI18n ? ` // 剥离语言后缀:index.en -> index
1178
- slug = slug.replace(/\\.([a-z]{2}(-[A-Z]{2})?)$/, '');` : ""}
1179
- return slug;`}
1180
- }` : ""}
1181
- function titleFromPath(path: string): string {
1182
- ${useDirParser ? ` const normalized = path.replace(/\\\\/g, '/');
1183
- const idx = normalized.indexOf('content/');
1184
- const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;
1185
- // dir parser: 剥离语言目录前缀 content/en/guide/configuration.mdx -> guide/configuration
1186
- const parts = relative.split('/');
1187
- if (parts.length > 1 && /^[a-z]{2}(-[A-Z]{2})?$/i.test(parts[0])) {
1188
- const slug = parts.slice(1).join('/').replace(/\\.(md|mdx)$/i, '');
1189
- return titleMap[slug] || slug.split('/').pop() || slug;
1190
- }
1191
- const slug = relative.replace(/\\.(md|mdx)$/i, '');
1192
- return titleMap[slug] || slug.split('/').pop() || slug;` : ` const normalized = path.replace(/\\\\/g, '/');
1193
- const idx = normalized.indexOf('content/');
1194
- const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;
1195
- let slug = relative.replace(/\\.(md|mdx)$/i, '');
1196
- ${isI18n ? ` // 剥离语言后缀:guide/configuration.en -> guide/configuration
1197
- slug = slug.replace(/\\.([a-z]{2}(-[A-Z]{2})?)$/, '');` : ""}
1198
- return titleMap[slug] || slug.split('/').pop() || slug;`}
1199
- }
1200
1150
 
1201
1151
  export const docs = defineDocs({
1202
1152
  dir: 'content',
1203
- docs: {
1204
- schema: (ctx) =>
1205
- z.object({
1206
- title: z.string().optional(),
1207
- description: z.string().optional(),
1208
- icon: z.string().optional(),
1209
- full: z.boolean().optional(),
1210
- }).transform((data) => ({
1211
- ...data,
1212
- title: data.title ?? titleFromPath(ctx.path),
1213
- }))${isStrict ? `
1214
- .refine((_data) => {
1215
- const slug = slugFromPath(ctx.path);
1216
- if (allowedSlugs.size > 0 && !allowedSlugs.has(slug)) {
1217
- return false;
1218
- }
1219
- return true;
1220
- })` : ""},
1221
- },
1222
1153
  });
1223
1154
 
1224
1155
  export default defineConfig({
@@ -1496,36 +1427,13 @@ function generateDocsLayout(ctx) {
1496
1427
  const githubLine = githubLink ? `\n github: '${githubLink}',` : "";
1497
1428
  const linksLine = linksArray.length > 0 ? `\n links: ${JSON.stringify(linksArray)},` : "";
1498
1429
  const footerLine = footerText ? `\n footer: { children: '${footerText.replace(/'/g, "\\'")}' },` : "";
1499
- const sidebar = config.sidebar;
1500
- const hasSidebar = sidebar && sidebar.length > 0;
1501
- const iconNames = /* @__PURE__ */ new Set();
1502
- if (hasSidebar) for (const g of sidebar ?? []) {
1503
- if (g.icon) iconNames.add(g.icon);
1504
- for (const p of g.pages) if (p.icon) iconNames.add(p.icon);
1505
- }
1506
- const hasIcons = iconNames.size > 0;
1507
- const iconNameList = [...iconNames];
1508
- const sidebarConfigSnippet = hasSidebar ? `\nconst sidebarConfig = ${JSON.stringify((sidebar ?? []).map((g) => ({
1509
- group: g.group,
1510
- icon: g.icon,
1511
- collapsed: g.collapsed,
1512
- pages: g.pages.map((p) => ({
1513
- slug: p.slug,
1514
- icon: p.icon
1515
- }))
1516
- })), null, 2)} as const;
1517
- ` : "";
1518
- const lucideImportLine = hasIcons ? `\nimport { ${iconNameList.join(", ")} } from 'lucide-react';` : "";
1519
- const iconMapSnippet = hasIcons ? `\nconst iconMap = {${iconNameList.map((name) => `\n ${name}: <${name} />,`).join("")}\n} as const;
1520
- ` : "";
1521
- const treeLine = hasSidebar ? hasIcons ? isI18n ? "tree: restructureTree(source.getPageTree(lang), sidebarConfig, iconMap)," : "tree: restructureTree(source.getPageTree(), sidebarConfig, iconMap)," : isI18n ? "tree: restructureTree(source.getPageTree(lang), sidebarConfig)," : "tree: restructureTree(source.getPageTree(), sidebarConfig)," : isI18n ? "tree: source.getPageTree(lang)," : "tree: source.getPageTree(),";
1522
- const restructureTreeImport = hasSidebar ? "\nimport { restructureTree } from 'openmanual/utils/restructure-tree';" : "";
1430
+ const configDesc = config.description ?? "";
1431
+ const descLine = configDesc ? isI18n ? "" : `description: '${configDesc.replace(/'/g, "\\'")}',` : "";
1432
+ const treeLine = isI18n ? "tree: source.getPageTree(lang)," : "tree: source.getPageTree(),";
1523
1433
  if (isI18n) return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
1524
1434
  import { baseOptions } from '@/lib/layout';
1525
1435
  import { source } from '@/lib/source';
1526
- import type { ReactNode } from 'react';${restructureTreeImport}${lucideImportLine}
1527
- ${sidebarConfigSnippet}${iconMapSnippet}
1528
-
1436
+ import type { ReactNode } from 'react';${configDesc ? `\nconst configDescription = '${configDesc.replace(/'/g, "\\'")}' as const;\n` : ""}
1529
1437
  export default async function DocsLayoutWrapper({
1530
1438
  params,
1531
1439
  children,
@@ -1533,11 +1441,13 @@ export default async function DocsLayoutWrapper({
1533
1441
  params: Promise<{ lang: string }>;
1534
1442
  children: ReactNode;
1535
1443
  }) {
1536
- const { lang } = await params;
1444
+ const { lang } = await params;${configDesc ? `
1445
+ const indexPage = source.getPage([], lang);
1446
+ const siteDescription = indexPage?.data.description ?? configDescription;` : ""}
1537
1447
 
1538
1448
  const docsOptions = {
1539
1449
  ...baseOptions(lang),
1540
- ${treeLine}${githubLine}${linksLine}${footerLine}
1450
+ ${treeLine}${githubLine}${linksLine}${footerLine}${configDesc ? "\n description: siteDescription," : ""}
1541
1451
  };
1542
1452
 
1543
1453
  return (
@@ -1550,11 +1460,10 @@ export default async function DocsLayoutWrapper({
1550
1460
  return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
1551
1461
  import { baseOptions } from '@/lib/layout';
1552
1462
  import { source } from '@/lib/source';
1553
- import type { ReactNode } from 'react';${restructureTreeImport}${lucideImportLine}
1554
- ${sidebarConfigSnippet}${iconMapSnippet}
1463
+ import type { ReactNode } from 'react';
1555
1464
  const docsOptions = {
1556
1465
  ...baseOptions(),
1557
- ${treeLine}${githubLine}${linksLine}${footerLine}
1466
+ ${treeLine}${githubLine}${linksLine}${footerLine}${descLine}
1558
1467
  };
1559
1468
 
1560
1469
  export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
@@ -1588,35 +1497,124 @@ async function ensureLogoFile(ctx, logoPath, variant) {
1588
1497
  }
1589
1498
  }
1590
1499
  /**
1591
- * Generate meta.json (and meta.en.json in i18n mode) for each sidebar
1592
- * group directory so that fumadocs displays the configured group name.
1500
+ * Generate complete meta.json for each sidebar group directory.
1501
+ * Writes title, icon, defaultOpen, and pages ordering so that Fumadocs
1502
+ * can render the sidebar natively without restructureTree().
1503
+ *
1504
+ * In dir-parser mode, generates meta.json inside each language subdirectory
1505
+ * (e.g. content/zh/guide/meta.json). In dot-parser mode, generates at the
1506
+ * group directory level with locale-suffixed files for i18n.
1593
1507
  */
1594
1508
  async function generateMetaFiles(ctx) {
1595
1509
  const sidebar = ctx.config.sidebar;
1596
1510
  if (!sidebar || sidebar.length === 0) return;
1597
1511
  const contentAbsDir = join(ctx.projectDir, ctx.contentDir);
1598
1512
  const isI18n = isI18nEnabled(ctx.config);
1513
+ const useDirParser = isDirParser(ctx.config);
1514
+ const languages = isI18n ? (ctx.config.i18n?.languages ?? []).map((l) => l.code) : [];
1599
1515
  for (const group of sidebar) {
1516
+ if (group.pages.every((p) => !p.slug.includes("/"))) {
1517
+ await generateRootMetaJson(ctx, group, contentAbsDir, languages, useDirParser);
1518
+ continue;
1519
+ }
1600
1520
  const dirPrefix = group.pages.map((p) => p.slug).find((slug) => slug.includes("/"))?.split("/")[0];
1601
1521
  if (!dirPrefix) continue;
1602
- const dirPath = join(contentAbsDir, dirPrefix);
1603
- const metaPath = join(dirPath, "meta.json");
1604
- try {
1605
- await access(metaPath);
1606
- } catch {
1607
- await mkdir(dirPath, { recursive: true });
1608
- await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\n`, "utf-8");
1609
- }
1610
- if (isI18n) {
1611
- const metaEnPath = join(dirPath, "meta.en.json");
1612
- try {
1613
- await access(metaEnPath);
1614
- } catch {
1615
- await mkdir(dirPath, { recursive: true });
1616
- await writeFile(metaEnPath, `${JSON.stringify({ title: group.group }, null, 2)}\n`, "utf-8");
1522
+ const metaObj = { title: group.group };
1523
+ if (group.icon) metaObj.icon = group.icon;
1524
+ if (group.collapsed !== void 0) metaObj.defaultOpen = !group.collapsed;
1525
+ const pageFiles = group.pages.filter((p) => p.slug.startsWith(`${dirPrefix}/`)).map((p) => p.slug.split("/").slice(1).join("/"));
1526
+ if (pageFiles.length > 0) metaObj.pages = pageFiles;
1527
+ if (useDirParser) for (const lang of languages) await writeMetaIfNotExists(join(join(contentAbsDir, lang, dirPrefix), "meta.json"), metaObj);
1528
+ else {
1529
+ const dirPath = join(contentAbsDir, dirPrefix);
1530
+ await writeMetaIfNotExists(join(dirPath, "meta.json"), metaObj);
1531
+ if (isI18n) for (const lang of languages) {
1532
+ if (lang === ctx.config.i18n?.defaultLanguage) continue;
1533
+ await writeMetaIfNotExists(join(dirPath, `meta.${lang}.json`), metaObj);
1617
1534
  }
1618
1535
  }
1619
1536
  }
1537
+ await injectPageFrontmatter(ctx);
1538
+ }
1539
+ /**
1540
+ * Generate meta.json for root-level page groups (pages without directory prefix).
1541
+ * Writes to content/{lang}/meta.json (dir-parser) or content/meta.json (dot-parser).
1542
+ */
1543
+ async function generateRootMetaJson(_ctx, group, contentAbsDir, languages, useDirParser) {
1544
+ const metaObj = { title: group.group };
1545
+ if (group.icon) metaObj.icon = group.icon;
1546
+ if (group.collapsed !== void 0) metaObj.defaultOpen = !group.collapsed;
1547
+ const pageFiles = group.pages.map((p) => p.slug);
1548
+ if (pageFiles.length > 0) metaObj.pages = pageFiles;
1549
+ if (useDirParser) for (const lang of languages) await writeMetaIfNotExists(join(contentAbsDir, lang, "meta.json"), metaObj);
1550
+ else await writeMetaIfNotExists(join(contentAbsDir, "meta.json"), metaObj);
1551
+ }
1552
+ /**
1553
+ * Write meta.json only if it does not already exist (preserve user edits).
1554
+ */
1555
+ async function writeMetaIfNotExists(filePath, data) {
1556
+ try {
1557
+ await access(filePath);
1558
+ } catch {
1559
+ await mkdir(join(filePath, ".."), { recursive: true });
1560
+ await writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
1561
+ }
1562
+ }
1563
+ /**
1564
+ * Inject page-level title and icon from sidebar config into each .mdx file's frontmatter.
1565
+ * Uses upsert semantics: only writes fields that are missing from existing frontmatter.
1566
+ */
1567
+ async function injectPageFrontmatter(ctx) {
1568
+ const sidebar = ctx.config.sidebar;
1569
+ if (!sidebar) return;
1570
+ const contentAbsDir = join(ctx.projectDir, ctx.contentDir);
1571
+ const useDirParser = isDirParser(ctx.config);
1572
+ const languages = isI18nEnabled(ctx.config) ? (ctx.config.i18n?.languages ?? []).map((l) => l.code) : [];
1573
+ for (const group of sidebar) for (const page of group.pages) {
1574
+ const fieldsToInject = {};
1575
+ if (page.title) fieldsToInject.title = page.title;
1576
+ if (page.icon) fieldsToInject.icon = page.icon;
1577
+ if (Object.keys(fieldsToInject).length === 0) continue;
1578
+ const targets = resolveMdxPaths(contentAbsDir, page.slug, useDirParser, languages);
1579
+ for (const mdxPath of targets) try {
1580
+ const content = await readFile(mdxPath, "utf-8");
1581
+ const updated = upsertFrontmatter(content, fieldsToInject);
1582
+ if (updated !== content) await writeFile(mdxPath, updated, "utf-8");
1583
+ } catch {}
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Resolve .mdx file paths for a given slug across all applicable locales.
1588
+ */
1589
+ function resolveMdxPaths(contentAbsDir, slug, useDirParser, languages) {
1590
+ if (useDirParser) return languages.map((lang) => join(contentAbsDir, lang, `${slug}.mdx`));
1591
+ return [join(contentAbsDir, `${slug}.mdx`)];
1592
+ }
1593
+ /**
1594
+ * Upsert fields into MDX frontmatter. Only adds missing fields; never overwrites existing ones.
1595
+ *
1596
+ * Handles:
1597
+ * - No frontmatter → creates one with the injected fields
1598
+ * - Existing frontmatter missing some fields → adds only the missing ones
1599
+ * - All fields already present → returns original content unchanged
1600
+ */
1601
+ function upsertFrontmatter(content, fields) {
1602
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1603
+ if (!match) return `---\n${Object.entries(fields).map(([key, value]) => `${key}: ${value}`).join("\n")}\n---\n\n${content}`;
1604
+ const existingLines = (match[1] ?? "").split("\n");
1605
+ const existingKeys = /* @__PURE__ */ new Set();
1606
+ for (const line of existingLines) {
1607
+ const trimmed = line.trim();
1608
+ if (trimmed && !trimmed.startsWith("#")) {
1609
+ const keyMatch = trimmed.match(/^(\w[\w-]*)\s*:/);
1610
+ if (keyMatch?.[1]) existingKeys.add(keyMatch[1]);
1611
+ }
1612
+ }
1613
+ const newFields = Object.entries(fields).filter(([key]) => !existingKeys.has(key));
1614
+ if (newFields.length === 0) return content;
1615
+ const newFieldLines = newFields.map(([key, value]) => `${key}: ${value}`).join("\n");
1616
+ const insertionPoint = (match.index ?? 0) + match[0].length - 3;
1617
+ return content.slice(0, insertionPoint) + "\n" + newFieldLines + "\n" + content.slice(insertionPoint);
1620
1618
  }
1621
1619
 
1622
1620
  //#endregion
@@ -2072,7 +2070,7 @@ const regenerateCommand = new Command("_regenerate").description("内部命令
2072
2070
  //#endregion
2073
2071
  //#region src/cli/bin.ts
2074
2072
  function getVersion() {
2075
- return "0.11.0";
2073
+ return "0.13.0";
2076
2074
  }
2077
2075
  const program = new Command();
2078
2076
  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/callout-component.ts","../src/core/generator/global-css.ts","../src/core/generator/i18n-config.ts","../src/core/generator/i18n-ui.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/middleware.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/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 FaviconSchema = 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 I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).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 favicon: FaviconSchema.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 i18n: I18nConfigSchema.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>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\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\nexport function isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\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 i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\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 { OpenManualConfig } from '../config/schema.js';\n\n/**\n * 生成 lib/i18n.ts\n *\n * 使用 fumadocs-core 的 defineI18n 定义多语言配置。\n */\nexport function generateI18nConfig(_ctx: { config: OpenManualConfig }): string {\n const i18nCfg = _ctx.config.i18n;\n if (!i18nCfg?.enabled || !i18nCfg.languages || i18nCfg.languages.length < 2) {\n throw new Error('generateI18nConfig called but i18n is not properly configured');\n }\n\n const defaultLang = i18nCfg.defaultLanguage ?? _ctx.config.locale ?? 'zh';\n const languageCodes = i18nCfg.languages.map((l) => `'${l.code}'`).join(', ');\n const parserLine = i18nCfg.parser === 'dir' ? `\\n parser: 'dir',` : '';\n\n return `import { defineI18n } from 'fumadocs-core/i18n';\n\nexport const i18n = defineI18n({\n defaultLanguage: '${defaultLang}',\n languages: [${languageCodes}],${parserLine}\n});\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\n/**\n * 生成 lib/i18n-ui.ts\n *\n * 使用 fumadocs-ui 的 defineI18nUI 定义各语言的 UI 翻译(显示名称等)。\n */\nexport function generateI18nUI(_ctx: { config: OpenManualConfig }): string {\n const i18nCfg = _ctx.config.i18n;\n if (!i18nCfg?.languages || i18nCfg.languages.length === 0) {\n throw new Error('generateI18nUI called but no languages configured');\n }\n\n const langEntries = i18nCfg.languages\n .map(\n (lang) => ` '${lang.code}': {\n displayName: '${lang.name}',\n }`\n )\n .join(',\\n');\n\n return `import { defineI18nUI } from 'fumadocs-ui/i18n';\nimport { i18n } from '@/lib/i18n';\n\nexport const i18nUI = defineI18nUI(i18n, {\n translations: {\n${langEntries}\n },\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 const isI18n = config.i18n?.enabled === true;\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 if (isI18n) {\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(_locale: string): BaseLayoutProps {\n return {\n nav: {\n title: <NavLogo ${logoProps} /> as ReactNode,\n },\n };\n}\n`;\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","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateLibSource(ctx: { config: OpenManualConfig }): string {\n const isI18n = ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\nimport { i18n } from '@/lib/i18n';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n i18n,\n});\n`;\n }\n\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\n/**\n * 生成 middleware.ts(或 proxy.ts)\n *\n * 使用自定义的轻量 i18n 中间件,仅处理:\n * 1. 根路径 / → 重定向到默认语言(如 /zh)\n * 2. 其他所有请求(包括静态资源)→ 直接放行\n *\n * 注意:Next.js 16 已废弃 middleware 推荐使用 proxy,\n * 但 fumadocs-core 尚未提供 createI18nProxy,\n * 因此使用自定义实现避免 createI18nMiddleware 拦截静态资源导致 404。\n */\nexport function generateMiddleware(_ctx: { config: OpenManualConfig }): string {\n const defaultLang = _ctx.config.i18n?.defaultLanguage ?? _ctx.config.locale ?? 'zh';\n\n return `import { NextResponse } from 'next/server';\n\nconst defaultLanguage = '${defaultLang}';\n\nexport default function middleware(request: Request): NextResponse | undefined {\n const { pathname } = new URL(request.url);\n\n // 仅处理根路径重定向,其他请求(含静态资源)放行\n if (pathname === '/') {\n return NextResponse.redirect(new URL('/' + defaultLanguage, request.url));\n }\n\n return undefined;\n}\n\nexport const config = {\n matcher: ['/((?!api|_next/static|_next/image|favicon\\\\.ico).*)'],\n};\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 const isI18n = _ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return generatePageI18n(_ctx, isStrict, pageActionsEnabled);\n }\n\n return generatePageSingle(_ctx, isStrict, pageActionsEnabled);\n}\n\nfunction generatePageSingle(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean\n): string {\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\nfunction generatePageI18n(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean\n): string {\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[]; lang: string }) => isAllowed(p.slug));\n // Ensure every language has a homepage entry (slug: [])\n const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];\n for (const lang of languages) {\n if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {\n const firstForLang = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (firstForLang) {\n params.unshift({ ...firstForLang, slug: [] });\n }\n }\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n // Ensure every language has a homepage entry (slug: [])\n const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];\n for (const lang of languages) {\n if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {\n const firstForLang = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (firstForLang) {\n params.unshift({ ...firstForLang, slug: [] });\n }\n }\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[]; lang: string }> }) {\n const { slug, lang } = await params;\n const page = source.getPage(slug, lang);\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\n/**\n * 生成 app/provider.tsx(或 app/[lang]/provider.tsx)\n *\n * 重要:直接从 fumadocs-ui 导入组件,而非通过 openmanual/components/provider 中转。\n * 这避免了 pnpm file: 协议下 fumadocs-ui 被安装两次(一次作为 openmanual 的依赖,\n * 一次作为生成应用的依赖)导致的多实例 React Context 问题。\n */\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n const isI18n = ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return `'use client';\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport SafeSearchDialog from './components/search-dialog';\nimport { i18nUI } from '@/lib/i18n-ui';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children, lang }: { children: ReactNode; lang: string }) {\n return (\n <RootProvider\n i18n={i18nUI.provider(lang)}\n search={{\n enabled: ${searchEnabled},\n SearchDialog: SafeSearchDialog,\n options: { type: 'static', api: '/api/search' },\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n }\n\n return `'use client';\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport SafeSearchDialog from './components/search-dialog';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children }: { children: ReactNode }) {\n return (\n <RootProvider\n search={{\n enabled: ${searchEnabled},\n SearchDialog: SafeSearchDialog,\n options: { type: 'static', api: '/api/search' },\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n}\n\n/**\n * 生成 app/components/search-dialog.tsx(或 app/[lang]/components/search-dialog.tsx)\n *\n * SafeSearchDialog 组件直接放在生成应用中,确保所有 fumadocs-ui 导入\n * 都来自同一个实例,避免 React Context 跨实例失效的问题。\n *\n * 重要修复:注入自定义 initOrama 解决 Orama 不支持中文等语言的问题。\n *\n * 问题根因:\n * fumadocs-core 的 orama-static.js 在运行时调用 create({ language: 'zh' }),\n * 但 @orama/orama 不支持 'zh' 作为 language 参数(仅支持 31 种语言),\n * 会抛出 LANGUAGE_NOT_SUPPORTED 错误导致搜索功能完全失效。\n *\n * 修复方案:\n * 利用 fumadocs-core 的 StaticOptions.initOrama 扩展点,提供自定义的\n * initOrama 函数。对于 Orama 不支持的语言(如 zh),不传 language 参数,\n * 让 Orama 使用默认的 english 分词器;对于支持的语言正常传入。\n */\nexport function generateSearchDialog(_ctx?: { config: OpenManualConfig }): string {\n return `'use client';\n\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { useOnChange } from 'fumadocs-core/utils/use-on-change';\nimport { create } from '@orama/orama';\nimport {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n TagsList,\n TagsListItem,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';\nimport { useMemo, useState } from 'react';\n\n/**\n * Orama 支持的语言名称集合。\n *\n * 不在此列表中的语言(如中文 zh)不能作为 language 参数传入 @orama/orama 的 create(),\n * 否则会抛出 LANGUAGE_NOT_SUPPORTED 错误导致搜索功能完全失效。\n *\n * 来源:@orama/orama 内部的 SUPPORTED_LANGUAGES 列表,\n * 与 fumadocs-core/dist/search/server.js 中的 STEMMERS 保持一致。\n */\nconst SUPPORTED_ORAMA_LANGUAGES = new Set([\n 'arabic', 'armenian', 'bulgarian', 'czech', 'danish', 'dutch',\n 'english', 'finnish', 'french', 'german', 'greek', 'hungarian',\n 'indian', 'indonesian', 'irish', 'italian', 'lithuanian', 'nepali',\n 'norwegian', 'portuguese', 'romanian', 'russian', 'serbian',\n 'slovenian', 'spanish', 'swedish', 'tamil', 'turkish',\n 'ukrainian', 'sanskrit',\n]);\n\n/**\n * 将 locale code(如 'zh', 'en')映射为 Orama 支持的 language 全名。\n *\n * 对于不支持的语言返回 undefined,此时 create() 不传 language 参数,\n * Orama 会默认使用 english 分词器(对中文等语言做基本的空格/标点分词)。\n */\nfunction resolveOramaLanguage(localeCode: string): string | undefined {\n const map: Record<string, string> = {\n ar: 'arabic', am: 'armenian', bg: 'bulgarian', cz: 'czech',\n dk: 'danish', nl: 'dutch', en: 'english', fi: 'finnish',\n fr: 'french', de: 'german', gr: 'greek', hu: 'hungarian',\n in: 'indian', id: 'indonesian', ie: 'irish', it: 'italian',\n lt: 'lithuanian', np: 'nepali', no: 'norwegian', pt: 'portuguese',\n ro: 'romanian', ru: 'russian', rs: 'serbian', sl: 'slovenian',\n es: 'spanish', se: 'swedish', ta: 'tamil', tr: 'turkish',\n uk: 'ukrainian', sk: 'sanskrit',\n };\n const langName = map[localeCode];\n return langName && SUPPORTED_ORAMA_LANGUAGES.has(langName) ? langName : undefined;\n}\n\ninterface SafeSearchDialogProps {\n defaultTag?: string;\n tags?: { value: string; name: string }[];\n api?: string;\n delayMs?: number;\n type?: 'fetch' | 'static';\n allowClear?: boolean;\n links?: [string, string][];\n footer?: React.ReactNode;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport default function SafeSearchDialog({\n defaultTag,\n tags = [],\n api,\n delayMs,\n type = 'fetch',\n allowClear = false,\n links = [],\n footer,\n open = false,\n onOpenChange = (): void => {},\n}: SafeSearchDialogProps) {\n const { locale } = useI18n();\n const [tag, setTag] = useState(defaultTag);\n\n /**\n * 自定义 initOrama:根据 locale 是否受 Orama 支持,决定是否传入 language 参数。\n *\n * 这解决了 Orama 不支持 'zh' 等语言时抛出 LANGUAGE_NOT_SUPPORTED 导致搜索失效的问题。\n * fumadocs-core 的 StaticOptions 类型已官方暴露 initOrama 参数供此用途。\n */\n const safeInitOrama = useMemo(\n () => (localeCode?: string) => {\n const lang = localeCode ? resolveOramaLanguage(localeCode) : undefined;\n return create({\n schema: { _: 'string' },\n ...(lang ? { language: lang } : {}),\n });\n },\n [],\n );\n\n const { search, setSearch, query } = useDocsSearch(\n type === 'fetch'\n ? {\n type: 'fetch',\n ...(api != null && { api }),\n ...(locale != null && { locale }),\n ...(tag != null && { tag }),\n ...(delayMs != null && { delayMs }),\n }\n : {\n type: 'static',\n ...(api != null && { from: api }),\n ...(locale != null && { locale }),\n ...(tag != null && { tag }),\n ...(delayMs != null && { delayMs }),\n initOrama: safeInitOrama,\n }\n );\n\n const defaultItems = useMemo(() => {\n if (links.length === 0) return null;\n return links.map(([name, link]) => ({\n type: 'page' as const,\n id: name,\n content: name,\n url: link,\n }));\n }, [links]);\n\n useOnChange(defaultTag, (v) => {\n setTag(v);\n });\n\n // 核心修复:使用 Array.isArray 守卫,防止非数组值导致 .map() 报错\n const safeItems = Array.isArray(query.data) ? query.data : defaultItems;\n\n return (\n <SearchDialog\n open={open}\n onOpenChange={onOpenChange}\n search={search}\n onSearchChange={setSearch}\n isLoading={query.isLoading}\n >\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={safeItems} />\n </SearchDialogContent>\n <SearchDialogFooter>\n {tags.length > 0 && (\n <TagsList {...(tag != null && { tag })} onTagChange={setTag} allowClear={allowClear}>\n {tags.map((tagItem) => (\n <TagsListItem key={tagItem.value} value={tagItem.value}>\n {tagItem.name}\n </TagsListItem>\n ))}\n </TagsList>\n )}\n {footer}\n </SearchDialogFooter>\n </SearchDialog>\n );\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\nimport { isDirParser } from '../config/schema.js';\n\nexport function generateRawContentRoute(ctx: { config: OpenManualConfig }): string {\n const isI18n = ctx.config.i18n?.enabled === true;\n const useDirParser = isDirParser(ctx.config);\n\n if (isI18n && useDirParser) {\n // === Dir parser 模式:文件在 content/{lang}/{slug}.ext ===\n const defaultLang = ctx.config.i18n?.defaultLanguage ?? ctx.config.locale ?? 'zh';\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nconst _defaultLang = '${defaultLang}';\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 // 从查询参数获取语言,回退到默认语言(API 路由不在 [lang] 路径段下)\n const { searchParams } = new URL(request.url);\n const lang = searchParams.get('lang') ?? _defaultLang;\n // dir parser: 文件位于 content/{lang}/{slug}.ext\n for (const ext of ['.mdx', '.md']) {\n try {\n const filePath = join(process.cwd(), 'content', lang, \\`\\${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\n if (isI18n) {\n // === Dot parser 模式:文件在 content/{slug}.{lang}.ext ===\n const defaultLang = ctx.config.i18n?.defaultLanguage ?? ctx.config.locale ?? 'zh';\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nconst _defaultLang = '${defaultLang}';\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 // 从查询参数获取语言,回退到默认语言(API 路由不在 [lang] 路径段下)\n const { searchParams } = new URL(request.url);\n const lang = searchParams.get('lang') ?? _defaultLang;\n // 尝试带语言后缀的文件,再回退到默认语言文件\n const suffix = lang !== _defaultLang ? \\`.\\${lang}\\` : '';\n for (const ext of ['.mdx', '.md']) {\n // 先尝试带后缀\n if (suffix) {\n try {\n const filePath = join(process.cwd(), 'content', \\`\\${slug}\\${suffix}\\${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 /* 回退 */\n }\n }\n // 再尝试不带后缀(默认语言或 fallback)\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\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 type { OpenManualConfig } from '../config/schema.js';\n\n/**\n * Orama/FlexSearch 支持的语言映射(来自 fumadocs-core/dist/search/server.js STEMMERS)\n *\n * key = 语言全名(传给 tokenizer 的 language 值),value = 语言代码(locale code)\n *\n * 不在此列表中的语言(如中文 zh)不能作为 language 参数传入,\n * 否则构建时会抛出 \"Language X is not supported\" 错误。\n * 对于不支持的语言,传入空对象 {} 让 Orama 使用默认分词器。\n */\nconst SUPPORTED_LOCALE_MAP: Record<string, string> = {\n arabic: 'ar',\n armenian: 'am',\n bulgarian: 'bg',\n czech: 'cz',\n danish: 'dk',\n dutch: 'nl',\n english: 'en',\n finnish: 'fi',\n french: 'fr',\n german: 'de',\n greek: 'gr',\n hungarian: 'hu',\n indian: 'in',\n indonesian: 'id',\n irish: 'ie',\n italian: 'it',\n lithuanian: 'lt',\n nepali: 'np',\n norwegian: 'no',\n portuguese: 'pt',\n romanian: 'ro',\n russian: 'ru',\n serbian: 'rs',\n slovenian: 'ru',\n spanish: 'es',\n swedish: 'se',\n tamil: 'ta',\n turkish: 'tr',\n ukrainian: 'uk',\n sanskrit: 'sk',\n};\n\n/**\n * 根据语言代码查找对应的支持的 language 名称\n * 例如:'en' → 'english','zh' → undefined(不支持)\n */\nfunction resolveLanguageName(localeCode: string): string | undefined {\n return Object.keys(SUPPORTED_LOCALE_MAP).find((key) => SUPPORTED_LOCALE_MAP[key] === localeCode);\n}\n\nexport function generateSearchRoute(ctx?: { config: OpenManualConfig }): string {\n const i18nCfg = ctx?.config.i18n;\n const isI18n = i18nCfg?.enabled === true && i18nCfg.languages && i18nCfg.languages.length >= 2;\n\n // i18n 模式下需要显式配置 localeMap:\n // - 支持的语言映射到对应的 language 名称(如 en → 'english')\n // - 不支持的语言(如 zh/中文)传空对象,让 Orama 使用默认分词器\n if (isI18n) {\n const localeMapEntries = (i18nCfg?.languages ?? [])\n .map((l) => {\n const langName = resolveLanguageName(l.code);\n if (langName) {\n return ` ${l.code}: '${langName}'`;\n }\n // 不支持的语言(如中文 zh):传空对象让 Orama 使用默认分词器\n return ` ${l.code}: {}`;\n })\n .join(',\\n');\n\n // 将 localeMap 定义为独立变量(Record<string, unknown>),\n // 再通过 as any 传入 createFromSource 以绕过 fumadocs-core 的严格类型约束。\n // 不能直接在对象字面量中用 (key as any),因为 Turbopack 不支持该语法。\n return `import { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n\nexport const revalidate = false;\nconst _localeMap: Record<string, unknown> = {\n${localeMapEntries},\n};\nexport const { staticGET: GET } = createFromSource(source, {\n localeMap: _localeMap as any,\n});\n`;\n }\n\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 {\n buildTitleMap,\n collectConfiguredSlugs,\n isDirParser,\n isI18nEnabled,\n type OpenManualConfig,\n} 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 const isI18n = isI18nEnabled(_ctx.config);\n const useDirParser = isDirParser(_ctx.config);\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 useDirParser\n ? `\n // dir parser: 剥离语言目录前缀 content/en/guide/configuration.mdx -> guide/configuration\n const parts = relative.split('/');\n if (parts.length > 1 && /^[a-z]{2}(-[A-Z]{2})?$/i.test(parts[0])) {\n return parts.slice(1).join('/').replace(/\\\\.(md|mdx)$/i, '');\n }\n return relative.replace(/\\\\.(md|mdx)$/i, '');`\n : `\n let slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n${\n isI18n\n ? ` // 剥离语言后缀:index.en -> index\n slug = slug.replace(/\\\\.([a-z]{2}(-[A-Z]{2})?)$/, '');`\n : ''\n}\n return slug;`\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 // titleFromPath: dir 模式剥离语言目录前缀,dot 模式(i18n)剥离文件后缀\n const titleFromPathBody = useDirParser\n ? ` const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n // dir parser: 剥离语言目录前缀 content/en/guide/configuration.mdx -> guide/configuration\n const parts = relative.split('/');\n if (parts.length > 1 && /^[a-z]{2}(-[A-Z]{2})?$/i.test(parts[0])) {\n const slug = parts.slice(1).join('/').replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;\n }\n const slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;`\n : ` const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n let slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n${\n isI18n\n ? ` // 剥离语言后缀:guide/configuration.en -> guide/configuration\n slug = slug.replace(/\\\\.([a-z]{2}(-[A-Z]{2})?)$/, '');`\n : ''\n}\n return titleMap[slug] || slug.split('/').pop() || slug;`;\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${titleFromPathBody}\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 { isI18nEnabled } from '../config/schema.js';\nimport { generateCalloutComponent } from './callout-component.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateI18nConfig } from './i18n-config.js';\nimport { generateI18nUI } from './i18n-ui.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateMiddleware } from './middleware.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, generateSearchDialog } 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 .cache/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 isI18n = isI18nEnabled(ctx.config);\n\n // 基础配置文件(两种模式共用)\n const baseFiles: 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(ctx),\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 ];\n\n let files: Array<{ path: string; content: string }>;\n\n if (isI18n) {\n // === 多语言模式:[lang]/ 动态路由结构 ===\n files = [\n ...baseFiles,\n // i18n 核心文件\n { path: 'lib/i18n.ts', content: generateI18nConfig(ctx) },\n { path: 'lib/i18n-ui.ts', content: generateI18nUI(ctx) },\n // 中间件:重定向 / 到默认语言\n { path: 'middleware.ts', content: generateMiddleware(ctx) },\n // API 路由(放在 app/ 下,middleware 排除 /api/)\n ...(ctx.dev\n ? [\n { path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute(ctx) },\n { path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) },\n ]\n : [{ path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) }]),\n // [lang]/ 路由结构\n {\n path: 'app/[lang]/layout.tsx',\n content: generateRootLayoutI18n(ctx),\n },\n {\n path: 'app/[lang]/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[lang]/components/search-dialog.tsx',\n content: generateSearchDialog(ctx),\n },\n {\n path: 'app/[lang]/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[lang]/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n } else {\n // === 单语言模式(原有结构,不变)===\n files = [\n ...baseFiles,\n // API 路由:raw content 仅 dev 模式;搜索路由两种模式都生成\n ...(ctx.dev\n ? [\n { path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute(ctx) },\n { path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) },\n ]\n : [{ path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) }]),\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(ctx),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/components/search-dialog.tsx',\n content: generateSearchDialog(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\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(ctx: GenerateContext): string {\n const { config } = ctx;\n const favicon = config.favicon;\n\n const metadataExport = favicon\n ? `import type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n icons: {\n icon: '${favicon}',\n },\n};\n\n`\n : '';\n\n return `import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\n${metadataExport}import '../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\n/**\n * 生成 app/[lang]/layout.tsx — 多语言模式的根布局\n *\n * 与单语言模式的关键区别:\n * 1. 从 params 中获取 lang 参数\n * 2. AppLayout 接收 lang 参数设置 html lang 属性\n * 3. AppProvider 接收 lang 参数用于 i18n UI\n */\nfunction generateRootLayoutI18n(ctx: GenerateContext): string {\n const { config } = ctx;\n const favicon = config.favicon;\n\n const metadataExport = favicon\n ? `import type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n icons: {\n icon: '${favicon}',\n },\n};\n\n`\n : '';\n\n return `${metadataExport}import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../../global.css';\n\nexport default async function RootLayout({\n params,\n children,\n}: {\n params: Promise<{ lang: string }>;\n children: ReactNode;\n}) {\n const { lang } = await params;\n\n return (\n <AppLayout lang={lang}>\n <AppProvider lang={lang}>{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 const isI18n = isI18nEnabled(config);\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 // i18n 模式下需要传入 lang 参数\n const treeLine = hasSidebar\n ? hasIcons\n ? isI18n\n ? 'tree: restructureTree(source.getPageTree(lang), sidebarConfig, iconMap),'\n : 'tree: restructureTree(source.getPageTree(), sidebarConfig, iconMap),'\n : isI18n\n ? 'tree: restructureTree(source.getPageTree(lang), sidebarConfig),'\n : 'tree: restructureTree(source.getPageTree(), sidebarConfig),'\n : isI18n\n ? 'tree: source.getPageTree(lang),'\n : 'tree: source.getPageTree(),';\n\n const restructureTreeImport = hasSidebar\n ? \"\\nimport { restructureTree } from 'openmanual/utils/restructure-tree';\"\n : '';\n\n // i18n 模式下的组件签名和 baseOptions 调用\n if (isI18n) {\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}\n\nexport default async function DocsLayoutWrapper({\n params,\n children,\n}: {\n params: Promise<{ lang: string }>;\n children: ReactNode;\n}) {\n const { lang } = await params;\n\n const docsOptions = {\n ...baseOptions(lang),\n ${treeLine}${githubLine}${linksLine}${footerLine}\n };\n\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\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 (and meta.en.json in i18n mode) for each sidebar\n * group directory so that fumadocs displays the configured group 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 const isI18n = isI18nEnabled(ctx.config);\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\n // 生成默认语言的 meta.json\n const metaPath = join(dirPath, 'meta.json');\n try {\n await access(metaPath);\n } catch {\n await mkdir(dirPath, { recursive: true });\n await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\\n`, 'utf-8');\n }\n\n // i18n 模式下生成 meta.en.json(如果不存在)\n if (isI18n) {\n const metaEnPath = join(dirPath, 'meta.en.json');\n try {\n await access(metaEnPath);\n } catch {\n await mkdir(dirPath, { recursive: true });\n // 初始使用原始 group 名称,用户可后续翻译为英文\n await writeFile(\n metaEnPath,\n `${JSON.stringify({ title: group.group }, null, 2)}\\n`,\n 'utf-8'\n );\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 = '.cache';\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 // 清理残留文件(防止 dev 模式的文件在 build 时残留导致报错)\n await cleanTempDir(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, writeFile } 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 { 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 // i18n 模式下生成根目录 index.html,重定向到默认语言路径\n if (config.i18n?.enabled) {\n const defaultLang = config.i18n.defaultLanguage ?? config.locale ?? 'zh';\n const redirectHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url=/${defaultLang}\" />\n <script>window.location.href='/${defaultLang}';</script>\n</head>\n<body>\n <p>Redirecting to <a href=\"/${defaultLang}\">/${defaultLang}</a>...</p>\n</body>\n</html>`;\n await writeFile(resolve(outputDir, 'index.html'), redirectHtml, 'utf-8');\n }\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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"}
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/i18n-config.ts","../src/core/generator/i18n-ui.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/middleware.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/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 FaviconSchema = 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 I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).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 favicon: FaviconSchema.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 i18n: I18nConfigSchema.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>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\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 isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\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 i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\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 { OpenManualConfig } from '../config/schema.js';\n\n/**\n * 生成 lib/i18n.ts\n *\n * 使用 fumadocs-core 的 defineI18n 定义多语言配置。\n */\nexport function generateI18nConfig(_ctx: { config: OpenManualConfig }): string {\n const i18nCfg = _ctx.config.i18n;\n if (!i18nCfg?.enabled || !i18nCfg.languages || i18nCfg.languages.length < 2) {\n throw new Error('generateI18nConfig called but i18n is not properly configured');\n }\n\n const defaultLang = i18nCfg.defaultLanguage ?? _ctx.config.locale ?? 'zh';\n const languageCodes = i18nCfg.languages.map((l) => `'${l.code}'`).join(', ');\n const parserLine = i18nCfg.parser === 'dir' ? `\\n parser: 'dir',` : '';\n\n return `import { defineI18n } from 'fumadocs-core/i18n';\n\nexport const i18n = defineI18n({\n defaultLanguage: '${defaultLang}',\n languages: [${languageCodes}],${parserLine}\n});\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\n/**\n * 生成 lib/i18n-ui.ts\n *\n * 使用 fumadocs-ui 的 defineI18nUI 定义各语言的 UI 翻译(显示名称等)。\n */\nexport function generateI18nUI(_ctx: { config: OpenManualConfig }): string {\n const i18nCfg = _ctx.config.i18n;\n if (!i18nCfg?.languages || i18nCfg.languages.length === 0) {\n throw new Error('generateI18nUI called but no languages configured');\n }\n\n const langEntries = i18nCfg.languages\n .map(\n (lang) => ` '${lang.code}': {\n displayName: '${lang.name}',\n }`\n )\n .join(',\\n');\n\n return `import { defineI18nUI } from 'fumadocs-ui/i18n';\nimport { i18n } from '@/lib/i18n';\n\nexport const i18nUI = defineI18nUI(i18n, {\n translations: {\n${langEntries}\n },\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 const isI18n = config.i18n?.enabled === true;\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 if (isI18n) {\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(_locale: string): BaseLayoutProps {\n return {\n nav: {\n title: <NavLogo ${logoProps} /> as ReactNode,\n },\n };\n}\n`;\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","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateLibSource(ctx: { config: OpenManualConfig }): string {\n const isI18n = ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\nimport { i18n } from '@/lib/i18n';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n i18n,\n});\n`;\n }\n\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\n/**\n * 生成 middleware.ts(或 proxy.ts)\n *\n * 使用自定义的轻量 i18n 中间件,仅处理:\n * 1. 根路径 / → 重定向到默认语言(如 /zh)\n * 2. 其他所有请求(包括静态资源)→ 直接放行\n *\n * 注意:Next.js 16 已废弃 middleware 推荐使用 proxy,\n * 但 fumadocs-core 尚未提供 createI18nProxy,\n * 因此使用自定义实现避免 createI18nMiddleware 拦截静态资源导致 404。\n */\nexport function generateMiddleware(_ctx: { config: OpenManualConfig }): string {\n const defaultLang = _ctx.config.i18n?.defaultLanguage ?? _ctx.config.locale ?? 'zh';\n\n return `import { NextResponse } from 'next/server';\n\nconst defaultLanguage = '${defaultLang}';\n\nexport default function middleware(request: Request): NextResponse | undefined {\n const { pathname } = new URL(request.url);\n\n // 仅处理根路径重定向,其他请求(含静态资源)放行\n if (pathname === '/') {\n return NextResponse.redirect(new URL('/' + defaultLanguage, request.url));\n }\n\n return undefined;\n}\n\nexport const config = {\n matcher: ['/((?!api|_next/static|_next/image|favicon\\\\.ico).*)'],\n};\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 '@orama/orama': '^3.1.0',\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 const isI18n = _ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return generatePageI18n(_ctx, isStrict, pageActionsEnabled);\n }\n\n return generatePageSingle(_ctx, isStrict, pageActionsEnabled);\n}\n\nfunction generatePageSingle(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean\n): string {\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\nfunction generatePageI18n(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean\n): string {\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[]; lang: string }) => isAllowed(p.slug));\n // Ensure every language has a homepage entry (slug: [])\n const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];\n for (const lang of languages) {\n if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {\n const firstForLang = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (firstForLang) {\n params.unshift({ ...firstForLang, slug: [] });\n }\n }\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n // Ensure every language has a homepage entry (slug: [])\n const languages = [...new Set(params.map((p: { lang: string }) => p.lang))];\n for (const lang of languages) {\n if (!params.some((p: { slug: string[]; lang: string }) => p.slug.length === 0 && p.lang === lang)) {\n const firstForLang = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (firstForLang) {\n params.unshift({ ...firstForLang, slug: [] });\n }\n }\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[]; lang: string }> }) {\n const { slug, lang } = await params;\n const page = source.getPage(slug, lang);\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\n/**\n * 生成 app/provider.tsx(或 app/[lang]/provider.tsx)\n *\n * 重要:直接从 fumadocs-ui 导入组件,而非通过 openmanual/components/provider 中转。\n * 这避免了 pnpm file: 协议下 fumadocs-ui 被安装两次(一次作为 openmanual 的依赖,\n * 一次作为生成应用的依赖)导致的多实例 React Context 问题。\n */\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n const isI18n = ctx.config.i18n?.enabled === true;\n\n if (isI18n) {\n return `'use client';\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport SafeSearchDialog from './components/search-dialog';\nimport { i18nUI } from '@/lib/i18n-ui';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children, lang }: { children: ReactNode; lang: string }) {\n return (\n <RootProvider\n i18n={i18nUI.provider(lang)}\n search={{\n enabled: ${searchEnabled},\n SearchDialog: SafeSearchDialog,\n options: { type: 'static', api: '/api/search' },\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n }\n\n return `'use client';\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport SafeSearchDialog from './components/search-dialog';\nimport type { ReactNode } from 'react';\n\nexport function AppProvider({ children }: { children: ReactNode }) {\n return (\n <RootProvider\n search={{\n enabled: ${searchEnabled},\n SearchDialog: SafeSearchDialog,\n options: { type: 'static', api: '/api/search' },\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n}\n\n/**\n * 生成 app/components/search-dialog.tsx(或 app/[lang]/components/search-dialog.tsx)\n *\n * SafeSearchDialog 组件直接放在生成应用中,确保所有 fumadocs-ui 导入\n * 都来自同一个实例,避免 React Context 跨实例失效的问题。\n *\n * 重要修复:注入自定义 initOrama 解决 Orama 不支持中文等语言的问题。\n *\n * 问题根因:\n * fumadocs-core 的 orama-static.js 在运行时调用 create({ language: 'zh' }),\n * 但 @orama/orama 不支持 'zh' 作为 language 参数(仅支持 31 种语言),\n * 会抛出 LANGUAGE_NOT_SUPPORTED 错误导致搜索功能完全失效。\n *\n * 修复方案:\n * 利用 fumadocs-core 的 StaticOptions.initOrama 扩展点,提供自定义的\n * initOrama 函数。对于 Orama 不支持的语言(如 zh),不传 language 参数,\n * 让 Orama 使用默认的 english 分词器;对于支持的语言正常传入。\n */\nexport function generateSearchDialog(_ctx?: { config: OpenManualConfig }): string {\n return `'use client';\n\nimport { useDocsSearch } from 'fumadocs-core/search/client';\nimport { useOnChange } from 'fumadocs-core/utils/use-on-change';\nimport { create } from '@orama/orama';\nimport {\n SearchDialog,\n SearchDialogClose,\n SearchDialogContent,\n SearchDialogFooter,\n SearchDialogHeader,\n SearchDialogIcon,\n SearchDialogInput,\n SearchDialogList,\n SearchDialogOverlay,\n TagsList,\n TagsListItem,\n} from 'fumadocs-ui/components/dialog/search';\nimport { useI18n } from 'fumadocs-ui/contexts/i18n';\nimport { useMemo, useState } from 'react';\n\n/**\n * Orama 支持的语言名称集合。\n *\n * 不在此列表中的语言(如中文 zh)不能作为 language 参数传入 @orama/orama 的 create(),\n * 否则会抛出 LANGUAGE_NOT_SUPPORTED 错误导致搜索功能完全失效。\n *\n * 来源:@orama/orama 内部的 SUPPORTED_LANGUAGES 列表,\n * 与 fumadocs-core/dist/search/server.js 中的 STEMMERS 保持一致。\n */\nconst SUPPORTED_ORAMA_LANGUAGES = new Set([\n 'arabic', 'armenian', 'bulgarian', 'czech', 'danish', 'dutch',\n 'english', 'finnish', 'french', 'german', 'greek', 'hungarian',\n 'indian', 'indonesian', 'irish', 'italian', 'lithuanian', 'nepali',\n 'norwegian', 'portuguese', 'romanian', 'russian', 'serbian',\n 'slovenian', 'spanish', 'swedish', 'tamil', 'turkish',\n 'ukrainian', 'sanskrit',\n]);\n\n/**\n * 将 locale code(如 'zh', 'en')映射为 Orama 支持的 language 全名。\n *\n * 对于不支持的语言返回 undefined,此时 create() 不传 language 参数,\n * Orama 会默认使用 english 分词器(对中文等语言做基本的空格/标点分词)。\n */\nfunction resolveOramaLanguage(localeCode: string): string | undefined {\n const map: Record<string, string> = {\n ar: 'arabic', am: 'armenian', bg: 'bulgarian', cz: 'czech',\n dk: 'danish', nl: 'dutch', en: 'english', fi: 'finnish',\n fr: 'french', de: 'german', gr: 'greek', hu: 'hungarian',\n in: 'indian', id: 'indonesian', ie: 'irish', it: 'italian',\n lt: 'lithuanian', np: 'nepali', no: 'norwegian', pt: 'portuguese',\n ro: 'romanian', ru: 'russian', rs: 'serbian', sl: 'slovenian',\n es: 'spanish', se: 'swedish', ta: 'tamil', tr: 'turkish',\n uk: 'ukrainian', sk: 'sanskrit',\n };\n const langName = map[localeCode];\n return langName && SUPPORTED_ORAMA_LANGUAGES.has(langName) ? langName : undefined;\n}\n\ninterface SafeSearchDialogProps {\n defaultTag?: string;\n tags?: { value: string; name: string }[];\n api?: string;\n delayMs?: number;\n type?: 'fetch' | 'static';\n allowClear?: boolean;\n links?: [string, string][];\n footer?: React.ReactNode;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport default function SafeSearchDialog({\n defaultTag,\n tags = [],\n api,\n delayMs,\n type = 'fetch',\n allowClear = false,\n links = [],\n footer,\n open = false,\n onOpenChange = (): void => {},\n}: SafeSearchDialogProps) {\n const { locale } = useI18n();\n const [tag, setTag] = useState(defaultTag);\n\n /**\n * 自定义 initOrama:根据 locale 是否受 Orama 支持,决定是否传入 language 参数。\n *\n * 这解决了 Orama 不支持 'zh' 等语言时抛出 LANGUAGE_NOT_SUPPORTED 导致搜索失效的问题。\n * fumadocs-core 的 StaticOptions 类型已官方暴露 initOrama 参数供此用途。\n */\n const safeInitOrama = useMemo(\n () => (localeCode?: string) => {\n const lang = localeCode ? resolveOramaLanguage(localeCode) : undefined;\n return create({\n schema: { _: 'string' },\n ...(lang ? { language: lang } : {}),\n });\n },\n [],\n );\n\n const { search, setSearch, query } = useDocsSearch(\n type === 'fetch'\n ? {\n type: 'fetch',\n ...(api != null && { api }),\n ...(locale != null && { locale }),\n ...(tag != null && { tag }),\n ...(delayMs != null && { delayMs }),\n }\n : {\n type: 'static',\n ...(api != null && { from: api }),\n ...(locale != null && { locale }),\n ...(tag != null && { tag }),\n ...(delayMs != null && { delayMs }),\n initOrama: safeInitOrama,\n }\n );\n\n const defaultItems = useMemo(() => {\n if (links.length === 0) return null;\n return links.map(([name, link]) => ({\n type: 'page' as const,\n id: name,\n content: name,\n url: link,\n }));\n }, [links]);\n\n useOnChange(defaultTag, (v) => {\n setTag(v);\n });\n\n // 核心修复:使用 Array.isArray 守卫,防止非数组值导致 .map() 报错\n const safeItems = Array.isArray(query.data) ? query.data : defaultItems;\n\n return (\n <SearchDialog\n open={open}\n onOpenChange={onOpenChange}\n search={search}\n onSearchChange={setSearch}\n isLoading={query.isLoading}\n >\n <SearchDialogOverlay />\n <SearchDialogContent>\n <SearchDialogHeader>\n <SearchDialogIcon />\n <SearchDialogInput />\n <SearchDialogClose />\n </SearchDialogHeader>\n <SearchDialogList items={safeItems} />\n </SearchDialogContent>\n <SearchDialogFooter>\n {tags.length > 0 && (\n <TagsList {...(tag != null && { tag })} onTagChange={setTag} allowClear={allowClear}>\n {tags.map((tagItem) => (\n <TagsListItem key={tagItem.value} value={tagItem.value}>\n {tagItem.name}\n </TagsListItem>\n ))}\n </TagsList>\n )}\n {footer}\n </SearchDialogFooter>\n </SearchDialog>\n );\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\nimport { isDirParser } from '../config/schema.js';\n\nexport function generateRawContentRoute(ctx: { config: OpenManualConfig }): string {\n const isI18n = ctx.config.i18n?.enabled === true;\n const useDirParser = isDirParser(ctx.config);\n\n if (isI18n && useDirParser) {\n // === Dir parser 模式:文件在 content/{lang}/{slug}.ext ===\n const defaultLang = ctx.config.i18n?.defaultLanguage ?? ctx.config.locale ?? 'zh';\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nconst _defaultLang = '${defaultLang}';\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 // 从查询参数获取语言,回退到默认语言(API 路由不在 [lang] 路径段下)\n const { searchParams } = new URL(request.url);\n const lang = searchParams.get('lang') ?? _defaultLang;\n // dir parser: 文件位于 content/{lang}/{slug}.ext\n for (const ext of ['.mdx', '.md']) {\n try {\n const filePath = join(process.cwd(), 'content', lang, \\`\\${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\n if (isI18n) {\n // === Dot parser 模式:文件在 content/{slug}.{lang}.ext ===\n const defaultLang = ctx.config.i18n?.defaultLanguage ?? ctx.config.locale ?? 'zh';\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nconst _defaultLang = '${defaultLang}';\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 // 从查询参数获取语言,回退到默认语言(API 路由不在 [lang] 路径段下)\n const { searchParams } = new URL(request.url);\n const lang = searchParams.get('lang') ?? _defaultLang;\n // 尝试带语言后缀的文件,再回退到默认语言文件\n const suffix = lang !== _defaultLang ? \\`.\\${lang}\\` : '';\n for (const ext of ['.mdx', '.md']) {\n // 先尝试带后缀\n if (suffix) {\n try {\n const filePath = join(process.cwd(), 'content', \\`\\${slug}\\${suffix}\\${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 /* 回退 */\n }\n }\n // 再尝试不带后缀(默认语言或 fallback)\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\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 type { OpenManualConfig } from '../config/schema.js';\n\n/**\n * Orama/FlexSearch 支持的语言映射(来自 fumadocs-core/dist/search/server.js STEMMERS)\n *\n * key = 语言全名(传给 tokenizer 的 language 值),value = 语言代码(locale code)\n *\n * 不在此列表中的语言(如中文 zh)不能作为 language 参数传入,\n * 否则构建时会抛出 \"Language X is not supported\" 错误。\n * 对于不支持的语言,传入空对象 {} 让 Orama 使用默认分词器。\n */\nconst SUPPORTED_LOCALE_MAP: Record<string, string> = {\n arabic: 'ar',\n armenian: 'am',\n bulgarian: 'bg',\n czech: 'cz',\n danish: 'dk',\n dutch: 'nl',\n english: 'en',\n finnish: 'fi',\n french: 'fr',\n german: 'de',\n greek: 'gr',\n hungarian: 'hu',\n indian: 'in',\n indonesian: 'id',\n irish: 'ie',\n italian: 'it',\n lithuanian: 'lt',\n nepali: 'np',\n norwegian: 'no',\n portuguese: 'pt',\n romanian: 'ro',\n russian: 'ru',\n serbian: 'rs',\n slovenian: 'ru',\n spanish: 'es',\n swedish: 'se',\n tamil: 'ta',\n turkish: 'tr',\n ukrainian: 'uk',\n sanskrit: 'sk',\n};\n\n/**\n * 根据语言代码查找对应的支持的 language 名称\n * 例如:'en' → 'english','zh' → undefined(不支持)\n */\nfunction resolveLanguageName(localeCode: string): string | undefined {\n return Object.keys(SUPPORTED_LOCALE_MAP).find((key) => SUPPORTED_LOCALE_MAP[key] === localeCode);\n}\n\nexport function generateSearchRoute(ctx?: { config: OpenManualConfig }): string {\n const i18nCfg = ctx?.config.i18n;\n const isI18n = i18nCfg?.enabled === true && i18nCfg.languages && i18nCfg.languages.length >= 2;\n\n // i18n 模式下需要显式配置 localeMap:\n // - 支持的语言映射到对应的 language 名称(如 en → 'english')\n // - 不支持的语言(如 zh/中文)传空对象,让 Orama 使用默认分词器\n if (isI18n) {\n const localeMapEntries = (i18nCfg?.languages ?? [])\n .map((l) => {\n const langName = resolveLanguageName(l.code);\n if (langName) {\n return ` ${l.code}: '${langName}'`;\n }\n // 不支持的语言(如中文 zh):传空对象让 Orama 使用默认分词器\n return ` ${l.code}: {}`;\n })\n .join(',\\n');\n\n // 将 localeMap 定义为独立变量(Record<string, unknown>),\n // 再通过 as any 传入 createFromSource 以绕过 fumadocs-core 的严格类型约束。\n // 不能直接在对象字面量中用 (key as any),因为 Turbopack 不支持该语法。\n return `import { source } from '@/lib/source';\nimport { createFromSource } from 'fumadocs-core/search/server';\n\nexport const revalidate = false;\nconst _localeMap: Record<string, unknown> = {\n${localeMapEntries},\n};\nexport const { staticGET: GET } = createFromSource(source, {\n localeMap: _localeMap as any,\n});\n`;\n }\n\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 type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateSourceConfig(_ctx: { config: OpenManualConfig }): string {\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\nimport { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';\n\nexport const docs = defineDocs({\n dir: 'content',\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, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { isDirParser, isI18nEnabled } from '../config/schema.js';\nimport { generateCalloutComponent } from './callout-component.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateI18nConfig } from './i18n-config.js';\nimport { generateI18nUI } from './i18n-ui.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateMiddleware } from './middleware.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, generateSearchDialog } 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 .cache/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 isI18n = isI18nEnabled(ctx.config);\n\n // 基础配置文件(两种模式共用)\n const baseFiles: 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(ctx),\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 ];\n\n let files: Array<{ path: string; content: string }>;\n\n if (isI18n) {\n // === 多语言模式:[lang]/ 动态路由结构 ===\n files = [\n ...baseFiles,\n // i18n 核心文件\n { path: 'lib/i18n.ts', content: generateI18nConfig(ctx) },\n { path: 'lib/i18n-ui.ts', content: generateI18nUI(ctx) },\n // 中间件:重定向 / 到默认语言\n { path: 'middleware.ts', content: generateMiddleware(ctx) },\n // API 路由(放在 app/ 下,middleware 排除 /api/)\n ...(ctx.dev\n ? [\n { path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute(ctx) },\n { path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) },\n ]\n : [{ path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) }]),\n // [lang]/ 路由结构\n {\n path: 'app/[lang]/layout.tsx',\n content: generateRootLayoutI18n(ctx),\n },\n {\n path: 'app/[lang]/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[lang]/components/search-dialog.tsx',\n content: generateSearchDialog(ctx),\n },\n {\n path: 'app/[lang]/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[lang]/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n } else {\n // === 单语言模式(原有结构,不变)===\n files = [\n ...baseFiles,\n // API 路由:raw content 仅 dev 模式;搜索路由两种模式都生成\n ...(ctx.dev\n ? [\n { path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute(ctx) },\n { path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) },\n ]\n : [{ path: 'app/api/search/route.ts', content: generateSearchRoute(ctx) }]),\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(ctx),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/components/search-dialog.tsx',\n content: generateSearchDialog(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\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(ctx: GenerateContext): string {\n const { config } = ctx;\n const favicon = config.favicon;\n\n const metadataExport = favicon\n ? `import type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n icons: {\n icon: '${favicon}',\n },\n};\n\n`\n : '';\n\n return `import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\n${metadataExport}import '../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\n/**\n * 生成 app/[lang]/layout.tsx — 多语言模式的根布局\n *\n * 与单语言模式的关键区别:\n * 1. 从 params 中获取 lang 参数\n * 2. AppLayout 接收 lang 参数设置 html lang 属性\n * 3. AppProvider 接收 lang 参数用于 i18n UI\n */\nfunction generateRootLayoutI18n(ctx: GenerateContext): string {\n const { config } = ctx;\n const favicon = config.favicon;\n\n const metadataExport = favicon\n ? `import type { Metadata } from 'next';\n\nexport const metadata: Metadata = {\n icons: {\n icon: '${favicon}',\n },\n};\n\n`\n : '';\n\n return `${metadataExport}import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../../global.css';\n\nexport default async function RootLayout({\n params,\n children,\n}: {\n params: Promise<{ lang: string }>;\n children: ReactNode;\n}) {\n const { lang } = await params;\n\n return (\n <AppLayout lang={lang}>\n <AppProvider lang={lang}>{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 const isI18n = isI18nEnabled(config);\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 // description:i18n 模式下从当前语言首页 frontmatter 动态获取,单语言模式使用配置值\n const configDesc = config.description ?? '';\n const descLine = configDesc\n ? isI18n\n ? ''\n : `description: '${configDesc.replace(/'/g, \"\\\\'\")}',`\n : '';\n\n // Fumadocs reads title/icon/defaultOpen/pages from meta.json and icon from frontmatter natively.\n // No need for restructureTree() — use getPageTree() directly.\n const treeLine = isI18n ? 'tree: source.getPageTree(lang),' : 'tree: source.getPageTree(),';\n\n // i18n 模式下的组件签名和 baseOptions 调用\n if (isI18n) {\n const configDescSnippet = configDesc\n ? `\\nconst configDescription = '${configDesc.replace(/'/g, \"\\\\'\")}' as const;\\n`\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';${configDescSnippet}\nexport default async function DocsLayoutWrapper({\n params,\n children,\n}: {\n params: Promise<{ lang: string }>;\n children: ReactNode;\n}) {\n const { lang } = await params;${\n configDesc\n ? `\n const indexPage = source.getPage([], lang);\n const siteDescription = indexPage?.data.description ?? configDescription;`\n : ''\n }\n\n const docsOptions = {\n ...baseOptions(lang),\n ${treeLine}${githubLine}${linksLine}${footerLine}${\n configDesc ? '\\n description: siteDescription,' : ''\n }\n };\n\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\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';\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${githubLine}${linksLine}${footerLine}${descLine}\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 complete meta.json for each sidebar group directory.\n * Writes title, icon, defaultOpen, and pages ordering so that Fumadocs\n * can render the sidebar natively without restructureTree().\n *\n * In dir-parser mode, generates meta.json inside each language subdirectory\n * (e.g. content/zh/guide/meta.json). In dot-parser mode, generates at the\n * group directory level with locale-suffixed files for i18n.\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 const isI18n = isI18nEnabled(ctx.config);\n const useDirParser = isDirParser(ctx.config);\n const languages = isI18n ? (ctx.config.i18n?.languages ?? []).map((l) => l.code) : [];\n\n for (const group of sidebar) {\n // Determine if this is a root-level group (all slugs have no \"/\")\n const isRootGroup = group.pages.every((p) => !p.slug.includes('/'));\n\n if (isRootGroup) {\n // Root-level pages: generate meta.json at content root (or per-language root)\n await generateRootMetaJson(ctx, group, contentAbsDir, languages, useDirParser);\n continue;\n }\n\n // Directory-level group: extract directory prefix from slug\n const dirPrefix = group.pages\n .map((p) => p.slug)\n .find((slug) => slug.includes('/'))\n ?.split('/')[0];\n\n if (!dirPrefix) continue;\n\n // Build complete meta object from sidebar group config\n const metaObj: Record<string, unknown> = {\n title: group.group,\n };\n if (group.icon) metaObj.icon = group.icon;\n if (group.collapsed !== undefined) metaObj.defaultOpen = !group.collapsed;\n\n // Extract page filenames (strip directory prefix: \"guide/configuration\" -> \"configuration\")\n const pageFiles = group.pages\n .filter((p) => p.slug.startsWith(`${dirPrefix}/`))\n .map((p) => p.slug.split('/').slice(1).join('/'));\n if (pageFiles.length > 0) metaObj.pages = pageFiles;\n\n if (useDirParser) {\n // Dir parser: write meta.json into each language subdirectory\n for (const lang of languages) {\n const langDir = join(contentAbsDir, lang, dirPrefix);\n await writeMetaIfNotExists(join(langDir, 'meta.json'), metaObj);\n }\n } else {\n // Dot parser: write at group directory level\n const dirPath = join(contentAbsDir, dirPrefix);\n await writeMetaIfNotExists(join(dirPath, 'meta.json'), metaObj);\n\n // i18n dot-parser: generate locale-suffixed meta files\n if (isI18n) {\n for (const lang of languages) {\n if (lang === ctx.config.i18n?.defaultLanguage) continue;\n await writeMetaIfNotExists(join(dirPath, `meta.${lang}.json`), metaObj);\n }\n }\n }\n }\n\n // Inject page-level icon/title into frontmatter for all pages\n await injectPageFrontmatter(ctx);\n}\n\n/**\n * Generate meta.json for root-level page groups (pages without directory prefix).\n * Writes to content/{lang}/meta.json (dir-parser) or content/meta.json (dot-parser).\n */\nasync function generateRootMetaJson(\n _ctx: GenerateContext,\n group: {\n group: string;\n icon?: string | undefined;\n collapsed?: boolean | undefined;\n pages: Array<{ slug: string; title: string; icon?: string | undefined }>;\n },\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<void> {\n const metaObj: Record<string, unknown> = {\n title: group.group,\n };\n if (group.icon) metaObj.icon = group.icon;\n if (group.collapsed !== undefined) metaObj.defaultOpen = !group.collapsed;\n\n // Root-level pages are just filenames (no directory prefix)\n const pageFiles = group.pages.map((p) => p.slug);\n if (pageFiles.length > 0) metaObj.pages = pageFiles;\n\n if (useDirParser) {\n for (const lang of languages) {\n await writeMetaIfNotExists(join(contentAbsDir, lang, 'meta.json'), metaObj);\n }\n } else {\n await writeMetaIfNotExists(join(contentAbsDir, 'meta.json'), metaObj);\n }\n}\n\n/**\n * Write meta.json only if it does not already exist (preserve user edits).\n */\nasync function writeMetaIfNotExists(\n filePath: string,\n data: Record<string, unknown>\n): Promise<void> {\n try {\n await access(filePath);\n // File already exists — skip to preserve user customizations\n } catch {\n await mkdir(join(filePath, '..'), { recursive: true });\n await writeFile(filePath, `${JSON.stringify(data, null, 2)}\\n`, 'utf-8');\n }\n}\n\n/**\n * Inject page-level title and icon from sidebar config into each .mdx file's frontmatter.\n * Uses upsert semantics: only writes fields that are missing from existing frontmatter.\n */\nasync function injectPageFrontmatter(ctx: GenerateContext): Promise<void> {\n const sidebar = ctx.config.sidebar;\n if (!sidebar) return;\n\n const contentAbsDir = join(ctx.projectDir, ctx.contentDir);\n const useDirParser = isDirParser(ctx.config);\n const languages = isI18nEnabled(ctx.config)\n ? (ctx.config.i18n?.languages ?? []).map((l) => l.code)\n : [];\n\n for (const group of sidebar) {\n for (const page of group.pages) {\n const fieldsToInject: Record<string, string> = {};\n if (page.title) fieldsToInject.title = page.title;\n if (page.icon) fieldsToInject.icon = page.icon;\n\n if (Object.keys(fieldsToInject).length === 0) continue;\n\n // Determine target file paths based on parser mode\n const targets = resolveMdxPaths(contentAbsDir, page.slug, useDirParser, languages);\n\n for (const mdxPath of targets) {\n try {\n const content = await readFile(mdxPath, 'utf-8');\n const updated = upsertFrontmatter(content, fieldsToInject);\n if (updated !== content) {\n await writeFile(mdxPath, updated, 'utf-8');\n }\n } catch {\n // File does not exist yet — skip (will be created by generatePage)\n }\n }\n }\n }\n}\n\n/**\n * Resolve .mdx file paths for a given slug across all applicable locales.\n */\nfunction resolveMdxPaths(\n contentAbsDir: string,\n slug: string,\n useDirParser: boolean,\n languages: string[]\n): string[] {\n if (useDirParser) {\n // Dir parser: content/{lang}/{slug}.mdx for each language\n return languages.map((lang) => join(contentAbsDir, lang, `${slug}.mdx`));\n }\n // Dot parser: content/{slug}.mdx\n return [join(contentAbsDir, `${slug}.mdx`)];\n}\n\n/**\n * Upsert fields into MDX frontmatter. Only adds missing fields; never overwrites existing ones.\n *\n * Handles:\n * - No frontmatter → creates one with the injected fields\n * - Existing frontmatter missing some fields → adds only the missing ones\n * - All fields already present → returns original content unchanged\n */\nfunction upsertFrontmatter(content: string, fields: Record<string, string>): string {\n const FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/;\n const match = content.match(FRONTMATTER_REGEX);\n\n if (!match) {\n // No frontmatter — insert at the beginning\n const fieldLines = Object.entries(fields)\n .map(([key, value]) => `${key}: ${value}`)\n .join('\\n');\n return `---\\n${fieldLines}\\n---\\n\\n${content}`;\n }\n\n // Parse existing frontmatter lines into a set of existing keys\n const existingContent = match[1] ?? '';\n const existingLines = existingContent.split('\\n');\n const existingKeys = new Set<string>();\n for (const line of existingLines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#')) {\n const keyMatch = trimmed.match(/^(\\w[\\w-]*)\\s*:/);\n if (keyMatch?.[1]) existingKeys.add(keyMatch[1]);\n }\n }\n\n // Only inject fields that don't already exist\n const newFields = Object.entries(fields).filter(([key]) => !existingKeys.has(key));\n if (newFields.length === 0) return content; // All fields already present\n\n const newFieldLines = newFields.map(([key, value]) => `${key}: ${value}`).join('\\n');\n\n // Insert new fields before the closing ---\n const insertionPoint = (match.index ?? 0) + match[0].length - 3;\n return (\n content.slice(0, insertionPoint) + '\\n' + newFieldLines + '\\n' + content.slice(insertionPoint)\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 = '.cache';\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 // 清理残留文件(防止 dev 模式的文件在 build 时残留导致报错)\n await cleanTempDir(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, writeFile } 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 { 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 // i18n 模式下生成根目录 index.html,重定向到默认语言路径\n if (config.i18n?.enabled) {\n const defaultLang = config.i18n.defaultLanguage ?? config.locale ?? 'zh';\n const redirectHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url=/${defaultLang}\" />\n <script>window.location.href='/${defaultLang}';</script>\n</head>\n<body>\n <p>Redirecting to <a href=\"/${defaultLang}\">/${defaultLang}</a>...</p>\n</body>\n</html>`;\n await writeFile(resolve(outputDir, 'index.html'), redirectHtml, 'utf-8');\n }\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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"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.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 FaviconSchema = 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 I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).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 favicon: FaviconSchema.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 i18n: I18nConfigSchema.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>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\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\nexport function isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\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 i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/config/schema.ts","../src/core/config/loader.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 FaviconSchema = 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 I18nLocaleSchema = z.object({\n code: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const I18nConfigSchema = z.object({\n enabled: z.boolean().optional(),\n defaultLanguage: z.string().optional(),\n languages: z.array(I18nLocaleSchema).optional(),\n parser: z.enum(['dot', 'dir']).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 favicon: FaviconSchema.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 i18n: I18nConfigSchema.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>;\nexport type FaviconConfig = z.infer<typeof FaviconSchema>;\nexport type I18nLocale = z.infer<typeof I18nLocaleSchema>;\nexport type I18nConfig = z.infer<typeof I18nConfigSchema>;\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 isI18nEnabled(config: OpenManualConfig): boolean {\n return config.i18n?.enabled === true && (config.i18n.languages?.length ?? 0) > 1;\n}\n\nexport function isDirParser(config: OpenManualConfig): boolean {\n return config.i18n?.parser === 'dir';\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 i18n: config.i18n\n ? {\n enabled: config.i18n.enabled ?? false,\n defaultLanguage: config.i18n.defaultLanguage ?? config.locale ?? 'zh',\n languages: config.i18n.languages ?? [],\n parser: config.i18n.parser ?? 'dot',\n }\n : undefined,\n };\n}\n"],"mappings":"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"}
@@ -10,7 +10,11 @@ interface SidebarConfigEntry {
10
10
  icon?: string;
11
11
  }[];
12
12
  }
13
+ interface RestructureOptions {
14
+ /** 是否保留树节点原始名称(不覆盖为 sidebar 配置值)。i18n 模式下应设为 true */
15
+ preserveNames?: boolean;
16
+ }
13
17
  declare function slugToUrl(slug: string): string;
14
- declare function restructureTree(tree: PageTree.Root, sidebarConfig: readonly SidebarConfigEntry[], iconMap?: Record<string, React.ReactNode>): PageTree.Root;
18
+ declare function restructureTree(tree: PageTree.Root, sidebarConfig: readonly SidebarConfigEntry[], iconMap?: Record<string, React.ReactNode>, options?: RestructureOptions): PageTree.Root;
15
19
  //#endregion
16
- export { SidebarConfigEntry, restructureTree, slugToUrl };
20
+ export { RestructureOptions, 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, iconMap) {
5
+ function restructureTree(tree, sidebarConfig, iconMap, options) {
6
6
  const consumed = /* @__PURE__ */ new Set();
7
7
  const newChildren = [];
8
8
  const children = tree.children ?? [];
@@ -49,7 +49,7 @@ function restructureTree(tree, sidebarConfig, iconMap) {
49
49
  });
50
50
  newChildren.push({
51
51
  ...originalFolder,
52
- name: group.group,
52
+ ...options?.preserveNames ? {} : { name: group.group },
53
53
  icon: group.icon && iconMap ? iconMap[group.icon] : void 0,
54
54
  defaultOpen: !group.collapsed,
55
55
  children: childrenWithIcons
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmanual",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "author": "shenjingnan <sjn.code@gmail.com>",
5
5
  "description": "AI 友好的开源文档系统框架",
6
6
  "type": "module",