openmanual 0.15.3 → 0.16.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 +93 -16
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -15,6 +15,18 @@ const LogoSchema = z.union([z.string(), z.object({
|
|
|
15
15
|
light: z.string(),
|
|
16
16
|
dark: z.string()
|
|
17
17
|
})]);
|
|
18
|
+
/** Logo 显示位置 */
|
|
19
|
+
const LogoPositionSchema = z.enum(["sidebar", "header"]);
|
|
20
|
+
/**
|
|
21
|
+
* 顶级 Logo 配置(支持字符串简写和对象形式)
|
|
22
|
+
* - 字符串简写: "/logo.svg" → { light, dark: 同值, position: 'sidebar' }
|
|
23
|
+
* - 对象形式: { light, dark, position? }
|
|
24
|
+
*/
|
|
25
|
+
const TopLevelLogoSchema = z.union([z.string(), z.object({
|
|
26
|
+
light: z.string(),
|
|
27
|
+
dark: z.string(),
|
|
28
|
+
position: LogoPositionSchema.optional()
|
|
29
|
+
})]);
|
|
18
30
|
const FaviconSchema = z.string();
|
|
19
31
|
const NavbarSchema = z.object({
|
|
20
32
|
logo: LogoSchema.optional(),
|
|
@@ -96,6 +108,7 @@ const OpenManualConfigSchema = z.object({
|
|
|
96
108
|
locale: z.string().optional(),
|
|
97
109
|
contentPolicy: z.enum(["strict", "all"]).optional(),
|
|
98
110
|
favicon: FaviconSchema.optional(),
|
|
111
|
+
logo: TopLevelLogoSchema.optional(),
|
|
99
112
|
navbar: NavbarSchema.optional(),
|
|
100
113
|
header: TopBarSchema.optional(),
|
|
101
114
|
footer: FooterSchema.optional(),
|
|
@@ -145,6 +158,51 @@ function isSeparateTabMode(config) {
|
|
|
145
158
|
function isHeaderEnabled(config) {
|
|
146
159
|
return config.header !== void 0;
|
|
147
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* 将顶级 logo 配置标准化为 { light, dark } 形式
|
|
163
|
+
*/
|
|
164
|
+
function normalizeTopLevelLogo(logo) {
|
|
165
|
+
if (typeof logo === "string") return {
|
|
166
|
+
light: logo,
|
|
167
|
+
dark: logo,
|
|
168
|
+
position: "sidebar"
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
light: logo.light,
|
|
172
|
+
dark: logo.dark,
|
|
173
|
+
position: logo.position ?? "sidebar"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 解析有效的 Logo 配置(统一优先级链)
|
|
178
|
+
*
|
|
179
|
+
* 优先级:
|
|
180
|
+
* 1. config.logo(新顶级配置)
|
|
181
|
+
* 2. config.navbar.logo(旧 sidebar logo)
|
|
182
|
+
* 3. config.header.logo(旧 header logo)
|
|
183
|
+
* 4. undefined(调用方回退到 config.name)
|
|
184
|
+
*/
|
|
185
|
+
function resolveEffectiveLogo(config) {
|
|
186
|
+
if (config.logo) {
|
|
187
|
+
const { position, ...source } = normalizeTopLevelLogo(config.logo);
|
|
188
|
+
return {
|
|
189
|
+
source,
|
|
190
|
+
position
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (config.navbar?.logo) return {
|
|
194
|
+
source: config.navbar.logo,
|
|
195
|
+
position: "sidebar"
|
|
196
|
+
};
|
|
197
|
+
if (config.header?.logo) return {
|
|
198
|
+
source: config.header.logo,
|
|
199
|
+
position: "header"
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
source: void 0,
|
|
203
|
+
position: "sidebar"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
148
206
|
|
|
149
207
|
//#endregion
|
|
150
208
|
//#region src/core/config/loader.ts
|
|
@@ -183,17 +241,31 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
183
241
|
return mergeDefaults(result.data);
|
|
184
242
|
}
|
|
185
243
|
function mergeDefaults(config) {
|
|
244
|
+
const topLevelLogo = config.logo ? normalizeTopLevelLogo(config.logo) : null;
|
|
245
|
+
const topLevelLogoSource = topLevelLogo ? topLevelLogo.light === topLevelLogo.dark ? topLevelLogo.light : {
|
|
246
|
+
light: topLevelLogo.light,
|
|
247
|
+
dark: topLevelLogo.dark
|
|
248
|
+
} : null;
|
|
186
249
|
return {
|
|
187
250
|
...config,
|
|
188
251
|
contentPolicy: config.contentPolicy ?? "strict",
|
|
189
252
|
contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? "content",
|
|
190
253
|
outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? "dist",
|
|
191
254
|
locale: config.locale ?? DEFAULT_CONFIG.locale ?? "zh",
|
|
255
|
+
logo: topLevelLogo ? typeof config.logo === "string" ? config.logo : {
|
|
256
|
+
light: topLevelLogo.light,
|
|
257
|
+
dark: topLevelLogo.dark,
|
|
258
|
+
position: topLevelLogo.position
|
|
259
|
+
} : void 0,
|
|
192
260
|
navbar: {
|
|
193
261
|
...DEFAULT_CONFIG.navbar,
|
|
194
262
|
...config.navbar,
|
|
195
|
-
logo: config.navbar?.logo ?? config.name
|
|
263
|
+
logo: config.navbar?.logo ?? (topLevelLogo && topLevelLogo.position === "sidebar" ? topLevelLogoSource ?? config.name : config.name)
|
|
196
264
|
},
|
|
265
|
+
header: config.header ? {
|
|
266
|
+
...config.header,
|
|
267
|
+
logo: config.header.logo ?? (topLevelLogo && topLevelLogo.position === "header" ? topLevelLogoSource ?? void 0 : void 0)
|
|
268
|
+
} : void 0,
|
|
197
269
|
footer: {
|
|
198
270
|
...DEFAULT_CONFIG.footer,
|
|
199
271
|
...config.footer,
|
|
@@ -486,11 +558,6 @@ ${config.search?.position === "header" ? `
|
|
|
486
558
|
display: none;
|
|
487
559
|
}
|
|
488
560
|
` : ""}
|
|
489
|
-
|
|
490
|
-
/* 隐藏侧边栏顶部导航区域(logo/标题 + 折叠按钮容器) */
|
|
491
|
-
#nd-sidebar > div:first-child {
|
|
492
|
-
display: none;
|
|
493
|
-
}
|
|
494
561
|
`;
|
|
495
562
|
}
|
|
496
563
|
|
|
@@ -789,7 +856,7 @@ export const APIPage = createAPIPage(openapi, {
|
|
|
789
856
|
//#endregion
|
|
790
857
|
//#region src/core/generator/package-json.ts
|
|
791
858
|
function getOpenManualVersion() {
|
|
792
|
-
return "0.
|
|
859
|
+
return "0.16.0";
|
|
793
860
|
}
|
|
794
861
|
function generatePackageJson(ctx) {
|
|
795
862
|
const openmanualVersion = getOpenManualVersion();
|
|
@@ -1780,10 +1847,13 @@ async function generateAll(ctx) {
|
|
|
1780
1847
|
await mkdir(join(fullPath, ".."), { recursive: true });
|
|
1781
1848
|
await writeFile(fullPath, file.content, "utf-8");
|
|
1782
1849
|
}
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1850
|
+
const rawLogo = ctx.config.logo != null ? typeof ctx.config.logo === "string" ? ctx.config.logo : {
|
|
1851
|
+
light: ctx.config.logo.light,
|
|
1852
|
+
dark: ctx.config.logo.dark
|
|
1853
|
+
} : ctx.config.navbar?.logo;
|
|
1854
|
+
if (rawLogo && typeof rawLogo === "string" && isImagePath(rawLogo)) await ensureLogoFile(ctx, rawLogo, "light");
|
|
1855
|
+
else if (rawLogo && typeof rawLogo === "object") {
|
|
1856
|
+
const { light, dark } = resolveLogoPaths(rawLogo);
|
|
1787
1857
|
if (isImagePath(light)) await ensureLogoFile(ctx, light, "light");
|
|
1788
1858
|
if (isImagePath(dark) && dark !== light) await ensureLogoFile(ctx, dark, "dark");
|
|
1789
1859
|
}
|
|
@@ -1866,6 +1936,11 @@ function generateDocsLayout(ctx) {
|
|
|
1866
1936
|
const isOApi = isOpenApiEnabled(config);
|
|
1867
1937
|
const rootGroups = ctx.rootGroups;
|
|
1868
1938
|
const isHeaderSearch = config.search?.position === "header";
|
|
1939
|
+
const { source: logoSource, position: logoPosition } = resolveEffectiveLogo(config);
|
|
1940
|
+
const hasSidebarLogo = logoSource !== void 0 && logoPosition === "sidebar";
|
|
1941
|
+
const sidebarLogoImport = hasSidebarLogo ? "\nimport { NavLogo } from 'openmanual/components/nav-layout';" : "";
|
|
1942
|
+
const sidebarLogoProps = hasSidebarLogo ? resolveNavLogoProps(logoSource, config.name) : null;
|
|
1943
|
+
const sidebarBannerLine = hasSidebarLogo && sidebarLogoProps && !sidebarLogoProps.includes("type=\"text\"") ? `\n banner: <NavLogo ${sidebarLogoProps} />,` : "";
|
|
1869
1944
|
const linksArray = navLinks.map((l) => ({
|
|
1870
1945
|
text: l.label,
|
|
1871
1946
|
url: l.href,
|
|
@@ -1887,7 +1962,7 @@ function generateDocsLayout(ctx) {
|
|
|
1887
1962
|
if (isI18n) return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
1888
1963
|
import { baseOptions } from '@/lib/layout';
|
|
1889
1964
|
import { source } from '@/lib/source';
|
|
1890
|
-
import type { ReactNode } from 'react';${configDesc ? `\nconst configDescription = '${configDesc.replace(/'/g, "\\'")}' as const;\n` : ""}
|
|
1965
|
+
import type { ReactNode } from 'react';${sidebarLogoImport}${configDesc ? `\nconst configDescription = '${configDesc.replace(/'/g, "\\'")}' as const;\n` : ""}
|
|
1891
1966
|
export default async function DocsLayoutWrapper({
|
|
1892
1967
|
params,
|
|
1893
1968
|
children,
|
|
@@ -1905,7 +1980,8 @@ ${isOApi && separateTab ? ` const _omFirstApi = source.getPages(lang)?.find((p:
|
|
|
1905
1980
|
const docsOptions = {
|
|
1906
1981
|
...baseOptions(lang),
|
|
1907
1982
|
${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${configDesc ? "\n description: siteDescription," : ""}${isHeaderSearch ? "\n searchToggle: { enabled: false }," : ""}
|
|
1908
|
-
sidebar: { collapsible: false
|
|
1983
|
+
sidebar: { collapsible: false,${sidebarBannerLine}
|
|
1984
|
+
},
|
|
1909
1985
|
};
|
|
1910
1986
|
|
|
1911
1987
|
return (
|
|
@@ -1918,14 +1994,15 @@ ${isOApi && separateTab ? ` const _omFirstApi = source.getPages(lang)?.find((p:
|
|
|
1918
1994
|
return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
1919
1995
|
import { baseOptions } from '@/lib/layout';
|
|
1920
1996
|
import { source } from '@/lib/source';
|
|
1921
|
-
import type { ReactNode } from 'react';${isOApi && separateTab ? `
|
|
1997
|
+
import type { ReactNode } from 'react';${sidebarLogoImport}${isOApi && separateTab ? `
|
|
1922
1998
|
const _omFirstApi = source.getPages()?.find((p: any) => p.data?.type === 'openapi');
|
|
1923
1999
|
const _omApiUrl = _omFirstApi?.url ?? '/openapi';
|
|
1924
2000
|
` : ""}
|
|
1925
2001
|
const docsOptions = {
|
|
1926
2002
|
...baseOptions(),
|
|
1927
2003
|
${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${descLine}${isHeaderSearch ? "\n searchToggle: { enabled: false }," : ""}
|
|
1928
|
-
sidebar: { collapsible: false
|
|
2004
|
+
sidebar: { collapsible: false,${sidebarBannerLine}
|
|
2005
|
+
},
|
|
1929
2006
|
};
|
|
1930
2007
|
|
|
1931
2008
|
export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
|
|
@@ -2555,7 +2632,7 @@ const regenerateCommand = new Command("_regenerate").description("内部命令
|
|
|
2555
2632
|
//#endregion
|
|
2556
2633
|
//#region src/cli/bin.ts
|
|
2557
2634
|
function getVersion() {
|
|
2558
|
-
return "0.
|
|
2635
|
+
return "0.16.0";
|
|
2559
2636
|
}
|
|
2560
2637
|
const program = new Command();
|
|
2561
2638
|
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/content/meta-scanner.ts","../src/core/content/scanner.ts","../src/core/content/tree.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/openapi.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/top-bar.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\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\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 /**\n * 搜索入口位置\n * - 'sidebar': 放在侧边栏顶部(Fumadocs 风格)\n * - 'header': 放在 header 中间(Mintlify 风格)\n *\n * 默认值: 'sidebar'\n * 不配置 search 字段则不启用搜索(与 header 的「配置即启用」语义一致)\n */\n position: z.enum(['sidebar', 'header']).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 OpenApiSpecSchema = z.object({\n /** OpenAPI 规范文件路径,相对于项目根目录 */\n path: z.string(),\n /** 分组标签(用于在侧边栏中显示的分组名) */\n group: z.string().optional(),\n});\n\nexport const OpenApiSchema = z.object({\n /**\n * OpenAPI 规范文件路径(单个文件),相对于项目根目录。支持 .json / .yaml / .yml\n * @deprecated 推荐使用 specs 字段\n */\n specPath: z.string().optional(),\n /**\n * OpenAPI 规范文件配置,支持字符串(单文件)或对象数组(多文件)\n * - string: 单个 spec 文件路径\n * - array: 多个 spec 文件配置,每个可指定 path 和 group\n */\n specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),\n /** 侧边栏 Tab 显示名称,默认 \"接口文档\"(仅在 separateTab: true 时生效) */\n label: z.string().optional(),\n /**\n * API 端点在侧边栏中的分组策略\n * - 'tag': 按 OpenAPI 规范中的 tags 分组(默认)\n * - 'route': 按路由路径分组\n * - 'none': 不分组,平铺展示\n */\n groupBy: z.enum(['tag', 'route', 'none']).optional().default('tag'),\n /**\n * 是否将 API 文档作为独立的侧边栏 Tab 展示\n * - true: 保持旧行为,API 文档在独立 Tab 中(向后兼容)\n * - false: 将 API 端点混合到文档导航树中(类似 Mintlify 风格) // cspell:ignore Mintlify\n */\n separateTab: z.boolean().optional().default(false),\n});\n\n/** 顶部横条链接项 */\nexport const TopBarLinkSchema = z\n .object({\n /** 链接显示文本(与 icon 至少填一个) */\n label: z.string().optional(),\n /** lucide-react 图标名称(如 \"Github\", \"Twitter\",与 label 至少填一个) */\n icon: z.string().optional(),\n href: z.string(),\n external: z.boolean().optional().default(true),\n })\n .refine((data) => data.label || data.icon, {\n message: '至少需要提供 label 或 icon 中的一个',\n path: ['label'],\n });\n\n/** 顶部横条配置 */\nexport const TopBarSchema = z.object({\n /** 高度,默认 '64px' */\n height: z.string().optional(),\n /** Logo 配置(独立于 navbar.logo) */\n logo: LogoSchema.optional(),\n /** 右侧导航链接 */\n links: z.array(TopBarLinkSchema).optional(),\n /** 是否显示粘性(sticky),默认 true */\n sticky: z.boolean().optional().default(true),\n /** 背景色(CSS 值) */\n background: z.string().optional(),\n /** 底部边框 */\n bordered: z.boolean().optional().default(true),\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 header: TopBarSchema.optional(),\n footer: FooterSchema.optional(),\n // @deprecated Use meta.json files in content directories instead\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 openapi: OpenApiSchema.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>;\nexport type OpenApiConfig = z.infer<typeof OpenApiSchema>;\nexport type TopBarConfig = z.infer<typeof TopBarSchema>;\nexport type TopBarLink = z.infer<typeof TopBarLinkSchema>;\n\n// @deprecated Use collectSlugsFromMeta from meta-scanner instead\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n console.warn(\n '[openmanual] The \"sidebar\" field in openmanual.json is deprecated. ' +\n 'Please use meta.json files in your content directories instead.'\n );\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\nexport function isOpenApiEnabled(config: OpenManualConfig): boolean {\n if (config.openapi === undefined) return false;\n // 检查 specs(新格式)或 specPath(旧格式,向后兼容)\n const hasSpecs = config.openapi.specs !== undefined;\n const hasSpecPath = typeof config.openapi?.specPath === 'string';\n return hasSpecs || hasSpecPath;\n}\n\n/**\n * 从 OpenAPI 配置中解析出所有 spec 文件路径\n * 兼容 specs(新)和 specPath(旧)两种格式\n */\nexport function resolveOpenApiSpecPaths(config: OpenManualConfig): string[] {\n const openApiCfg = config.openapi;\n if (!openApiCfg) return [];\n\n // 新格式:specs 数组或字符串\n if (openApiCfg.specs !== undefined) {\n if (typeof openApiCfg.specs === 'string') {\n return [openApiCfg.specs];\n }\n return openApiCfg.specs.map((s) => s.path);\n }\n\n // 旧格式:specPath 字符串\n if (typeof openApiCfg.specPath === 'string') {\n return [openApiCfg.specPath];\n }\n\n return [];\n}\n\n/**\n * 判断是否使用独立 Tab 模式(旧行为)\n */\nexport function isSeparateTabMode(config: OpenManualConfig): boolean {\n return config.openapi?.separateTab === true;\n}\n\n/**\n * 判断是否启用了顶部横条(配置了 header 即启用)\n */\nexport function isHeaderEnabled(config: OpenManualConfig): boolean {\n return config.header !== undefined;\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 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 字段则保持 undefined(不启用搜索)\n search: config.search ? { position: config.search.position ?? 'sidebar' } : undefined,\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 openapi: config.openapi\n ? {\n specPath: config.openapi.specPath,\n specs: config.openapi.specs,\n label: config.openapi.label ?? '接口文档',\n groupBy: config.openapi.groupBy ?? 'tag',\n separateTab: config.openapi.separateTab ?? false,\n }\n : undefined,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\n\nexport interface MetaGroupInfo {\n /** Absolute path to the meta.json file */\n filePath: string;\n /** Directory path relative to contentDir (e.g. \"zh/guide\" or \"zh\" for root) */\n dirPath: string;\n /** Computed: whether this is a root-level group (meta.json directly under {lang}/), used for slug generation */\n isRoot: boolean;\n /** From meta.json: whether Fumadocs should treat this as a root folder (Layout Tabs) */\n root?: boolean;\n /** Parsed title from meta.json */\n title: string;\n /** Optional icon from meta.json */\n icon?: string;\n /** Whether the folder is expanded by default (inverted from \"collapsed\") */\n defaultOpen?: boolean;\n /** Ordered list of page filenames within this group */\n pages?: string[];\n}\n\n// Scan all meta.json files in the content directory and return structured info.\n// Supports dir-parser (content/{lang}/**/meta.json) and dot-parser modes.\nexport async function scanMetaFiles(\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<MetaGroupInfo[]> {\n let patterns: string[];\n\n if (useDirParser) {\n // Dir-parser: each language has its own subdirectory with meta.json files\n patterns = languages.map((lang) => `${lang}/**/meta.json`);\n } else {\n // Dot-parser: meta.json at directory level + locale-suffixed variants\n patterns = ['**/meta.json'];\n if (languages.length > 1) {\n for (const lang of languages) {\n patterns.push(`**/meta.${lang}.json`);\n }\n }\n }\n\n const entries = await fg(patterns, {\n cwd: contentAbsDir,\n absolute: true,\n ignore: ['node_modules'],\n });\n\n const groups: MetaGroupInfo[] = [];\n\n for (const filePath of entries) {\n const group = await parseMetaFile(filePath, contentAbsDir, languages, useDirParser);\n if (group) {\n groups.push(group);\n }\n }\n\n return groups;\n}\n\nasync function parseMetaFile(\n filePath: string,\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<MetaGroupInfo | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n const data = JSON.parse(raw) as Record<string, unknown>;\n\n if (typeof data.title !== 'string' || data.title.length === 0) {\n return null; // Skip meta.json without a valid title\n }\n\n const relPath = filePath.replace(contentAbsDir, '').replace(/^\\/+/, '');\n const dirPath = relPath.replace(/\\/?meta(\\.[^/]+)?\\.json$/, '');\n\n // Determine if this is a root-level meta.json\n // In dir-parser mode: content/{lang}/meta.json → isRoot when dirPath equals language code\n // In dot-parser mode: content/meta.json → isRoot when dirPath is empty\n let isRoot = false;\n if (useDirParser) {\n isRoot = languages.includes(dirPath);\n } else {\n // For dot-parser, skip locale-suffixed meta files for non-default languages\n const isLocaleSuffixed = /meta\\.\\w{2}(-\\w{2})?\\.json$/.test(relPath);\n if (isLocaleSuffixed && languages.length > 1) {\n return null; // Skip locale variants - only process base meta.json\n }\n // Root only when meta.json is at the content root (empty dirPath)\n isRoot = dirPath === '';\n }\n\n const group: MetaGroupInfo = {\n filePath,\n dirPath,\n isRoot,\n title: data.title as string,\n };\n\n if (typeof data.icon === 'string') group.icon = data.icon;\n if (typeof data.root === 'boolean') group.root = data.root;\n if (typeof data.defaultOpen === 'boolean') group.defaultOpen = data.defaultOpen;\n if (Array.isArray(data.pages)) {\n group.pages = (data.pages as string[]).filter((p) => typeof p === 'string');\n }\n\n return group;\n } catch {\n return null; // Skip unreadable or invalid JSON files\n }\n}\n\n/**\n * Collect all slugs from meta group infos.\n *\n * For root-level groups, page filenames are used directly as slugs.\n * For directory-level groups, page filenames are prefixed with the directory path.\n */\nexport function collectSlugsFromMeta(groups: MetaGroupInfo[]): Set<string> {\n const slugs = new Set<string>();\n\n for (const group of groups) {\n if (!group.pages) continue;\n\n for (const page of group.pages) {\n if (group.isRoot) {\n // Root-level: filename is the slug (e.g. \"index\", \"quickstart\")\n slugs.add(page);\n } else {\n // Directory-level: prefix with dirPath (e.g. \"guide/configuration\")\n const slug = `${group.dirPath}/${page}`;\n slugs.add(slug);\n }\n }\n }\n\n return slugs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { relative, sep } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\n\nexport interface ContentFile {\n /** Absolute path to the file */\n filePath: string;\n /** Slug derived from file path relative to contentDir */\n slug: string;\n /** File name without extension */\n name: string;\n /** Parsed frontmatter */\n frontmatter: Record<string, unknown>;\n /** Raw content (without frontmatter) */\n content: string;\n /** Directory segments for tree building */\n segments: string[];\n}\n\nexport async function scanContentDir(contentDir: string): Promise<ContentFile[]> {\n const pattern = '**/*.{md,mdx}';\n const entries = await fg(pattern, {\n cwd: contentDir,\n absolute: true,\n ignore: ['node_modules'],\n });\n\n const files: ContentFile[] = [];\n\n for (const filePath of entries) {\n const file = await parseContentFile(filePath, contentDir);\n if (file) {\n files.push(file);\n }\n }\n\n return files.sort((a, b) => a.slug.localeCompare(b.slug));\n}\n\nasync function parseContentFile(filePath: string, contentDir: string): Promise<ContentFile | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n const { data: frontmatter, content } = matter(raw);\n const relPath = relative(contentDir, filePath);\n const segments = relPath.replace(/\\.(md|mdx)$/, '').split(sep);\n const name = segments.at(-1) ?? '';\n const slug = segments.join('/');\n\n return {\n filePath,\n slug,\n name,\n frontmatter: frontmatter as Record<string, unknown>,\n content,\n segments,\n };\n } catch {\n return null;\n }\n}\n\nexport function getContentTree(files: ContentFile[]): ContentDirectory {\n const root: ContentDirectory = { name: '', files: [], children: [] };\n\n for (const file of files) {\n let current = root;\n\n for (const segment of file.segments.slice(0, -1)) {\n let child = current.children.find((c) => c.name === segment);\n if (!child) {\n child = { name: segment, files: [], children: [] };\n current.children.push(child);\n }\n current = child;\n }\n\n current.files.push(file);\n }\n\n return root;\n}\n\nexport interface ContentDirectory {\n name: string;\n files: ContentFile[];\n children: ContentDirectory[];\n}\n","import type { ContentFile } from './scanner.js';\n\n/**\n * Build Fumadocs-compatible page tree from file system.\n * Always auto-generates from file system structure (sidebar config removed).\n */\nexport function buildPageTree(files: ContentFile[]): PageTreeItem[] {\n return buildFromFileSystem(files);\n}\n\ninterface PageTreeItem {\n type: 'page' | 'folder';\n name: string;\n slug?: string;\n icon?: string | undefined;\n children?: PageTreeItem[];\n index?: boolean;\n}\n\nfunction buildFromFileSystem(files: ContentFile[]): PageTreeItem[] {\n const tree = buildDirectoryTree(files);\n return convertDirectoryToTree(tree);\n}\n\ninterface DirNode {\n name: string;\n files: ContentFile[];\n children: Map<string, DirNode>;\n}\n\nfunction buildDirectoryTree(files: ContentFile[]): DirNode {\n const root: DirNode = { name: '', files: [], children: new Map() };\n\n for (const file of files) {\n let current = root;\n\n for (const segment of file.segments.slice(0, -1)) {\n let child = current.children.get(segment);\n if (!child) {\n child = { name: segment, files: [], children: new Map() };\n current.children.set(segment, child);\n }\n current = child;\n }\n\n current.files.push(file);\n }\n\n return root;\n}\n\nfunction convertDirectoryToTree(dir: DirNode): PageTreeItem[] {\n const items: PageTreeItem[] = [];\n\n for (const file of dir.files) {\n const title = (file.frontmatter.title as string) ?? formatTitle(file.name);\n items.push({\n type: 'page',\n name: title,\n slug: file.slug,\n });\n }\n\n for (const [, child] of dir.children) {\n const folderItem: PageTreeItem = {\n type: 'folder',\n name: formatTitle(child.name),\n children: convertDirectoryToTree(child),\n };\n\n const indexFile = child.files.find((f) => f.name === 'index' || f.slug.endsWith('/index'));\n if (indexFile) {\n folderItem.slug = indexFile.slug.replace(/\\/index$/, '') || 'index';\n folderItem.index = true;\n }\n\n items.push(folderItem);\n }\n\n return items;\n}\n\n/** Format a directory name into a display title (e.g. \"my-page\" → \"My Page\") */\nexport function formatTitle(name: string): string {\n return name.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Generate Fumadocs source.config.ts content string\n * that defines docs from a content directory.\n */\nexport function generateSourceConfigContent(contentDir: string): string {\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\n\nexport const docs = defineDocs({\n dir: '${contentDir}',\n});\n\nexport default defineConfig();`;\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';\nimport { isOpenApiEnabled } 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 const isOApi = isOpenApiEnabled(config);\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 --color-fd-inputborder: hsla(30, 20%, 50%, 40%); /* 输入框边框色(hover 用)*/\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 const openapiCssImport = isOApi ? \"\\n@import 'fumadocs-openapi/css/preset.css';\" : '';\n\n return `@import 'tailwindcss';\n@source './node_modules/openmanual/dist/components/**/*.js';\n@import 'fumadocs-ui/css/neutral.css';\n@import 'fumadocs-ui/css/preset.css';${openapiCssImport}\n\n@layer base {\n body {\n @apply flex flex-col min-h-screen;\n }\n}\n\n/* 注册自定义颜色到 Tailwind v4 @theme,确保所有变体(hover/dark 等)可正确生成 */\n@theme {\n --color-fd-inputborder: hsla(30, 12%, 80%, 50%);\n}\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 --color-fd-muted-foreground: hsl(30, 10%, 55%); /* 次要文字色 */\n --color-fd-inputborder: hsla(30, 12%, 80%, 50%); /* 输入框边框色(hover 用)*/\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 config.search?.position === 'header'\n ? `\n\n/* header 搜索模式:隐藏侧边栏折叠面板中的搜索图标 */\n[data-sidebar-panel] [data-search] {\n display: none;\n}\n`\n : ''\n}\n\n/* 隐藏侧边栏顶部导航区域(logo/标题 + 折叠按钮容器) */\n#nd-sidebar > div:first-child {\n display: 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\n/**\n * 将 LogoConfig 解析为 NavLogo 组件的 props 字符串\n *\n * 消除 top-bar.ts 和 layout.ts 中重复的三分支判断。\n */\nexport function resolveNavLogoProps(logo: LogoConfig | string, alt: string): string {\n if (typeof logo === 'string' && isImagePath(logo)) {\n return `type=\"image\" src=\"${logo}\" alt=\"${alt}\"`;\n }\n if (typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (light === dark) {\n return `type=\"image\" src=\"${light}\" alt=\"${alt}\"`;\n }\n return `type=\"image\" srcLight=\"${light}\" srcDark=\"${dark}\" alt=\"${alt}\"`;\n }\n return `type=\"text\" text=\"${logo}\"`;\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const isI18n = config.i18n?.enabled === true;\n\n if (isI18n) {\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(_locale: string): BaseLayoutProps {\n return {\n nav: {\n enabled: false,\n },\n };\n}\n`;\n }\n\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n enabled: false,\n },\n };\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\nimport { isI18nEnabled, isOpenApiEnabled, isSeparateTabMode } from '../config/schema.js';\n\nexport function generateLibSource(ctx: { config: OpenManualConfig }): string {\n const isI18n = isI18nEnabled(ctx.config);\n const isOApi = isOpenApiEnabled(ctx.config);\n\n // === OpenAPI 启用时:使用 multiple() 合并 docs + openapi 源 ===\n if (isOApi) {\n const separateTab = isSeparateTabMode(ctx.config);\n const groupBy = ctx.config.openapi?.groupBy ?? 'tag';\n\n if (isI18n) {\n // i18n 模式:根据 separateTab 决定 baseDir\n // - separateTab: 使用 ${lang}/openapi(旧行为,独立 Tab)\n // - !separateTab: 使用 ${lang}/api(混合到文档树中)\n const baseDirStr = separateTab ? 'openapi' : 'api';\n\n return `import { docs } from '@/.source/server';\nimport { loader, multiple } from 'fumadocs-core/source';\nimport { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';\nimport { openapi } from '@/lib/openapi';\nimport { i18n } from '@/lib/i18n';\n\nconst _omOpenApiFiles = [];\nfor (const lang of i18n.languages) {\n const result = await openapiSource(openapi, {\n baseDir: \\`\\${lang}/${baseDirStr}\\`,\n${!separateTab ? ` meta: true,\\n groupBy: '${groupBy}',` : ''}\n });\n _omOpenApiFiles.push(...result.files);\n}\n\nexport const source = loader(\n multiple({\n docs: docs.toFumadocsSource(),\n openapi: { files: _omOpenApiFiles },\n }),\n {\n baseUrl: '/',\n i18n,\n plugins: [openapiPlugin()],\n },\n);\n`;\n }\n\n // 单语言模式\n const baseDir = separateTab ? 'openapi' : 'api';\n\n return `import { docs } from '@/.source/server';\nimport { loader, multiple } from 'fumadocs-core/source';\nimport { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';\nimport { openapi } from '@/lib/openapi';\n\nexport const source = loader(\n multiple({\n docs: docs.toFumadocsSource(),\n openapi: await openapiSource(openapi, {\n baseDir: '${baseDir}',\n${!separateTab ? ` meta: true,\\n groupBy: '${groupBy}',` : ''}\n }),\n }),\n {\n baseUrl: '/',\n plugins: [openapiPlugin()],\n },\n);\n`;\n }\n\n // === 无 OpenAPI:保持原有逻辑不变(向后兼容)===\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';\nimport { isOpenApiEnabled } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig; dev?: boolean }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n const isOApi = isOpenApiEnabled(config);\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 // serverExternalPackages:mermaid 始终需要;openapi 启用时额外加入 shiki\n const externalsArray = isOApi ? \"['mermaid', 'shiki']\" : \"['mermaid']\";\n const turbopackBlock = '';\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: ${externalsArray},\n images: {\n unoptimized: true,\n },${rewritesBlock}${turbopackBlock}\n};\n\nexport default withMDX(config);\n`;\n}\n","import { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { isOpenApiEnabled, resolveOpenApiSpecPaths } from '../config/schema.js';\n\n/**\n * 生成 lib/openapi.ts — OpenAPI 实例定义\n *\n * 使用绝对路径解析 spec 文件,避免 SSG 构建时相对路径失效的问题。\n * 支持多文件输入(specs 数组)和单文件(specPath 向后兼容)。\n */\nexport function generateOpenApiLib(ctx: {\n config: OpenManualConfig;\n projectDir: string;\n}): string | null {\n if (!isOpenApiEnabled(ctx.config)) return null;\n\n // 从配置中解析所有 spec 路径(兼容新旧格式)\n const specPaths = resolveOpenApiSpecPaths(ctx.config);\n if (specPaths.length === 0) return null;\n\n // 使用绝对路径确保在 .cache/app/ 下构建时能正确找到 spec 文件\n const absolutePaths = specPaths.map((p) => join(ctx.projectDir, p));\n const inputArray = absolutePaths.map((p) => `'${p}'`).join(', ');\n\n return `import { createOpenAPI } from 'fumadocs-openapi/server';\n\nexport const openapi = createOpenAPI({\n input: [${inputArray}],\n});\n`;\n}\n\n/**\n * 生成 components/api-page.client.tsx — APIPage 客户端配置\n *\n * createAPIPage 的 client 参数是 APIPageClientOptions(配置对象),\n * 不是 React 组件。使用 defineClientConfig() 创建默认导出。\n */\nexport function generateApiClientComponent(): string {\n return `'use client';\n\nimport { defineClientConfig } from 'fumadocs-openapi/ui/client';\n\nexport default defineClientConfig();\n`;\n}\n\n/**\n * 生成 components/api-page.tsx — APIPage 服务端组件包装器\n */\nexport function generateApiPageComponent(): string {\n return `import { openapi } from '@/lib/openapi';\nimport { createAPIPage } from 'fumadocs-openapi/ui';\nimport client from './api-page.client';\n\nexport const APIPage = createAPIPage(openapi, {\n client,\n});\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';\nimport { isOpenApiEnabled } 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 ...(isOpenApiEnabled(ctx.config)\n ? {\n 'fumadocs-openapi': '^10.7.1',\n shiki: '^3.0.0',\n }\n : {}),\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 type { OpenManualConfig } from '../config/schema.js';\nimport { isI18nEnabled, isOpenApiEnabled } from '../config/schema.js';\n\nexport function generatePage(_ctx: { config: OpenManualConfig; allSlugs?: Set<string> }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;\n const isI18n = isI18nEnabled(_ctx.config);\n const isOApi = isOpenApiEnabled(_ctx.config);\n const allSlugs = _ctx.allSlugs ?? new Set<string>();\n\n if (isI18n) {\n return generatePageI18n(_ctx, isStrict, pageActionsEnabled, allSlugs, isOApi);\n }\n\n return generatePageSingle(_ctx, isStrict, pageActionsEnabled, allSlugs, isOApi);\n}\n\nfunction generatePageSingle(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean,\n allSlugs: Set<string>,\n isOApi: boolean\n): string {\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set<string>(${JSON.stringify([...allSlugs])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug && slug.length > 0 ? slug.join('/') : 'index';\n return allowedSlugs.has(key) || (slug?.[0] === 'openapi') || (slug?.[0] === 'api');\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 const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');\n params.unshift({ ...(homepage ?? 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 const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');\n params.unshift({ ...(homepage ?? params[0]), slug: [] });\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const apiPageImport = isOApi ? \"\\nimport { APIPage } from '@/components/api-page';\" : '';\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 // OpenAPI 页面渲染分支\n const openapiBranch = isOApi\n ? `\n if (page.data.type === 'openapi') {\n return (\n <DocsPage full>\n <h1 className=\"text-[1.75em] font-semibold\">{page.data.title}</h1>\n <DocsBody>\n <APIPage {...(page.data as any).getAPIPageProps()} />\n </DocsBody>\n </DocsPage>\n );\n }\n`\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}${apiPageImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n let page = source.getPage(slug);\n // Fallback: when slug is empty (root path /) and getPage returns undefined,\n // try ['index'] — fumadocs-core's slugsPlugin may assign [\"index\"] to index.mdx\n // due to slug de-duplication conflict, causing getPage([]) to miss it.\n if (!page && (!slug || slug.length === 0)) {\n page = source.getPage(['index']);\n }\n${filterInPage}\n if (!page) {\n notFound();\n }\n${openapiBranch}\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, 'Tabs.Tab': 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 allSlugs: Set<string>,\n isOApi: boolean\n): string {\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set<string>(${JSON.stringify([...allSlugs])});\n\nfunction isAllowed(slug: string[] | undefined, lang?: string): boolean {\n if (allowedSlugs.size === 0) return true;\n const rawKey = slug && slug.length > 0 ? slug.join('/') : 'index';\n const key = lang ? \\`\\${lang}/\\${rawKey}\\` : rawKey;\n return allowedSlugs.has(key) || (slug?.[0] === 'openapi') || (slug?.[0] === 'api');\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug, lang)) {\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, p.lang));\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 homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);\n const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (homepage || fallback) {\n params.unshift({ ...(homepage ?? fallback!), slug: [], lang });\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 homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);\n const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (homepage || fallback) {\n params.unshift({ ...(homepage ?? fallback!), slug: [], lang });\n }\n }\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const apiPageImport = isOApi ? \"\\nimport { APIPage } from '@/components/api-page';\" : '';\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 // OpenAPI 页面渲染分支(i18n 模式)\n const openapiBranch = isOApi\n ? `\n if (page.data.type === 'openapi') {\n return (\n <DocsPage full>\n <h1 className=\"text-[1.75em] font-semibold\">{page.data.title}</h1>\n <DocsBody>\n <APIPage {...(page.data as any).getAPIPageProps()} />\n </DocsBody>\n </DocsPage>\n );\n }\n`\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}${apiPageImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[]; lang: string }> }) {\n const { slug, lang } = await params;\n let page = source.getPage(slug, lang);\n // Fallback: when slug is empty (root path /) and getPage returns undefined,\n // try ['index'] — fumadocs-core's slugsPlugin may assign [\"index\"] to index.mdx\n // due to slug de-duplication conflict, causing getPage([], lang) to miss it.\n if (!page && (!slug || slug.length === 0)) {\n page = source.getPage(['index'], lang);\n }\n${filterInPage}\n if (!page) {\n notFound();\n }\n${openapiBranch}\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, 'Tabs.Tab': 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 searchConfig = ctx.config.search;\n // 「配置即启用」:存在 search 字段即启用搜索(与 header 语义一致)\n const searchEnabled = searchConfig !== undefined;\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","import type { GenerateContext } from './index.js';\nimport { resolveNavLogoProps } from './layout.js';\n\nexport function generateTopBarComponent(ctx: GenerateContext): string {\n const { config } = ctx;\n const header = config.header!;\n const height = header.height ?? '64px';\n const sticky = header.sticky ?? true;\n const bordered = header.bordered ?? true;\n const background = header.background ?? '';\n\n // 处理 Logo — 仅使用 header.logo 或回退到项目名称(不回退到 navbar.logo 避免双 logo)\n const logoSource = header.logo ?? config.name;\n const logoProps = resolveNavLogoProps(logoSource, config.name);\n\n // 处理链接 — 序列化为 JSON 传给 NavLinks 组件\n const linksJson = JSON.stringify(header.links ?? []);\n\n const backgroundProp = background ? `\\n background='${background}',` : '';\n\n // 搜索位置:header 模式下在 center 插槽渲染搜索触发器\n const searchPosition = config.search?.position;\n const isTopBarSearch = searchPosition === 'header';\n\n const searchImport = isTopBarSearch\n ? \"\\nimport { TopBarSearchTrigger } from 'openmanual/components/top-bar-search-trigger';\"\n : '';\n\n // 将所有复杂 props 提取为变量,避免 Turbopack 解析行内大 JSON/JSX 时报错\n const centerProp = isTopBarSearch ? '\\n center={searchCenter}' : '';\n\n return `'use client';\n\nimport { TopBar } from 'openmanual/components/top-bar';\nimport { NavLogo } from 'openmanual/components/nav-layout';\nimport { NavLinks } from 'openmanual/components/nav-links';${searchImport}\n\nconst navLinks = ${linksJson};\n${isTopBarSearch ? 'const searchCenter = <TopBarSearchTrigger />;' : ''}\n\nexport function OmTopBar() {\n return (\n <TopBar\n height='${height}'\n sticky={${sticky}}${backgroundProp}\n bordered={${bordered}}\n left={<NavLogo ${logoProps} />}${centerProp}\n right={<NavLinks links={navLinks} />}\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 'collections/*': ['./.source/*'],\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 {\n isDirParser,\n isHeaderEnabled,\n isI18nEnabled,\n isOpenApiEnabled,\n isSeparateTabMode,\n resolveOpenApiSpecPaths,\n} from '../config/schema.js';\nimport {\n collectSlugsFromMeta,\n type MetaGroupInfo,\n scanMetaFiles,\n} from '../content/meta-scanner.js';\nimport { type ContentFile, scanContentDir } from '../content/scanner.js';\nimport { formatTitle } from '../content/tree.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 {\n generateApiClientComponent,\n generateApiPageComponent,\n generateOpenApiLib,\n} from './openapi.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 { generateTopBarComponent } from './top-bar.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 /** Pre-computed slugs from meta.json or file system (replaces collectConfiguredSlugs) */\n allSlugs?: Set<string>;\n /** Meta groups with root: true — used to generate explicit Layout Tabs in DocsLayout */\n rootGroups?: Array<{ title: string; dirPath: string; url: string; urls: string[] }>;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const isI18n = isI18nEnabled(ctx.config);\n\n // === OpenAPI 规范文件校验 ===\n if (isOpenApiEnabled(ctx.config)) {\n const { access } = await import('node:fs/promises');\n const { extname, join } = await import('node:path');\n const supportedExts = ['.json', '.yaml', '.yml'];\n const specPaths = resolveOpenApiSpecPaths(ctx.config);\n\n for (const specPath of specPaths) {\n const absolutePath = join(ctx.projectDir, specPath);\n const ext = extname(absolutePath).toLowerCase();\n\n if (!supportedExts.includes(ext)) {\n throw new Error(\n `[openapi] 不支持的 OpenAPI 规范文件格式: \"${ext}\"(文件: ${specPath})。支持的格式: ${supportedExts.join(', ')}`\n );\n }\n\n try {\n await access(absolutePath);\n } catch {\n throw new Error(\n `[openapi] OpenAPI 规范文件不存在: \"${specPath}\"。` +\n `请确认 \"openapi.specs\" 或 \"openapi.specPath\" 在 openmanual.json 中配置的路径正确。`\n );\n }\n }\n }\n\n // Pre-compute slugs for page generation (replaces collectConfiguredSlugs from sidebar)\n await computeAllSlugs(ctx);\n\n // 基础配置文件(两种模式共用)\n const isOApi = isOpenApiEnabled(ctx.config);\n\n // === OpenAPI 文件(条件性生成)===\n const openapiFiles: Array<{ path: string; content: string }> = [];\n if (isOApi) {\n const openapiLib = generateOpenApiLib(ctx);\n if (openapiLib) {\n openapiFiles.push({ path: 'lib/openapi.ts', content: openapiLib });\n }\n openapiFiles.push({\n path: 'components/api-page.client.tsx',\n content: generateApiClientComponent(),\n });\n openapiFiles.push({\n path: 'components/api-page.tsx',\n content: generateApiPageComponent(),\n });\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 // 顶部横条组件(条件性生成,在 i18n/单语言分支中分别注册正确路径)\n const headerEnabled = isHeaderEnabled(ctx.config);\n\n let files: Array<{ path: string; content: string }>;\n\n if (isI18n) {\n // === 多语言模式:[lang]/ 动态路由结构 ===\n files = [\n ...baseFiles,\n ...openapiFiles,\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 ...(headerEnabled\n ? [{ path: 'app/[lang]/components/top-bar.tsx', content: generateTopBarComponent(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 ...openapiFiles,\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 ...(headerEnabled\n ? [{ path: 'app/components/top-bar.tsx', content: generateTopBarComponent(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 const headerEnabled = isHeaderEnabled(config);\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 const topBarImport = headerEnabled ? \"import { OmTopBar } from './components/top-bar';\\n\" : '';\n\n const topBarJsx = headerEnabled ? '<OmTopBar />\\n ' : '';\n\n return `${metadataExport}${topBarImport}import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <AppLayout>\n <AppProvider>${topBarJsx}{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 const headerEnabled = isHeaderEnabled(config);\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 const topBarImport = headerEnabled ? \"import { OmTopBar } from './components/top-bar';\\n\" : '';\n\n const topBarJsx = headerEnabled ? '<OmTopBar />\\n ' : '';\n\n return `${metadataExport}${topBarImport}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}>${topBarJsx}{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 const isOApi = isOpenApiEnabled(config);\n const rootGroups = ctx.rootGroups;\n const isHeaderSearch = config.search?.position === 'header';\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 // Generate explicit sidebar.tabs from root groups so Layout Tabs are visible on all pages (including homepage).\n // Tab URLs use directory paths so isActive() prefix matching covers all pages in the group.\n // OpenAPI Tab 注入(仅在 separateTab 模式下注入独立 Tab;否则 API 页面混合到文档树中)\n const separateTab = isOpenApiEnabled(config) && isSeparateTabMode(config);\n const openapiTab = separateTab\n ? {\n title: config.openapi?.label ?? '接口文档',\n url: isI18n ? '/${lang}/openapi' : '/openapi',\n urls: new Set<string>(),\n }\n : null;\n\n const sidebarTabsLine =\n (rootGroups && rootGroups.length > 0) || openapiTab\n ? isI18n\n ? generateI18nSidebarTabs(config, rootGroups, openapiTab)\n : generateSingleSidebarTabs(config, rootGroups, openapiTab)\n : '';\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${\n isOApi && separateTab\n ? ` const _omFirstApi = source.getPages(lang)?.find((p: any) => p.data?.type === 'openapi');\n const _omApiUrl = _omFirstApi?.url ?? \\`/\\${lang}/openapi\\`;\n`\n : ''\n}${\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}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${\n configDesc ? '\\n description: siteDescription,' : ''\n }${isHeaderSearch ? '\\n searchToggle: { enabled: false },' : ''}\n sidebar: { collapsible: false },\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';${\n isOApi && separateTab\n ? `\nconst _omFirstApi = source.getPages()?.find((p: any) => p.data?.type === 'openapi');\nconst _omApiUrl = _omFirstApi?.url ?? '/openapi';\n`\n : ''\n }\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${descLine}${\n isHeaderSearch ? '\\n searchToggle: { enabled: false },' : ''\n }\n sidebar: { collapsible: false },\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\n/**\n * Generate i18n sidebar.tabs string.\n * Extracted from generateDocsLayout to avoid deeply nested template literals\n * that confuse the oxc parser when escaped backticks are involved.\n */\nfunction generateI18nSidebarTabs(\n config: OpenManualConfig,\n rootGroups: GenerateContext['rootGroups'],\n openapiTab: { title: string; url: string; urls: Set<string> } | null\n): string {\n const entries = (rootGroups ?? []).map((g) => ({\n title: g.title,\n dirPath: g.dirPath,\n url: g.url,\n urls: g.urls,\n }));\n const entriesJson = JSON.stringify(entries);\n const nameEscaped = config.name.replace(/'/g, \"\\\\'\");\n\n const openapiTabLine = openapiTab\n ? `,\\n { title: '${(openapiTab.title as string).replace(/'/g, \"\\\\'\")}', url: _omApiUrl, urls: new Set<string>() }`\n : '';\n\n // Build the generated code string piece by piece to keep each template literal shallow\n const homeTab = `{ title: '${nameEscaped}', url: \\`/\\${lang}\\` }`;\n const mapExpr = `(${entriesJson} as Array<{title:string;dirPath:string;url:string;urls:string[]}>).filter(g => g.dirPath.startsWith(\\`\\${lang}/\\`)).map(g => ({ title: g.title, url: g.url, urls: new Set<string>(g.urls) }))`;\n\n return `\\n sidebar: {\\n tabs: [\\n ${homeTab},\\n ...${mapExpr}${openapiTabLine}\\n ],\\n },`;\n}\n\n/**\n * Generate single-language (non-i18n) sidebar.tabs string.\n * Uses template literal to preserve Set<string> for urls property.\n */\nfunction generateSingleSidebarTabs(\n config: OpenManualConfig,\n rootGroups: GenerateContext['rootGroups'],\n openapiTab: { title: string; url: string; urls: Set<string> } | null\n): string {\n const nameEscaped = config.name.replace(/'/g, \"\\\\'\");\n const groupEntries = (rootGroups ?? [])\n .map((g) => {\n const title = g.title.replace(/'/g, \"\\\\'\");\n const urlsArr = JSON.stringify(g.urls);\n return `{ title: '${title}', url: '${g.url}', urls: new Set(${urlsArr}) }`;\n })\n .join(',\\n ');\n\n const openapiTabLine = openapiTab\n ? `,\\n { title: '${(openapiTab.title as string).replace(/'/g, \"\\\\'\")}', url: _omApiUrl, urls: new Set<string>() }`\n : '';\n\n return `\\n sidebar: {\\n tabs: [\\n { title: '${nameEscaped}', url: '/' },${groupEntries ? '\\n ' + groupEntries : ''}${openapiTabLine}\\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 or enrich meta.json files for each content directory.\n *\n * Strategy:\n * 1. If meta.json files exist → enrich missing fields (icon/defaultOpen/pages)\n * 2. If no meta.json → auto-generate from file system structure\n */\n\n/**\n * Compute all slugs from meta.json files, falling back to file system scan.\n * Stores result in ctx.allSlugs for use by generatePage().\n * Also extracts root groups (meta.json with root: true) into ctx.rootGroups.\n */\nasync function computeAllSlugs(ctx: GenerateContext): Promise<void> {\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 // Priority 1: Collect from meta.json files\n const metaGroups = await scanMetaFiles(contentAbsDir, languages, useDirParser);\n if (metaGroups.length > 0) {\n ctx.allSlugs = collectSlugsFromMeta(metaGroups);\n // Also collect root-level file slugs (e.g. index.mdx, quickstart.mdx under {lang}/)\n // which are not covered by any meta.json pages field\n const allFiles = await scanContentDir(contentAbsDir);\n for (const file of allFiles) {\n // Root-level files: directly under {lang}/ (dir-parser: segments length == 2)\n // or directly under content/ (dot-parser: segments length == 1)\n const isRootLevel = useDirParser\n ? file.segments.length === 2 && languages.includes(file.segments[0]!)\n : file.segments.length === 1;\n if (isRootLevel) {\n ctx.allSlugs.add(file.slug);\n }\n }\n // Scan all directories to collect actual file slugs and enable urls Set generation.\n const scannedDirCache = new Map<string, ContentFile[]>();\n for (const group of metaGroups) {\n const dirAbsPath = join(contentAbsDir, group.dirPath);\n try {\n const dirFiles = await scanContentDir(dirAbsPath);\n scannedDirCache.set(group.dirPath, dirFiles);\n // Only add to allSlugs if group has no explicit pages (avoid duplicates)\n if (!group.pages || group.pages.length === 0) {\n for (const df of dirFiles) {\n ctx.allSlugs.add(`${group.dirPath}/${df.slug}`);\n }\n }\n } catch {\n // Directory may not exist or empty — skip\n }\n }\n // Extract groups with root: true for explicit Layout Tabs generation.\n // - url: first actual page (for navigation — avoids 404 on directory paths)\n // - urls: Set of all pages in the group (for isLayoutTabActive exact matching)\n ctx.rootGroups = metaGroups\n .filter((g) => g.root === true)\n .map((g) => {\n const cached = scannedDirCache.get(g.dirPath);\n const firstPage = g.pages?.[0] ?? cached?.[0]?.name ?? 'index';\n const allUrls = (cached ?? []).map((f) => `/${g.dirPath}/${f.name}` as string);\n return {\n title: g.title,\n dirPath: g.dirPath,\n url: `/${g.dirPath}/${firstPage}`,\n urls: allUrls,\n };\n });\n return;\n }\n\n // Priority 2: Collect from file system (all .mdx/.md slugs)\n const files = await scanContentDir(contentAbsDir);\n ctx.allSlugs = new Set(files.map((f) => f.slug));\n}\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\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 // Scan existing meta.json files\n const metaGroups = await scanMetaFiles(contentAbsDir, languages, useDirParser);\n\n // Enrich existing meta.json files\n if (metaGroups.length > 0) {\n for (const group of metaGroups) {\n await enrichMetaFile(group);\n }\n return;\n }\n\n // Auto-generate from file system structure\n await autoGenerateMetaFromFS(ctx, contentAbsDir, languages, useDirParser);\n}\n\n/**\n * Validate an existing meta.json file is readable.\n * Does NOT modify the file — all user-set fields (including \"root\") are preserved as-is.\n * Fumadocs reads meta.json directly via its own content source pipeline.\n */\nasync function enrichMetaFile(_group: MetaGroupInfo): Promise<void> {\n try {\n await readFile(_group.filePath, 'utf-8');\n } catch {\n // File unreadable - skip\n }\n}\n\n/**\n * Auto-generate meta.json files from the file system structure.\n * Used when no meta.json and no sidebar config exist.\n */\nasync function autoGenerateMetaFromFS(\n _ctx: GenerateContext,\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<void> {\n const files = await scanContentDir(contentAbsDir);\n\n // Group files by their directory structure\n const rootFiles: typeof files = [];\n const dirGroups = new Map<string, typeof files>();\n\n for (const file of files) {\n if (file.segments.length <= 1) {\n rootFiles.push(file);\n } else {\n const dirName = file.segments[0];\n if (dirName === undefined) continue;\n if (!dirGroups.has(dirName)) {\n dirGroups.set(dirName, []);\n }\n dirGroups.get(dirName)?.push(file);\n }\n }\n\n // Generate root-level meta.json if there are root files\n if (rootFiles.length > 0) {\n const rootMeta = {\n title: 'Getting Started',\n pages: rootFiles.map((f) => f.name),\n };\n\n if (useDirParser) {\n for (const lang of languages) {\n await writeMetaIfNotExists(join(contentAbsDir, lang, 'meta.json'), rootMeta);\n }\n } else {\n await writeMetaIfNotExists(join(contentAbsDir, 'meta.json'), rootMeta);\n }\n }\n\n // Generate meta.json for each directory group\n for (const [dirName, dirFiles] of dirGroups) {\n const dirMeta: Record<string, unknown> = {\n title: formatTitle(dirName),\n pages: dirFiles.map((f) => f.segments.slice(1).join('/')),\n };\n\n if (useDirParser) {\n for (const lang of languages) {\n await writeMetaIfNotExists(join(contentAbsDir, lang, dirName, 'meta.json'), dirMeta);\n }\n } else {\n await writeMetaIfNotExists(join(contentAbsDir, dirName, 'meta.json'), dirMeta);\n }\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","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/content/meta-scanner.ts","../src/core/content/scanner.ts","../src/core/content/tree.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/openapi.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/top-bar.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\n/** Logo 显示位置 */\nexport const LogoPositionSchema = z.enum(['sidebar', 'header']);\n\n/**\n * 顶级 Logo 配置(支持字符串简写和对象形式)\n * - 字符串简写: \"/logo.svg\" → { light, dark: 同值, position: 'sidebar' }\n * - 对象形式: { light, dark, position? }\n */\nexport const TopLevelLogoSchema = z.union([\n z.string(),\n z.object({\n light: z.string(),\n dark: z.string(),\n position: LogoPositionSchema.optional(),\n }),\n]);\n\nexport const FaviconSchema = z.string();\n\nexport const NavbarSchema = z.object({\n /** @deprecated 使用顶级 `logo` 字段代替 */\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\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\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 /**\n * 搜索入口位置\n * - 'sidebar': 放在侧边栏顶部(Fumadocs 风格)\n * - 'header': 放在 header 中间(Mintlify 风格)\n *\n * 默认值: 'sidebar'\n * 不配置 search 字段则不启用搜索(与 header 的「配置即启用」语义一致)\n */\n position: z.enum(['sidebar', 'header']).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 OpenApiSpecSchema = z.object({\n /** OpenAPI 规范文件路径,相对于项目根目录 */\n path: z.string(),\n /** 分组标签(用于在侧边栏中显示的分组名) */\n group: z.string().optional(),\n});\n\nexport const OpenApiSchema = z.object({\n /**\n * OpenAPI 规范文件路径(单个文件),相对于项目根目录。支持 .json / .yaml / .yml\n * @deprecated 推荐使用 specs 字段\n */\n specPath: z.string().optional(),\n /**\n * OpenAPI 规范文件配置,支持字符串(单文件)或对象数组(多文件)\n * - string: 单个 spec 文件路径\n * - array: 多个 spec 文件配置,每个可指定 path 和 group\n */\n specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),\n /** 侧边栏 Tab 显示名称,默认 \"接口文档\"(仅在 separateTab: true 时生效) */\n label: z.string().optional(),\n /**\n * API 端点在侧边栏中的分组策略\n * - 'tag': 按 OpenAPI 规范中的 tags 分组(默认)\n * - 'route': 按路由路径分组\n * - 'none': 不分组,平铺展示\n */\n groupBy: z.enum(['tag', 'route', 'none']).optional().default('tag'),\n /**\n * 是否将 API 文档作为独立的侧边栏 Tab 展示\n * - true: 保持旧行为,API 文档在独立 Tab 中(向后兼容)\n * - false: 将 API 端点混合到文档导航树中(类似 Mintlify 风格) // cspell:ignore Mintlify\n */\n separateTab: z.boolean().optional().default(false),\n});\n\n/** 顶部横条链接项 */\nexport const TopBarLinkSchema = z\n .object({\n /** 链接显示文本(与 icon 至少填一个) */\n label: z.string().optional(),\n /** lucide-react 图标名称(如 \"Github\", \"Twitter\",与 label 至少填一个) */\n icon: z.string().optional(),\n href: z.string(),\n external: z.boolean().optional().default(true),\n })\n .refine((data) => data.label || data.icon, {\n message: '至少需要提供 label 或 icon 中的一个',\n path: ['label'],\n });\n\n/** 顶部横条配置 */\nexport const TopBarSchema = z.object({\n /** 高度,默认 '64px' */\n height: z.string().optional(),\n /** @deprecated 使用顶级 `logo` 字段并设置 `position: \"header\"` 代替 */\n logo: LogoSchema.optional(),\n /** 右侧导航链接 */\n links: z.array(TopBarLinkSchema).optional(),\n /** 是否显示粘性(sticky),默认 true */\n sticky: z.boolean().optional().default(true),\n /** 背景色(CSS 值) */\n background: z.string().optional(),\n /** 底部边框 */\n bordered: z.boolean().optional().default(true),\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 /** 顶级 Logo 配置(替代 navbar.logo / header.logo) */\n logo: TopLevelLogoSchema.optional(),\n navbar: NavbarSchema.optional(),\n header: TopBarSchema.optional(),\n footer: FooterSchema.optional(),\n // @deprecated Use meta.json files in content directories instead\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 openapi: OpenApiSchema.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>;\nexport type OpenApiConfig = z.infer<typeof OpenApiSchema>;\nexport type TopBarConfig = z.infer<typeof TopBarSchema>;\nexport type TopBarLink = z.infer<typeof TopBarLinkSchema>;\nexport type TopLevelLogoConfig = z.infer<typeof TopLevelLogoSchema>;\nexport type LogoPosition = z.infer<typeof LogoPositionSchema>;\n\n// @deprecated Use collectSlugsFromMeta from meta-scanner instead\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n console.warn(\n '[openmanual] The \"sidebar\" field in openmanual.json is deprecated. ' +\n 'Please use meta.json files in your content directories instead.'\n );\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\nexport function isOpenApiEnabled(config: OpenManualConfig): boolean {\n if (config.openapi === undefined) return false;\n // 检查 specs(新格式)或 specPath(旧格式,向后兼容)\n const hasSpecs = config.openapi.specs !== undefined;\n const hasSpecPath = typeof config.openapi?.specPath === 'string';\n return hasSpecs || hasSpecPath;\n}\n\n/**\n * 从 OpenAPI 配置中解析出所有 spec 文件路径\n * 兼容 specs(新)和 specPath(旧)两种格式\n */\nexport function resolveOpenApiSpecPaths(config: OpenManualConfig): string[] {\n const openApiCfg = config.openapi;\n if (!openApiCfg) return [];\n\n // 新格式:specs 数组或字符串\n if (openApiCfg.specs !== undefined) {\n if (typeof openApiCfg.specs === 'string') {\n return [openApiCfg.specs];\n }\n return openApiCfg.specs.map((s) => s.path);\n }\n\n // 旧格式:specPath 字符串\n if (typeof openApiCfg.specPath === 'string') {\n return [openApiCfg.specPath];\n }\n\n return [];\n}\n\n/**\n * 判断是否使用独立 Tab 模式(旧行为)\n */\nexport function isSeparateTabMode(config: OpenManualConfig): boolean {\n return config.openapi?.separateTab === true;\n}\n\n/**\n * 判断是否启用了顶部横条(配置了 header 即启用)\n */\nexport function isHeaderEnabled(config: OpenManualConfig): boolean {\n return config.header !== undefined;\n}\n\n/**\n * 将顶级 logo 配置标准化为 { light, dark } 形式\n */\nexport function normalizeTopLevelLogo(logo: TopLevelLogoConfig): {\n light: string;\n dark: string;\n position: 'sidebar' | 'header';\n} {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo, position: 'sidebar' };\n }\n return {\n light: logo.light,\n dark: logo.dark,\n position: logo.position ?? 'sidebar',\n };\n}\n\n/**\n * 解析有效的 Logo 配置(统一优先级链)\n *\n * 优先级:\n * 1. config.logo(新顶级配置)\n * 2. config.navbar.logo(旧 sidebar logo)\n * 3. config.header.logo(旧 header logo)\n * 4. undefined(调用方回退到 config.name)\n */\nexport function resolveEffectiveLogo(config: OpenManualConfig): {\n source: LogoConfig | undefined;\n position: 'sidebar' | 'header';\n} {\n // 新顶级 logo 最高优先级\n if (config.logo) {\n const normalized = normalizeTopLevelLogo(config.logo);\n const { position, ...source } = normalized;\n return { source: source as LogoConfig, position };\n }\n\n // 旧 navbar.logo → sidebar\n if (config.navbar?.logo) {\n return { source: config.navbar.logo, position: 'sidebar' };\n }\n\n // 旧 header.logo → header\n if (config.header?.logo) {\n return { source: config.header.logo, position: 'header' };\n }\n\n return { source: undefined, position: 'sidebar' };\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { normalizeTopLevelLogo, 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 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 // 解析顶级 logo(如有),用于后续传播\n const topLevelLogo = config.logo ? normalizeTopLevelLogo(config.logo) : null;\n\n // 将顶级 logo 的 light/dark 部分提取为 LogoConfig 格式(去掉 position)\n const topLevelLogoSource = topLevelLogo\n ? topLevelLogo.light === topLevelLogo.dark\n ? topLevelLogo.light\n : { light: topLevelLogo.light, dark: topLevelLogo.dark }\n : null;\n\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 // 标准化顶级 logo 的 position 默认值\n logo: topLevelLogo\n ? typeof config.logo === 'string'\n ? config.logo\n : { light: topLevelLogo.light, dark: topLevelLogo.dark, position: topLevelLogo.position }\n : undefined,\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n // 传播逻辑:有顶级 logo 且 position=sidebar → 传播到 navbar.logo\n // 否则保持原有 navbar.logo ?? config.name 逻辑\n logo:\n config.navbar?.logo ??\n (topLevelLogo && topLevelLogo.position === 'sidebar'\n ? (topLevelLogoSource ?? config.name)\n : config.name),\n },\n header: config.header\n ? {\n ...config.header,\n // 传播逻辑:有顶级 logo 且 position=header → 传播到 header.logo\n // 否则保持原有 header.logo 不变\n logo:\n config.header.logo ??\n (topLevelLogo && topLevelLogo.position === 'header'\n ? (topLevelLogoSource ?? undefined)\n : undefined),\n }\n : undefined,\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 字段则保持 undefined(不启用搜索)\n search: config.search ? { position: config.search.position ?? 'sidebar' } : undefined,\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 openapi: config.openapi\n ? {\n specPath: config.openapi.specPath,\n specs: config.openapi.specs,\n label: config.openapi.label ?? '接口文档',\n groupBy: config.openapi.groupBy ?? 'tag',\n separateTab: config.openapi.separateTab ?? false,\n }\n : undefined,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\n\nexport interface MetaGroupInfo {\n /** Absolute path to the meta.json file */\n filePath: string;\n /** Directory path relative to contentDir (e.g. \"zh/guide\" or \"zh\" for root) */\n dirPath: string;\n /** Computed: whether this is a root-level group (meta.json directly under {lang}/), used for slug generation */\n isRoot: boolean;\n /** From meta.json: whether Fumadocs should treat this as a root folder (Layout Tabs) */\n root?: boolean;\n /** Parsed title from meta.json */\n title: string;\n /** Optional icon from meta.json */\n icon?: string;\n /** Whether the folder is expanded by default (inverted from \"collapsed\") */\n defaultOpen?: boolean;\n /** Ordered list of page filenames within this group */\n pages?: string[];\n}\n\n// Scan all meta.json files in the content directory and return structured info.\n// Supports dir-parser (content/{lang}/**/meta.json) and dot-parser modes.\nexport async function scanMetaFiles(\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<MetaGroupInfo[]> {\n let patterns: string[];\n\n if (useDirParser) {\n // Dir-parser: each language has its own subdirectory with meta.json files\n patterns = languages.map((lang) => `${lang}/**/meta.json`);\n } else {\n // Dot-parser: meta.json at directory level + locale-suffixed variants\n patterns = ['**/meta.json'];\n if (languages.length > 1) {\n for (const lang of languages) {\n patterns.push(`**/meta.${lang}.json`);\n }\n }\n }\n\n const entries = await fg(patterns, {\n cwd: contentAbsDir,\n absolute: true,\n ignore: ['node_modules'],\n });\n\n const groups: MetaGroupInfo[] = [];\n\n for (const filePath of entries) {\n const group = await parseMetaFile(filePath, contentAbsDir, languages, useDirParser);\n if (group) {\n groups.push(group);\n }\n }\n\n return groups;\n}\n\nasync function parseMetaFile(\n filePath: string,\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<MetaGroupInfo | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n const data = JSON.parse(raw) as Record<string, unknown>;\n\n if (typeof data.title !== 'string' || data.title.length === 0) {\n return null; // Skip meta.json without a valid title\n }\n\n const relPath = filePath.replace(contentAbsDir, '').replace(/^\\/+/, '');\n const dirPath = relPath.replace(/\\/?meta(\\.[^/]+)?\\.json$/, '');\n\n // Determine if this is a root-level meta.json\n // In dir-parser mode: content/{lang}/meta.json → isRoot when dirPath equals language code\n // In dot-parser mode: content/meta.json → isRoot when dirPath is empty\n let isRoot = false;\n if (useDirParser) {\n isRoot = languages.includes(dirPath);\n } else {\n // For dot-parser, skip locale-suffixed meta files for non-default languages\n const isLocaleSuffixed = /meta\\.\\w{2}(-\\w{2})?\\.json$/.test(relPath);\n if (isLocaleSuffixed && languages.length > 1) {\n return null; // Skip locale variants - only process base meta.json\n }\n // Root only when meta.json is at the content root (empty dirPath)\n isRoot = dirPath === '';\n }\n\n const group: MetaGroupInfo = {\n filePath,\n dirPath,\n isRoot,\n title: data.title as string,\n };\n\n if (typeof data.icon === 'string') group.icon = data.icon;\n if (typeof data.root === 'boolean') group.root = data.root;\n if (typeof data.defaultOpen === 'boolean') group.defaultOpen = data.defaultOpen;\n if (Array.isArray(data.pages)) {\n group.pages = (data.pages as string[]).filter((p) => typeof p === 'string');\n }\n\n return group;\n } catch {\n return null; // Skip unreadable or invalid JSON files\n }\n}\n\n/**\n * Collect all slugs from meta group infos.\n *\n * For root-level groups, page filenames are used directly as slugs.\n * For directory-level groups, page filenames are prefixed with the directory path.\n */\nexport function collectSlugsFromMeta(groups: MetaGroupInfo[]): Set<string> {\n const slugs = new Set<string>();\n\n for (const group of groups) {\n if (!group.pages) continue;\n\n for (const page of group.pages) {\n if (group.isRoot) {\n // Root-level: filename is the slug (e.g. \"index\", \"quickstart\")\n slugs.add(page);\n } else {\n // Directory-level: prefix with dirPath (e.g. \"guide/configuration\")\n const slug = `${group.dirPath}/${page}`;\n slugs.add(slug);\n }\n }\n }\n\n return slugs;\n}\n","import { readFile } from 'node:fs/promises';\nimport { relative, sep } from 'node:path';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\n\nexport interface ContentFile {\n /** Absolute path to the file */\n filePath: string;\n /** Slug derived from file path relative to contentDir */\n slug: string;\n /** File name without extension */\n name: string;\n /** Parsed frontmatter */\n frontmatter: Record<string, unknown>;\n /** Raw content (without frontmatter) */\n content: string;\n /** Directory segments for tree building */\n segments: string[];\n}\n\nexport async function scanContentDir(contentDir: string): Promise<ContentFile[]> {\n const pattern = '**/*.{md,mdx}';\n const entries = await fg(pattern, {\n cwd: contentDir,\n absolute: true,\n ignore: ['node_modules'],\n });\n\n const files: ContentFile[] = [];\n\n for (const filePath of entries) {\n const file = await parseContentFile(filePath, contentDir);\n if (file) {\n files.push(file);\n }\n }\n\n return files.sort((a, b) => a.slug.localeCompare(b.slug));\n}\n\nasync function parseContentFile(filePath: string, contentDir: string): Promise<ContentFile | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n const { data: frontmatter, content } = matter(raw);\n const relPath = relative(contentDir, filePath);\n const segments = relPath.replace(/\\.(md|mdx)$/, '').split(sep);\n const name = segments.at(-1) ?? '';\n const slug = segments.join('/');\n\n return {\n filePath,\n slug,\n name,\n frontmatter: frontmatter as Record<string, unknown>,\n content,\n segments,\n };\n } catch {\n return null;\n }\n}\n\nexport function getContentTree(files: ContentFile[]): ContentDirectory {\n const root: ContentDirectory = { name: '', files: [], children: [] };\n\n for (const file of files) {\n let current = root;\n\n for (const segment of file.segments.slice(0, -1)) {\n let child = current.children.find((c) => c.name === segment);\n if (!child) {\n child = { name: segment, files: [], children: [] };\n current.children.push(child);\n }\n current = child;\n }\n\n current.files.push(file);\n }\n\n return root;\n}\n\nexport interface ContentDirectory {\n name: string;\n files: ContentFile[];\n children: ContentDirectory[];\n}\n","import type { ContentFile } from './scanner.js';\n\n/**\n * Build Fumadocs-compatible page tree from file system.\n * Always auto-generates from file system structure (sidebar config removed).\n */\nexport function buildPageTree(files: ContentFile[]): PageTreeItem[] {\n return buildFromFileSystem(files);\n}\n\ninterface PageTreeItem {\n type: 'page' | 'folder';\n name: string;\n slug?: string;\n icon?: string | undefined;\n children?: PageTreeItem[];\n index?: boolean;\n}\n\nfunction buildFromFileSystem(files: ContentFile[]): PageTreeItem[] {\n const tree = buildDirectoryTree(files);\n return convertDirectoryToTree(tree);\n}\n\ninterface DirNode {\n name: string;\n files: ContentFile[];\n children: Map<string, DirNode>;\n}\n\nfunction buildDirectoryTree(files: ContentFile[]): DirNode {\n const root: DirNode = { name: '', files: [], children: new Map() };\n\n for (const file of files) {\n let current = root;\n\n for (const segment of file.segments.slice(0, -1)) {\n let child = current.children.get(segment);\n if (!child) {\n child = { name: segment, files: [], children: new Map() };\n current.children.set(segment, child);\n }\n current = child;\n }\n\n current.files.push(file);\n }\n\n return root;\n}\n\nfunction convertDirectoryToTree(dir: DirNode): PageTreeItem[] {\n const items: PageTreeItem[] = [];\n\n for (const file of dir.files) {\n const title = (file.frontmatter.title as string) ?? formatTitle(file.name);\n items.push({\n type: 'page',\n name: title,\n slug: file.slug,\n });\n }\n\n for (const [, child] of dir.children) {\n const folderItem: PageTreeItem = {\n type: 'folder',\n name: formatTitle(child.name),\n children: convertDirectoryToTree(child),\n };\n\n const indexFile = child.files.find((f) => f.name === 'index' || f.slug.endsWith('/index'));\n if (indexFile) {\n folderItem.slug = indexFile.slug.replace(/\\/index$/, '') || 'index';\n folderItem.index = true;\n }\n\n items.push(folderItem);\n }\n\n return items;\n}\n\n/** Format a directory name into a display title (e.g. \"my-page\" → \"My Page\") */\nexport function formatTitle(name: string): string {\n return name.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Generate Fumadocs source.config.ts content string\n * that defines docs from a content directory.\n */\nexport function generateSourceConfigContent(contentDir: string): string {\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\n\nexport const docs = defineDocs({\n dir: '${contentDir}',\n});\n\nexport default defineConfig();`;\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';\nimport { isOpenApiEnabled } 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 const isOApi = isOpenApiEnabled(config);\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 --color-fd-inputborder: hsla(30, 20%, 50%, 40%); /* 输入框边框色(hover 用)*/\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 const openapiCssImport = isOApi ? \"\\n@import 'fumadocs-openapi/css/preset.css';\" : '';\n\n return `@import 'tailwindcss';\n@source './node_modules/openmanual/dist/components/**/*.js';\n@import 'fumadocs-ui/css/neutral.css';\n@import 'fumadocs-ui/css/preset.css';${openapiCssImport}\n\n@layer base {\n body {\n @apply flex flex-col min-h-screen;\n }\n}\n\n/* 注册自定义颜色到 Tailwind v4 @theme,确保所有变体(hover/dark 等)可正确生成 */\n@theme {\n --color-fd-inputborder: hsla(30, 12%, 80%, 50%);\n}\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 --color-fd-muted-foreground: hsl(30, 10%, 55%); /* 次要文字色 */\n --color-fd-inputborder: hsla(30, 12%, 80%, 50%); /* 输入框边框色(hover 用)*/\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 config.search?.position === 'header'\n ? `\n\n/* header 搜索模式:隐藏侧边栏折叠面板中的搜索图标 */\n[data-sidebar-panel] [data-search] {\n display: none;\n}\n`\n : ''\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\n/**\n * 将 LogoConfig 解析为 NavLogo 组件的 props 字符串\n *\n * 消除 top-bar.ts 和 layout.ts 中重复的三分支判断。\n */\nexport function resolveNavLogoProps(logo: LogoConfig | string, alt: string): string {\n if (typeof logo === 'string' && isImagePath(logo)) {\n return `type=\"image\" src=\"${logo}\" alt=\"${alt}\"`;\n }\n if (typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (light === dark) {\n return `type=\"image\" src=\"${light}\" alt=\"${alt}\"`;\n }\n return `type=\"image\" srcLight=\"${light}\" srcDark=\"${dark}\" alt=\"${alt}\"`;\n }\n return `type=\"text\" text=\"${logo}\"`;\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const isI18n = config.i18n?.enabled === true;\n\n if (isI18n) {\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(_locale: string): BaseLayoutProps {\n return {\n nav: {\n enabled: false,\n },\n };\n}\n`;\n }\n\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n enabled: false,\n },\n };\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\nimport { isI18nEnabled, isOpenApiEnabled, isSeparateTabMode } from '../config/schema.js';\n\nexport function generateLibSource(ctx: { config: OpenManualConfig }): string {\n const isI18n = isI18nEnabled(ctx.config);\n const isOApi = isOpenApiEnabled(ctx.config);\n\n // === OpenAPI 启用时:使用 multiple() 合并 docs + openapi 源 ===\n if (isOApi) {\n const separateTab = isSeparateTabMode(ctx.config);\n const groupBy = ctx.config.openapi?.groupBy ?? 'tag';\n\n if (isI18n) {\n // i18n 模式:根据 separateTab 决定 baseDir\n // - separateTab: 使用 ${lang}/openapi(旧行为,独立 Tab)\n // - !separateTab: 使用 ${lang}/api(混合到文档树中)\n const baseDirStr = separateTab ? 'openapi' : 'api';\n\n return `import { docs } from '@/.source/server';\nimport { loader, multiple } from 'fumadocs-core/source';\nimport { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';\nimport { openapi } from '@/lib/openapi';\nimport { i18n } from '@/lib/i18n';\n\nconst _omOpenApiFiles = [];\nfor (const lang of i18n.languages) {\n const result = await openapiSource(openapi, {\n baseDir: \\`\\${lang}/${baseDirStr}\\`,\n${!separateTab ? ` meta: true,\\n groupBy: '${groupBy}',` : ''}\n });\n _omOpenApiFiles.push(...result.files);\n}\n\nexport const source = loader(\n multiple({\n docs: docs.toFumadocsSource(),\n openapi: { files: _omOpenApiFiles },\n }),\n {\n baseUrl: '/',\n i18n,\n plugins: [openapiPlugin()],\n },\n);\n`;\n }\n\n // 单语言模式\n const baseDir = separateTab ? 'openapi' : 'api';\n\n return `import { docs } from '@/.source/server';\nimport { loader, multiple } from 'fumadocs-core/source';\nimport { openapiPlugin, openapiSource } from 'fumadocs-openapi/server';\nimport { openapi } from '@/lib/openapi';\n\nexport const source = loader(\n multiple({\n docs: docs.toFumadocsSource(),\n openapi: await openapiSource(openapi, {\n baseDir: '${baseDir}',\n${!separateTab ? ` meta: true,\\n groupBy: '${groupBy}',` : ''}\n }),\n }),\n {\n baseUrl: '/',\n plugins: [openapiPlugin()],\n },\n);\n`;\n }\n\n // === 无 OpenAPI:保持原有逻辑不变(向后兼容)===\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';\nimport { isOpenApiEnabled } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig; dev?: boolean }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n const isOApi = isOpenApiEnabled(config);\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 // serverExternalPackages:mermaid 始终需要;openapi 启用时额外加入 shiki\n const externalsArray = isOApi ? \"['mermaid', 'shiki']\" : \"['mermaid']\";\n const turbopackBlock = '';\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: ${externalsArray},\n images: {\n unoptimized: true,\n },${rewritesBlock}${turbopackBlock}\n};\n\nexport default withMDX(config);\n`;\n}\n","import { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { isOpenApiEnabled, resolveOpenApiSpecPaths } from '../config/schema.js';\n\n/**\n * 生成 lib/openapi.ts — OpenAPI 实例定义\n *\n * 使用绝对路径解析 spec 文件,避免 SSG 构建时相对路径失效的问题。\n * 支持多文件输入(specs 数组)和单文件(specPath 向后兼容)。\n */\nexport function generateOpenApiLib(ctx: {\n config: OpenManualConfig;\n projectDir: string;\n}): string | null {\n if (!isOpenApiEnabled(ctx.config)) return null;\n\n // 从配置中解析所有 spec 路径(兼容新旧格式)\n const specPaths = resolveOpenApiSpecPaths(ctx.config);\n if (specPaths.length === 0) return null;\n\n // 使用绝对路径确保在 .cache/app/ 下构建时能正确找到 spec 文件\n const absolutePaths = specPaths.map((p) => join(ctx.projectDir, p));\n const inputArray = absolutePaths.map((p) => `'${p}'`).join(', ');\n\n return `import { createOpenAPI } from 'fumadocs-openapi/server';\n\nexport const openapi = createOpenAPI({\n input: [${inputArray}],\n});\n`;\n}\n\n/**\n * 生成 components/api-page.client.tsx — APIPage 客户端配置\n *\n * createAPIPage 的 client 参数是 APIPageClientOptions(配置对象),\n * 不是 React 组件。使用 defineClientConfig() 创建默认导出。\n */\nexport function generateApiClientComponent(): string {\n return `'use client';\n\nimport { defineClientConfig } from 'fumadocs-openapi/ui/client';\n\nexport default defineClientConfig();\n`;\n}\n\n/**\n * 生成 components/api-page.tsx — APIPage 服务端组件包装器\n */\nexport function generateApiPageComponent(): string {\n return `import { openapi } from '@/lib/openapi';\nimport { createAPIPage } from 'fumadocs-openapi/ui';\nimport client from './api-page.client';\n\nexport const APIPage = createAPIPage(openapi, {\n client,\n});\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';\nimport { isOpenApiEnabled } 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 ...(isOpenApiEnabled(ctx.config)\n ? {\n 'fumadocs-openapi': '^10.7.1',\n shiki: '^3.0.0',\n }\n : {}),\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 type { OpenManualConfig } from '../config/schema.js';\nimport { isI18nEnabled, isOpenApiEnabled } from '../config/schema.js';\n\nexport function generatePage(_ctx: { config: OpenManualConfig; allSlugs?: Set<string> }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;\n const isI18n = isI18nEnabled(_ctx.config);\n const isOApi = isOpenApiEnabled(_ctx.config);\n const allSlugs = _ctx.allSlugs ?? new Set<string>();\n\n if (isI18n) {\n return generatePageI18n(_ctx, isStrict, pageActionsEnabled, allSlugs, isOApi);\n }\n\n return generatePageSingle(_ctx, isStrict, pageActionsEnabled, allSlugs, isOApi);\n}\n\nfunction generatePageSingle(\n _ctx: { config: OpenManualConfig },\n isStrict: boolean,\n pageActionsEnabled: boolean,\n allSlugs: Set<string>,\n isOApi: boolean\n): string {\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set<string>(${JSON.stringify([...allSlugs])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug && slug.length > 0 ? slug.join('/') : 'index';\n return allowedSlugs.has(key) || (slug?.[0] === 'openapi') || (slug?.[0] === 'api');\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 const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');\n params.unshift({ ...(homepage ?? 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 const homepage = params.find((p: { slug: string[] }) => p.slug.length === 1 && p.slug[0] === 'index');\n params.unshift({ ...(homepage ?? params[0]), slug: [] });\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const apiPageImport = isOApi ? \"\\nimport { APIPage } from '@/components/api-page';\" : '';\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 // OpenAPI 页面渲染分支\n const openapiBranch = isOApi\n ? `\n if (page.data.type === 'openapi') {\n return (\n <DocsPage full>\n <h1 className=\"text-[1.75em] font-semibold\">{page.data.title}</h1>\n <DocsBody>\n <APIPage {...(page.data as any).getAPIPageProps()} />\n </DocsBody>\n </DocsPage>\n );\n }\n`\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}${apiPageImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n let page = source.getPage(slug);\n // Fallback: when slug is empty (root path /) and getPage returns undefined,\n // try ['index'] — fumadocs-core's slugsPlugin may assign [\"index\"] to index.mdx\n // due to slug de-duplication conflict, causing getPage([]) to miss it.\n if (!page && (!slug || slug.length === 0)) {\n page = source.getPage(['index']);\n }\n${filterInPage}\n if (!page) {\n notFound();\n }\n${openapiBranch}\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, 'Tabs.Tab': 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 allSlugs: Set<string>,\n isOApi: boolean\n): string {\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set<string>(${JSON.stringify([...allSlugs])});\n\nfunction isAllowed(slug: string[] | undefined, lang?: string): boolean {\n if (allowedSlugs.size === 0) return true;\n const rawKey = slug && slug.length > 0 ? slug.join('/') : 'index';\n const key = lang ? \\`\\${lang}/\\${rawKey}\\` : rawKey;\n return allowedSlugs.has(key) || (slug?.[0] === 'openapi') || (slug?.[0] === 'api');\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug, lang)) {\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, p.lang));\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 homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);\n const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (homepage || fallback) {\n params.unshift({ ...(homepage ?? fallback!), slug: [], lang });\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 homepage = params.find((p: { slug: string[]; lang: string }) => p.slug.length === 1 && p.slug[0] === 'index' && p.lang === lang);\n const fallback = params.find((p: { slug: string[]; lang: string }) => p.lang === lang);\n if (homepage || fallback) {\n params.unshift({ ...(homepage ?? fallback!), slug: [], lang });\n }\n }\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const apiPageImport = isOApi ? \"\\nimport { APIPage } from '@/components/api-page';\" : '';\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 // OpenAPI 页面渲染分支(i18n 模式)\n const openapiBranch = isOApi\n ? `\n if (page.data.type === 'openapi') {\n return (\n <DocsPage full>\n <h1 className=\"text-[1.75em] font-semibold\">{page.data.title}</h1>\n <DocsBody>\n <APIPage {...(page.data as any).getAPIPageProps()} />\n </DocsBody>\n </DocsPage>\n );\n }\n`\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}${apiPageImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[]; lang: string }> }) {\n const { slug, lang } = await params;\n let page = source.getPage(slug, lang);\n // Fallback: when slug is empty (root path /) and getPage returns undefined,\n // try ['index'] — fumadocs-core's slugsPlugin may assign [\"index\"] to index.mdx\n // due to slug de-duplication conflict, causing getPage([], lang) to miss it.\n if (!page && (!slug || slug.length === 0)) {\n page = source.getPage(['index'], lang);\n }\n${filterInPage}\n if (!page) {\n notFound();\n }\n${openapiBranch}\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, 'Tabs.Tab': 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 searchConfig = ctx.config.search;\n // 「配置即启用」:存在 search 字段即启用搜索(与 header 语义一致)\n const searchEnabled = searchConfig !== undefined;\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","import type { GenerateContext } from './index.js';\nimport { resolveNavLogoProps } from './layout.js';\n\nexport function generateTopBarComponent(ctx: GenerateContext): string {\n const { config } = ctx;\n const header = config.header!;\n const height = header.height ?? '64px';\n const sticky = header.sticky ?? true;\n const bordered = header.bordered ?? true;\n const background = header.background ?? '';\n\n // Logo 解析优先级(由 mergeDefaults + resolveEffectiveLogo 处理):\n // 1. config.logo 且 position='header' → 已传播到 header.logo\n // 2. header.logo(旧配置)\n // 3. 回退到 config.name(文本 logo)\n const logoSource = header.logo ?? config.name;\n const logoProps = resolveNavLogoProps(logoSource, config.name);\n\n // 处理链接 — 序列化为 JSON 传给 NavLinks 组件\n const linksJson = JSON.stringify(header.links ?? []);\n\n const backgroundProp = background ? `\\n background='${background}',` : '';\n\n // 搜索位置:header 模式下在 center 插槽渲染搜索触发器\n const searchPosition = config.search?.position;\n const isTopBarSearch = searchPosition === 'header';\n\n const searchImport = isTopBarSearch\n ? \"\\nimport { TopBarSearchTrigger } from 'openmanual/components/top-bar-search-trigger';\"\n : '';\n\n // 将所有复杂 props 提取为变量,避免 Turbopack 解析行内大 JSON/JSX 时报错\n const centerProp = isTopBarSearch ? '\\n center={searchCenter}' : '';\n\n return `'use client';\n\nimport { TopBar } from 'openmanual/components/top-bar';\nimport { NavLogo } from 'openmanual/components/nav-layout';\nimport { NavLinks } from 'openmanual/components/nav-links';${searchImport}\n\nconst navLinks = ${linksJson};\n${isTopBarSearch ? 'const searchCenter = <TopBarSearchTrigger />;' : ''}\n\nexport function OmTopBar() {\n return (\n <TopBar\n height='${height}'\n sticky={${sticky}}${backgroundProp}\n bordered={${bordered}}\n left={<NavLogo ${logoProps} />}${centerProp}\n right={<NavLinks links={navLinks} />}\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 'collections/*': ['./.source/*'],\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 {\n isDirParser,\n isHeaderEnabled,\n isI18nEnabled,\n isOpenApiEnabled,\n isSeparateTabMode,\n resolveEffectiveLogo,\n resolveOpenApiSpecPaths,\n} from '../config/schema.js';\nimport {\n collectSlugsFromMeta,\n type MetaGroupInfo,\n scanMetaFiles,\n} from '../content/meta-scanner.js';\nimport { type ContentFile, scanContentDir } from '../content/scanner.js';\nimport { formatTitle } from '../content/tree.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, resolveNavLogoProps } 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 {\n generateApiClientComponent,\n generateApiPageComponent,\n generateOpenApiLib,\n} from './openapi.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 { generateTopBarComponent } from './top-bar.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 /** Pre-computed slugs from meta.json or file system (replaces collectConfiguredSlugs) */\n allSlugs?: Set<string>;\n /** Meta groups with root: true — used to generate explicit Layout Tabs in DocsLayout */\n rootGroups?: Array<{ title: string; dirPath: string; url: string; urls: string[] }>;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const isI18n = isI18nEnabled(ctx.config);\n\n // === OpenAPI 规范文件校验 ===\n if (isOpenApiEnabled(ctx.config)) {\n const { access } = await import('node:fs/promises');\n const { extname, join } = await import('node:path');\n const supportedExts = ['.json', '.yaml', '.yml'];\n const specPaths = resolveOpenApiSpecPaths(ctx.config);\n\n for (const specPath of specPaths) {\n const absolutePath = join(ctx.projectDir, specPath);\n const ext = extname(absolutePath).toLowerCase();\n\n if (!supportedExts.includes(ext)) {\n throw new Error(\n `[openapi] 不支持的 OpenAPI 规范文件格式: \"${ext}\"(文件: ${specPath})。支持的格式: ${supportedExts.join(', ')}`\n );\n }\n\n try {\n await access(absolutePath);\n } catch {\n throw new Error(\n `[openapi] OpenAPI 规范文件不存在: \"${specPath}\"。` +\n `请确认 \"openapi.specs\" 或 \"openapi.specPath\" 在 openmanual.json 中配置的路径正确。`\n );\n }\n }\n }\n\n // Pre-compute slugs for page generation (replaces collectConfiguredSlugs from sidebar)\n await computeAllSlugs(ctx);\n\n // 基础配置文件(两种模式共用)\n const isOApi = isOpenApiEnabled(ctx.config);\n\n // === OpenAPI 文件(条件性生成)===\n const openapiFiles: Array<{ path: string; content: string }> = [];\n if (isOApi) {\n const openapiLib = generateOpenApiLib(ctx);\n if (openapiLib) {\n openapiFiles.push({ path: 'lib/openapi.ts', content: openapiLib });\n }\n openapiFiles.push({\n path: 'components/api-page.client.tsx',\n content: generateApiClientComponent(),\n });\n openapiFiles.push({\n path: 'components/api-page.tsx',\n content: generateApiPageComponent(),\n });\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 // 顶部横条组件(条件性生成,在 i18n/单语言分支中分别注册正确路径)\n const headerEnabled = isHeaderEnabled(ctx.config);\n\n let files: Array<{ path: string; content: string }>;\n\n if (isI18n) {\n // === 多语言模式:[lang]/ 动态路由结构 ===\n files = [\n ...baseFiles,\n ...openapiFiles,\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 ...(headerEnabled\n ? [{ path: 'app/[lang]/components/top-bar.tsx', content: generateTopBarComponent(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 ...openapiFiles,\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 ...(headerEnabled\n ? [{ path: 'app/components/top-bar.tsx', content: generateTopBarComponent(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 // Resolution priority: config.logo > navbar.logo (legacy)\n const rawLogo =\n ctx.config.logo != null\n ? typeof ctx.config.logo === 'string'\n ? ctx.config.logo\n : { light: ctx.config.logo.light, dark: ctx.config.logo.dark }\n : ctx.config.navbar?.logo;\n\n if (rawLogo && typeof rawLogo === 'string' && isImagePath(rawLogo)) {\n await ensureLogoFile(ctx, rawLogo, 'light');\n } else if (rawLogo && typeof rawLogo === 'object') {\n const { light, dark } = resolveLogoPaths(rawLogo);\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 const headerEnabled = isHeaderEnabled(config);\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 const topBarImport = headerEnabled ? \"import { OmTopBar } from './components/top-bar';\\n\" : '';\n\n const topBarJsx = headerEnabled ? '<OmTopBar />\\n ' : '';\n\n return `${metadataExport}${topBarImport}import { AppLayout } from 'openmanual/components/app-layout';\nimport { AppProvider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <AppLayout>\n <AppProvider>${topBarJsx}{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 const headerEnabled = isHeaderEnabled(config);\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 const topBarImport = headerEnabled ? \"import { OmTopBar } from './components/top-bar';\\n\" : '';\n\n const topBarJsx = headerEnabled ? '<OmTopBar />\\n ' : '';\n\n return `${metadataExport}${topBarImport}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}>${topBarJsx}{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 const isOApi = isOpenApiEnabled(config);\n const rootGroups = ctx.rootGroups;\n const isHeaderSearch = config.search?.position === 'header';\n\n // 解析有效 logo:判断是否需要在侧边栏显示\n const { source: logoSource, position: logoPosition } = resolveEffectiveLogo(config);\n const hasSidebarLogo = logoSource !== undefined && logoPosition === 'sidebar';\n const sidebarLogoImport = hasSidebarLogo\n ? \"\\nimport { NavLogo } from 'openmanual/components/nav-layout';\"\n : '';\n const sidebarLogoProps = hasSidebarLogo ? resolveNavLogoProps(logoSource!, config.name) : null;\n // sidebar.banner 只在有图片 logo 时添加(文本 logo 通过 nav.title 处理)\n const sidebarBannerLine =\n hasSidebarLogo && sidebarLogoProps && !sidebarLogoProps.includes('type=\"text\"')\n ? `\\n banner: <NavLogo ${sidebarLogoProps} />,`\n : '';\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 // Generate explicit sidebar.tabs from root groups so Layout Tabs are visible on all pages (including homepage).\n // Tab URLs use directory paths so isActive() prefix matching covers all pages in the group.\n // OpenAPI Tab 注入(仅在 separateTab 模式下注入独立 Tab;否则 API 页面混合到文档树中)\n const separateTab = isOpenApiEnabled(config) && isSeparateTabMode(config);\n const openapiTab = separateTab\n ? {\n title: config.openapi?.label ?? '接口文档',\n url: isI18n ? '/${lang}/openapi' : '/openapi',\n urls: new Set<string>(),\n }\n : null;\n\n const sidebarTabsLine =\n (rootGroups && rootGroups.length > 0) || openapiTab\n ? isI18n\n ? generateI18nSidebarTabs(config, rootGroups, openapiTab)\n : generateSingleSidebarTabs(config, rootGroups, openapiTab)\n : '';\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';${sidebarLogoImport}${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${\n isOApi && separateTab\n ? ` const _omFirstApi = source.getPages(lang)?.find((p: any) => p.data?.type === 'openapi');\n const _omApiUrl = _omFirstApi?.url ?? \\`/\\${lang}/openapi\\`;\n`\n : ''\n}${\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}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${\n configDesc ? '\\n description: siteDescription,' : ''\n }${isHeaderSearch ? '\\n searchToggle: { enabled: false },' : ''}\n sidebar: { collapsible: false,${sidebarBannerLine}\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';${sidebarLogoImport}${\n isOApi && separateTab\n ? `\nconst _omFirstApi = source.getPages()?.find((p: any) => p.data?.type === 'openapi');\nconst _omApiUrl = _omFirstApi?.url ?? '/openapi';\n`\n : ''\n }\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${sidebarTabsLine}${githubLine}${linksLine}${footerLine}${descLine}${\n isHeaderSearch ? '\\n searchToggle: { enabled: false },' : ''\n }\n sidebar: { collapsible: false,${sidebarBannerLine}\n },\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\n/**\n * Generate i18n sidebar.tabs string.\n * Extracted from generateDocsLayout to avoid deeply nested template literals\n * that confuse the oxc parser when escaped backticks are involved.\n */\nfunction generateI18nSidebarTabs(\n config: OpenManualConfig,\n rootGroups: GenerateContext['rootGroups'],\n openapiTab: { title: string; url: string; urls: Set<string> } | null\n): string {\n const entries = (rootGroups ?? []).map((g) => ({\n title: g.title,\n dirPath: g.dirPath,\n url: g.url,\n urls: g.urls,\n }));\n const entriesJson = JSON.stringify(entries);\n const nameEscaped = config.name.replace(/'/g, \"\\\\'\");\n\n const openapiTabLine = openapiTab\n ? `,\\n { title: '${(openapiTab.title as string).replace(/'/g, \"\\\\'\")}', url: _omApiUrl, urls: new Set<string>() }`\n : '';\n\n // Build the generated code string piece by piece to keep each template literal shallow\n const homeTab = `{ title: '${nameEscaped}', url: \\`/\\${lang}\\` }`;\n const mapExpr = `(${entriesJson} as Array<{title:string;dirPath:string;url:string;urls:string[]}>).filter(g => g.dirPath.startsWith(\\`\\${lang}/\\`)).map(g => ({ title: g.title, url: g.url, urls: new Set<string>(g.urls) }))`;\n\n return `\\n sidebar: {\\n tabs: [\\n ${homeTab},\\n ...${mapExpr}${openapiTabLine}\\n ],\\n },`;\n}\n\n/**\n * Generate single-language (non-i18n) sidebar.tabs string.\n * Uses template literal to preserve Set<string> for urls property.\n */\nfunction generateSingleSidebarTabs(\n config: OpenManualConfig,\n rootGroups: GenerateContext['rootGroups'],\n openapiTab: { title: string; url: string; urls: Set<string> } | null\n): string {\n const nameEscaped = config.name.replace(/'/g, \"\\\\'\");\n const groupEntries = (rootGroups ?? [])\n .map((g) => {\n const title = g.title.replace(/'/g, \"\\\\'\");\n const urlsArr = JSON.stringify(g.urls);\n return `{ title: '${title}', url: '${g.url}', urls: new Set(${urlsArr}) }`;\n })\n .join(',\\n ');\n\n const openapiTabLine = openapiTab\n ? `,\\n { title: '${(openapiTab.title as string).replace(/'/g, \"\\\\'\")}', url: _omApiUrl, urls: new Set<string>() }`\n : '';\n\n return `\\n sidebar: {\\n tabs: [\\n { title: '${nameEscaped}', url: '/' },${groupEntries ? '\\n ' + groupEntries : ''}${openapiTabLine}\\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 or enrich meta.json files for each content directory.\n *\n * Strategy:\n * 1. If meta.json files exist → enrich missing fields (icon/defaultOpen/pages)\n * 2. If no meta.json → auto-generate from file system structure\n */\n\n/**\n * Compute all slugs from meta.json files, falling back to file system scan.\n * Stores result in ctx.allSlugs for use by generatePage().\n * Also extracts root groups (meta.json with root: true) into ctx.rootGroups.\n */\nasync function computeAllSlugs(ctx: GenerateContext): Promise<void> {\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 // Priority 1: Collect from meta.json files\n const metaGroups = await scanMetaFiles(contentAbsDir, languages, useDirParser);\n if (metaGroups.length > 0) {\n ctx.allSlugs = collectSlugsFromMeta(metaGroups);\n // Also collect root-level file slugs (e.g. index.mdx, quickstart.mdx under {lang}/)\n // which are not covered by any meta.json pages field\n const allFiles = await scanContentDir(contentAbsDir);\n for (const file of allFiles) {\n // Root-level files: directly under {lang}/ (dir-parser: segments length == 2)\n // or directly under content/ (dot-parser: segments length == 1)\n const isRootLevel = useDirParser\n ? file.segments.length === 2 && languages.includes(file.segments[0]!)\n : file.segments.length === 1;\n if (isRootLevel) {\n ctx.allSlugs.add(file.slug);\n }\n }\n // Scan all directories to collect actual file slugs and enable urls Set generation.\n const scannedDirCache = new Map<string, ContentFile[]>();\n for (const group of metaGroups) {\n const dirAbsPath = join(contentAbsDir, group.dirPath);\n try {\n const dirFiles = await scanContentDir(dirAbsPath);\n scannedDirCache.set(group.dirPath, dirFiles);\n // Only add to allSlugs if group has no explicit pages (avoid duplicates)\n if (!group.pages || group.pages.length === 0) {\n for (const df of dirFiles) {\n ctx.allSlugs.add(`${group.dirPath}/${df.slug}`);\n }\n }\n } catch {\n // Directory may not exist or empty — skip\n }\n }\n // Extract groups with root: true for explicit Layout Tabs generation.\n // - url: first actual page (for navigation — avoids 404 on directory paths)\n // - urls: Set of all pages in the group (for isLayoutTabActive exact matching)\n ctx.rootGroups = metaGroups\n .filter((g) => g.root === true)\n .map((g) => {\n const cached = scannedDirCache.get(g.dirPath);\n const firstPage = g.pages?.[0] ?? cached?.[0]?.name ?? 'index';\n const allUrls = (cached ?? []).map((f) => `/${g.dirPath}/${f.name}` as string);\n return {\n title: g.title,\n dirPath: g.dirPath,\n url: `/${g.dirPath}/${firstPage}`,\n urls: allUrls,\n };\n });\n return;\n }\n\n // Priority 2: Collect from file system (all .mdx/.md slugs)\n const files = await scanContentDir(contentAbsDir);\n ctx.allSlugs = new Set(files.map((f) => f.slug));\n}\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\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 // Scan existing meta.json files\n const metaGroups = await scanMetaFiles(contentAbsDir, languages, useDirParser);\n\n // Enrich existing meta.json files\n if (metaGroups.length > 0) {\n for (const group of metaGroups) {\n await enrichMetaFile(group);\n }\n return;\n }\n\n // Auto-generate from file system structure\n await autoGenerateMetaFromFS(ctx, contentAbsDir, languages, useDirParser);\n}\n\n/**\n * Validate an existing meta.json file is readable.\n * Does NOT modify the file — all user-set fields (including \"root\") are preserved as-is.\n * Fumadocs reads meta.json directly via its own content source pipeline.\n */\nasync function enrichMetaFile(_group: MetaGroupInfo): Promise<void> {\n try {\n await readFile(_group.filePath, 'utf-8');\n } catch {\n // File unreadable - skip\n }\n}\n\n/**\n * Auto-generate meta.json files from the file system structure.\n * Used when no meta.json and no sidebar config exist.\n */\nasync function autoGenerateMetaFromFS(\n _ctx: GenerateContext,\n contentAbsDir: string,\n languages: string[],\n useDirParser: boolean\n): Promise<void> {\n const files = await scanContentDir(contentAbsDir);\n\n // Group files by their directory structure\n const rootFiles: typeof files = [];\n const dirGroups = new Map<string, typeof files>();\n\n for (const file of files) {\n if (file.segments.length <= 1) {\n rootFiles.push(file);\n } else {\n const dirName = file.segments[0];\n if (dirName === undefined) continue;\n if (!dirGroups.has(dirName)) {\n dirGroups.set(dirName, []);\n }\n dirGroups.get(dirName)?.push(file);\n }\n }\n\n // Generate root-level meta.json if there are root files\n if (rootFiles.length > 0) {\n const rootMeta = {\n title: 'Getting Started',\n pages: rootFiles.map((f) => f.name),\n };\n\n if (useDirParser) {\n for (const lang of languages) {\n await writeMetaIfNotExists(join(contentAbsDir, lang, 'meta.json'), rootMeta);\n }\n } else {\n await writeMetaIfNotExists(join(contentAbsDir, 'meta.json'), rootMeta);\n }\n }\n\n // Generate meta.json for each directory group\n for (const [dirName, dirFiles] of dirGroups) {\n const dirMeta: Record<string, unknown> = {\n title: formatTitle(dirName),\n pages: dirFiles.map((f) => f.segments.slice(1).join('/')),\n };\n\n if (useDirParser) {\n for (const lang of languages) {\n await writeMetaIfNotExists(join(contentAbsDir, lang, dirName, 'meta.json'), dirMeta);\n }\n } else {\n await writeMetaIfNotExists(join(contentAbsDir, dirName, 'meta.json'), dirMeta);\n }\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","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.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ declare const OpenManualConfigSchema: z.ZodObject<{
|
|
|
13
13
|
all: "all";
|
|
14
14
|
}>>;
|
|
15
15
|
favicon: z.ZodOptional<z.ZodString>;
|
|
16
|
+
logo: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
17
|
+
light: z.ZodString;
|
|
18
|
+
dark: z.ZodString;
|
|
19
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
20
|
+
sidebar: "sidebar";
|
|
21
|
+
header: "header";
|
|
22
|
+
}>>;
|
|
23
|
+
}, z.core.$strip>]>>;
|
|
16
24
|
navbar: z.ZodOptional<z.ZodObject<{
|
|
17
25
|
logo: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
18
26
|
light: z.ZodString;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,18 @@ const LogoSchema = z.union([z.string(), z.object({
|
|
|
7
7
|
light: z.string(),
|
|
8
8
|
dark: z.string()
|
|
9
9
|
})]);
|
|
10
|
+
/** Logo 显示位置 */
|
|
11
|
+
const LogoPositionSchema = z.enum(["sidebar", "header"]);
|
|
12
|
+
/**
|
|
13
|
+
* 顶级 Logo 配置(支持字符串简写和对象形式)
|
|
14
|
+
* - 字符串简写: "/logo.svg" → { light, dark: 同值, position: 'sidebar' }
|
|
15
|
+
* - 对象形式: { light, dark, position? }
|
|
16
|
+
*/
|
|
17
|
+
const TopLevelLogoSchema = z.union([z.string(), z.object({
|
|
18
|
+
light: z.string(),
|
|
19
|
+
dark: z.string(),
|
|
20
|
+
position: LogoPositionSchema.optional()
|
|
21
|
+
})]);
|
|
10
22
|
const FaviconSchema = z.string();
|
|
11
23
|
const NavbarSchema = z.object({
|
|
12
24
|
logo: LogoSchema.optional(),
|
|
@@ -88,6 +100,7 @@ const OpenManualConfigSchema = z.object({
|
|
|
88
100
|
locale: z.string().optional(),
|
|
89
101
|
contentPolicy: z.enum(["strict", "all"]).optional(),
|
|
90
102
|
favicon: FaviconSchema.optional(),
|
|
103
|
+
logo: TopLevelLogoSchema.optional(),
|
|
91
104
|
navbar: NavbarSchema.optional(),
|
|
92
105
|
header: TopBarSchema.optional(),
|
|
93
106
|
footer: FooterSchema.optional(),
|
|
@@ -99,6 +112,21 @@ const OpenManualConfigSchema = z.object({
|
|
|
99
112
|
i18n: I18nConfigSchema.optional(),
|
|
100
113
|
openapi: OpenApiSchema.optional()
|
|
101
114
|
});
|
|
115
|
+
/**
|
|
116
|
+
* 将顶级 logo 配置标准化为 { light, dark } 形式
|
|
117
|
+
*/
|
|
118
|
+
function normalizeTopLevelLogo(logo) {
|
|
119
|
+
if (typeof logo === "string") return {
|
|
120
|
+
light: logo,
|
|
121
|
+
dark: logo,
|
|
122
|
+
position: "sidebar"
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
light: logo.light,
|
|
126
|
+
dark: logo.dark,
|
|
127
|
+
position: logo.position ?? "sidebar"
|
|
128
|
+
};
|
|
129
|
+
}
|
|
102
130
|
|
|
103
131
|
//#endregion
|
|
104
132
|
//#region src/core/config/loader.ts
|
|
@@ -137,17 +165,31 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
137
165
|
return mergeDefaults(result.data);
|
|
138
166
|
}
|
|
139
167
|
function mergeDefaults(config) {
|
|
168
|
+
const topLevelLogo = config.logo ? normalizeTopLevelLogo(config.logo) : null;
|
|
169
|
+
const topLevelLogoSource = topLevelLogo ? topLevelLogo.light === topLevelLogo.dark ? topLevelLogo.light : {
|
|
170
|
+
light: topLevelLogo.light,
|
|
171
|
+
dark: topLevelLogo.dark
|
|
172
|
+
} : null;
|
|
140
173
|
return {
|
|
141
174
|
...config,
|
|
142
175
|
contentPolicy: config.contentPolicy ?? "strict",
|
|
143
176
|
contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? "content",
|
|
144
177
|
outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? "dist",
|
|
145
178
|
locale: config.locale ?? DEFAULT_CONFIG.locale ?? "zh",
|
|
179
|
+
logo: topLevelLogo ? typeof config.logo === "string" ? config.logo : {
|
|
180
|
+
light: topLevelLogo.light,
|
|
181
|
+
dark: topLevelLogo.dark,
|
|
182
|
+
position: topLevelLogo.position
|
|
183
|
+
} : void 0,
|
|
146
184
|
navbar: {
|
|
147
185
|
...DEFAULT_CONFIG.navbar,
|
|
148
186
|
...config.navbar,
|
|
149
|
-
logo: config.navbar?.logo ?? config.name
|
|
187
|
+
logo: config.navbar?.logo ?? (topLevelLogo && topLevelLogo.position === "sidebar" ? topLevelLogoSource ?? config.name : config.name)
|
|
150
188
|
},
|
|
189
|
+
header: config.header ? {
|
|
190
|
+
...config.header,
|
|
191
|
+
logo: config.header.logo ?? (topLevelLogo && topLevelLogo.position === "header" ? topLevelLogoSource ?? void 0 : void 0)
|
|
192
|
+
} : void 0,
|
|
151
193
|
footer: {
|
|
152
194
|
...DEFAULT_CONFIG.footer,
|
|
153
195
|
...config.footer,
|
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\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\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 /**\n * 搜索入口位置\n * - 'sidebar': 放在侧边栏顶部(Fumadocs 风格)\n * - 'header': 放在 header 中间(Mintlify 风格)\n *\n * 默认值: 'sidebar'\n * 不配置 search 字段则不启用搜索(与 header 的「配置即启用」语义一致)\n */\n position: z.enum(['sidebar', 'header']).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 OpenApiSpecSchema = z.object({\n /** OpenAPI 规范文件路径,相对于项目根目录 */\n path: z.string(),\n /** 分组标签(用于在侧边栏中显示的分组名) */\n group: z.string().optional(),\n});\n\nexport const OpenApiSchema = z.object({\n /**\n * OpenAPI 规范文件路径(单个文件),相对于项目根目录。支持 .json / .yaml / .yml\n * @deprecated 推荐使用 specs 字段\n */\n specPath: z.string().optional(),\n /**\n * OpenAPI 规范文件配置,支持字符串(单文件)或对象数组(多文件)\n * - string: 单个 spec 文件路径\n * - array: 多个 spec 文件配置,每个可指定 path 和 group\n */\n specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),\n /** 侧边栏 Tab 显示名称,默认 \"接口文档\"(仅在 separateTab: true 时生效) */\n label: z.string().optional(),\n /**\n * API 端点在侧边栏中的分组策略\n * - 'tag': 按 OpenAPI 规范中的 tags 分组(默认)\n * - 'route': 按路由路径分组\n * - 'none': 不分组,平铺展示\n */\n groupBy: z.enum(['tag', 'route', 'none']).optional().default('tag'),\n /**\n * 是否将 API 文档作为独立的侧边栏 Tab 展示\n * - true: 保持旧行为,API 文档在独立 Tab 中(向后兼容)\n * - false: 将 API 端点混合到文档导航树中(类似 Mintlify 风格) // cspell:ignore Mintlify\n */\n separateTab: z.boolean().optional().default(false),\n});\n\n/** 顶部横条链接项 */\nexport const TopBarLinkSchema = z\n .object({\n /** 链接显示文本(与 icon 至少填一个) */\n label: z.string().optional(),\n /** lucide-react 图标名称(如 \"Github\", \"Twitter\",与 label 至少填一个) */\n icon: z.string().optional(),\n href: z.string(),\n external: z.boolean().optional().default(true),\n })\n .refine((data) => data.label || data.icon, {\n message: '至少需要提供 label 或 icon 中的一个',\n path: ['label'],\n });\n\n/** 顶部横条配置 */\nexport const TopBarSchema = z.object({\n /** 高度,默认 '64px' */\n height: z.string().optional(),\n /** Logo 配置(独立于 navbar.logo) */\n logo: LogoSchema.optional(),\n /** 右侧导航链接 */\n links: z.array(TopBarLinkSchema).optional(),\n /** 是否显示粘性(sticky),默认 true */\n sticky: z.boolean().optional().default(true),\n /** 背景色(CSS 值) */\n background: z.string().optional(),\n /** 底部边框 */\n bordered: z.boolean().optional().default(true),\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 header: TopBarSchema.optional(),\n footer: FooterSchema.optional(),\n // @deprecated Use meta.json files in content directories instead\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 openapi: OpenApiSchema.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>;\nexport type OpenApiConfig = z.infer<typeof OpenApiSchema>;\nexport type TopBarConfig = z.infer<typeof TopBarSchema>;\nexport type TopBarLink = z.infer<typeof TopBarLinkSchema>;\n\n// @deprecated Use collectSlugsFromMeta from meta-scanner instead\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n console.warn(\n '[openmanual] The \"sidebar\" field in openmanual.json is deprecated. ' +\n 'Please use meta.json files in your content directories instead.'\n );\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\nexport function isOpenApiEnabled(config: OpenManualConfig): boolean {\n if (config.openapi === undefined) return false;\n // 检查 specs(新格式)或 specPath(旧格式,向后兼容)\n const hasSpecs = config.openapi.specs !== undefined;\n const hasSpecPath = typeof config.openapi?.specPath === 'string';\n return hasSpecs || hasSpecPath;\n}\n\n/**\n * 从 OpenAPI 配置中解析出所有 spec 文件路径\n * 兼容 specs(新)和 specPath(旧)两种格式\n */\nexport function resolveOpenApiSpecPaths(config: OpenManualConfig): string[] {\n const openApiCfg = config.openapi;\n if (!openApiCfg) return [];\n\n // 新格式:specs 数组或字符串\n if (openApiCfg.specs !== undefined) {\n if (typeof openApiCfg.specs === 'string') {\n return [openApiCfg.specs];\n }\n return openApiCfg.specs.map((s) => s.path);\n }\n\n // 旧格式:specPath 字符串\n if (typeof openApiCfg.specPath === 'string') {\n return [openApiCfg.specPath];\n }\n\n return [];\n}\n\n/**\n * 判断是否使用独立 Tab 模式(旧行为)\n */\nexport function isSeparateTabMode(config: OpenManualConfig): boolean {\n return config.openapi?.separateTab === true;\n}\n\n/**\n * 判断是否启用了顶部横条(配置了 header 即启用)\n */\nexport function isHeaderEnabled(config: OpenManualConfig): boolean {\n return config.header !== undefined;\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 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 字段则保持 undefined(不启用搜索)\n search: config.search ? { position: config.search.position ?? 'sidebar' } : undefined,\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 openapi: config.openapi\n ? {\n specPath: config.openapi.specPath,\n specs: config.openapi.specs,\n label: config.openapi.label ?? '接口文档',\n groupBy: config.openapi.groupBy ?? 'tag',\n separateTab: config.openapi.separateTab ?? false,\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\n/** Logo 显示位置 */\nexport const LogoPositionSchema = z.enum(['sidebar', 'header']);\n\n/**\n * 顶级 Logo 配置(支持字符串简写和对象形式)\n * - 字符串简写: \"/logo.svg\" → { light, dark: 同值, position: 'sidebar' }\n * - 对象形式: { light, dark, position? }\n */\nexport const TopLevelLogoSchema = z.union([\n z.string(),\n z.object({\n light: z.string(),\n dark: z.string(),\n position: LogoPositionSchema.optional(),\n }),\n]);\n\nexport const FaviconSchema = z.string();\n\nexport const NavbarSchema = z.object({\n /** @deprecated 使用顶级 `logo` 字段代替 */\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\n// @deprecated Use meta.json files in content directories instead\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\n// @deprecated Use meta.json files in content directories instead\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 /**\n * 搜索入口位置\n * - 'sidebar': 放在侧边栏顶部(Fumadocs 风格)\n * - 'header': 放在 header 中间(Mintlify 风格)\n *\n * 默认值: 'sidebar'\n * 不配置 search 字段则不启用搜索(与 header 的「配置即启用」语义一致)\n */\n position: z.enum(['sidebar', 'header']).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 OpenApiSpecSchema = z.object({\n /** OpenAPI 规范文件路径,相对于项目根目录 */\n path: z.string(),\n /** 分组标签(用于在侧边栏中显示的分组名) */\n group: z.string().optional(),\n});\n\nexport const OpenApiSchema = z.object({\n /**\n * OpenAPI 规范文件路径(单个文件),相对于项目根目录。支持 .json / .yaml / .yml\n * @deprecated 推荐使用 specs 字段\n */\n specPath: z.string().optional(),\n /**\n * OpenAPI 规范文件配置,支持字符串(单文件)或对象数组(多文件)\n * - string: 单个 spec 文件路径\n * - array: 多个 spec 文件配置,每个可指定 path 和 group\n */\n specs: z.union([z.string(), z.array(OpenApiSpecSchema)]).optional(),\n /** 侧边栏 Tab 显示名称,默认 \"接口文档\"(仅在 separateTab: true 时生效) */\n label: z.string().optional(),\n /**\n * API 端点在侧边栏中的分组策略\n * - 'tag': 按 OpenAPI 规范中的 tags 分组(默认)\n * - 'route': 按路由路径分组\n * - 'none': 不分组,平铺展示\n */\n groupBy: z.enum(['tag', 'route', 'none']).optional().default('tag'),\n /**\n * 是否将 API 文档作为独立的侧边栏 Tab 展示\n * - true: 保持旧行为,API 文档在独立 Tab 中(向后兼容)\n * - false: 将 API 端点混合到文档导航树中(类似 Mintlify 风格) // cspell:ignore Mintlify\n */\n separateTab: z.boolean().optional().default(false),\n});\n\n/** 顶部横条链接项 */\nexport const TopBarLinkSchema = z\n .object({\n /** 链接显示文本(与 icon 至少填一个) */\n label: z.string().optional(),\n /** lucide-react 图标名称(如 \"Github\", \"Twitter\",与 label 至少填一个) */\n icon: z.string().optional(),\n href: z.string(),\n external: z.boolean().optional().default(true),\n })\n .refine((data) => data.label || data.icon, {\n message: '至少需要提供 label 或 icon 中的一个',\n path: ['label'],\n });\n\n/** 顶部横条配置 */\nexport const TopBarSchema = z.object({\n /** 高度,默认 '64px' */\n height: z.string().optional(),\n /** @deprecated 使用顶级 `logo` 字段并设置 `position: \"header\"` 代替 */\n logo: LogoSchema.optional(),\n /** 右侧导航链接 */\n links: z.array(TopBarLinkSchema).optional(),\n /** 是否显示粘性(sticky),默认 true */\n sticky: z.boolean().optional().default(true),\n /** 背景色(CSS 值) */\n background: z.string().optional(),\n /** 底部边框 */\n bordered: z.boolean().optional().default(true),\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 /** 顶级 Logo 配置(替代 navbar.logo / header.logo) */\n logo: TopLevelLogoSchema.optional(),\n navbar: NavbarSchema.optional(),\n header: TopBarSchema.optional(),\n footer: FooterSchema.optional(),\n // @deprecated Use meta.json files in content directories instead\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 openapi: OpenApiSchema.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>;\nexport type OpenApiConfig = z.infer<typeof OpenApiSchema>;\nexport type TopBarConfig = z.infer<typeof TopBarSchema>;\nexport type TopBarLink = z.infer<typeof TopBarLinkSchema>;\nexport type TopLevelLogoConfig = z.infer<typeof TopLevelLogoSchema>;\nexport type LogoPosition = z.infer<typeof LogoPositionSchema>;\n\n// @deprecated Use collectSlugsFromMeta from meta-scanner instead\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n console.warn(\n '[openmanual] The \"sidebar\" field in openmanual.json is deprecated. ' +\n 'Please use meta.json files in your content directories instead.'\n );\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\nexport function isOpenApiEnabled(config: OpenManualConfig): boolean {\n if (config.openapi === undefined) return false;\n // 检查 specs(新格式)或 specPath(旧格式,向后兼容)\n const hasSpecs = config.openapi.specs !== undefined;\n const hasSpecPath = typeof config.openapi?.specPath === 'string';\n return hasSpecs || hasSpecPath;\n}\n\n/**\n * 从 OpenAPI 配置中解析出所有 spec 文件路径\n * 兼容 specs(新)和 specPath(旧)两种格式\n */\nexport function resolveOpenApiSpecPaths(config: OpenManualConfig): string[] {\n const openApiCfg = config.openapi;\n if (!openApiCfg) return [];\n\n // 新格式:specs 数组或字符串\n if (openApiCfg.specs !== undefined) {\n if (typeof openApiCfg.specs === 'string') {\n return [openApiCfg.specs];\n }\n return openApiCfg.specs.map((s) => s.path);\n }\n\n // 旧格式:specPath 字符串\n if (typeof openApiCfg.specPath === 'string') {\n return [openApiCfg.specPath];\n }\n\n return [];\n}\n\n/**\n * 判断是否使用独立 Tab 模式(旧行为)\n */\nexport function isSeparateTabMode(config: OpenManualConfig): boolean {\n return config.openapi?.separateTab === true;\n}\n\n/**\n * 判断是否启用了顶部横条(配置了 header 即启用)\n */\nexport function isHeaderEnabled(config: OpenManualConfig): boolean {\n return config.header !== undefined;\n}\n\n/**\n * 将顶级 logo 配置标准化为 { light, dark } 形式\n */\nexport function normalizeTopLevelLogo(logo: TopLevelLogoConfig): {\n light: string;\n dark: string;\n position: 'sidebar' | 'header';\n} {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo, position: 'sidebar' };\n }\n return {\n light: logo.light,\n dark: logo.dark,\n position: logo.position ?? 'sidebar',\n };\n}\n\n/**\n * 解析有效的 Logo 配置(统一优先级链)\n *\n * 优先级:\n * 1. config.logo(新顶级配置)\n * 2. config.navbar.logo(旧 sidebar logo)\n * 3. config.header.logo(旧 header logo)\n * 4. undefined(调用方回退到 config.name)\n */\nexport function resolveEffectiveLogo(config: OpenManualConfig): {\n source: LogoConfig | undefined;\n position: 'sidebar' | 'header';\n} {\n // 新顶级 logo 最高优先级\n if (config.logo) {\n const normalized = normalizeTopLevelLogo(config.logo);\n const { position, ...source } = normalized;\n return { source: source as LogoConfig, position };\n }\n\n // 旧 navbar.logo → sidebar\n if (config.navbar?.logo) {\n return { source: config.navbar.logo, position: 'sidebar' };\n }\n\n // 旧 header.logo → header\n if (config.header?.logo) {\n return { source: config.header.logo, position: 'header' };\n }\n\n return { source: undefined, position: 'sidebar' };\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { normalizeTopLevelLogo, 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 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 // 解析顶级 logo(如有),用于后续传播\n const topLevelLogo = config.logo ? normalizeTopLevelLogo(config.logo) : null;\n\n // 将顶级 logo 的 light/dark 部分提取为 LogoConfig 格式(去掉 position)\n const topLevelLogoSource = topLevelLogo\n ? topLevelLogo.light === topLevelLogo.dark\n ? topLevelLogo.light\n : { light: topLevelLogo.light, dark: topLevelLogo.dark }\n : null;\n\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 // 标准化顶级 logo 的 position 默认值\n logo: topLevelLogo\n ? typeof config.logo === 'string'\n ? config.logo\n : { light: topLevelLogo.light, dark: topLevelLogo.dark, position: topLevelLogo.position }\n : undefined,\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n // 传播逻辑:有顶级 logo 且 position=sidebar → 传播到 navbar.logo\n // 否则保持原有 navbar.logo ?? config.name 逻辑\n logo:\n config.navbar?.logo ??\n (topLevelLogo && topLevelLogo.position === 'sidebar'\n ? (topLevelLogoSource ?? config.name)\n : config.name),\n },\n header: config.header\n ? {\n ...config.header,\n // 传播逻辑:有顶级 logo 且 position=header → 传播到 header.logo\n // 否则保持原有 header.logo 不变\n logo:\n config.header.logo ??\n (topLevelLogo && topLevelLogo.position === 'header'\n ? (topLevelLogoSource ?? undefined)\n : undefined),\n }\n : undefined,\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 字段则保持 undefined(不启用搜索)\n search: config.search ? { position: config.search.position ?? 'sidebar' } : undefined,\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 openapi: config.openapi\n ? {\n specPath: config.openapi.specPath,\n specs: config.openapi.specs,\n label: config.openapi.label ?? '接口文档',\n groupBy: config.openapi.groupBy ?? 'tag',\n separateTab: config.openapi.separateTab ?? false,\n }\n : undefined,\n };\n}\n"],"mappings":";;;;;AAEA,MAAa,aAAa,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO;CAAE,OAAO,EAAE,QAAQ;CAAE,MAAM,EAAE,QAAQ;CAAE,CAAC,CAAC,CAAC;;AAGlG,MAAa,qBAAqB,EAAE,KAAK,CAAC,WAAW,SAAS,CAAC;;;;;;AAO/D,MAAa,qBAAqB,EAAE,MAAM,CACxC,EAAE,QAAQ,EACV,EAAE,OAAO;CACP,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,QAAQ;CAChB,UAAU,mBAAmB,UAAU;CACxC,CAAC,CACH,CAAC;AAEF,MAAa,gBAAgB,EAAE,QAAQ;AAEvC,MAAa,eAAe,EAAE,OAAO;CAEnC,MAAM,WAAW,UAAU;CAC3B,QAAQ,EAAE,KAAK,CAAC,UAAU;CAC1B,OAAO,EACJ,MACC,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ;EACjB,MAAM,EAAE,QAAQ;EACjB,CAAC,CACH,CACA,UAAU;CACd,CAAC;AAEF,MAAa,eAAe,EAAE,OAAO,EACnC,MAAM,EAAE,QAAQ,CAAC,UAAU,EAC5B,CAAC;AAGF,MAAa,oBAAoB,EAAE,OAAO;CACxC,MAAM,EAAE,QAAQ;CAChB,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC5B,CAAC;AAGF,MAAa,qBAAqB,EAAE,OAAO;CACzC,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC3B,WAAW,EAAE,SAAS,CAAC,UAAU;CACjC,OAAO,EAAE,MAAM,kBAAkB;CAClC,CAAC;AAEF,MAAa,cAAc,EAAE,OAAO;CAClC,YAAY,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU;CACjD,UAAU,EAAE,SAAS,CAAC,UAAU;CACjC,CAAC;AAEF,MAAa,eAAe,EAAE,OAAO,EASnC,UAAU,EAAE,KAAK,CAAC,WAAW,SAAS,CAAC,CAAC,UAAU,EACnD,CAAC;AAEF,MAAa,YAAY,EAAE,OAAO,EAChC,OAAO,EAAE,SAAS,CAAC,UAAU,EAC9B,CAAC;AAEF,MAAa,oBAAoB,EAAE,OAAO,EACxC,SAAS,EAAE,SAAS,CAAC,UAAU,EAChC,CAAC;AAEF,MAAa,mBAAmB,EAAE,OAAO;CACvC,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACvB,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACxB,CAAC;AAEF,MAAa,mBAAmB,EAAE,OAAO;CACvC,SAAS,EAAE,SAAS,CAAC,UAAU;CAC/B,iBAAiB,EAAE,QAAQ,CAAC,UAAU;CACtC,WAAW,EAAE,MAAM,iBAAiB,CAAC,UAAU;CAC/C,QAAQ,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU;CAC1C,CAAC;AAEF,MAAa,oBAAoB,EAAE,OAAO;CAExC,MAAM,EAAE,QAAQ;CAEhB,OAAO,EAAE,QAAQ,CAAC,UAAU;CAC7B,CAAC;AAEF,MAAa,gBAAgB,EAAE,OAAO;CAKpC,UAAU,EAAE,QAAQ,CAAC,UAAU;CAM/B,OAAO,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAAC,UAAU;CAEnE,OAAO,EAAE,QAAQ,CAAC,UAAU;CAO5B,SAAS,EAAE,KAAK;EAAC;EAAO;EAAS;EAAO,CAAC,CAAC,UAAU,CAAC,QAAQ,MAAM;CAMnE,aAAa,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM;CACnD,CAAC;;AAGF,MAAa,mBAAmB,EAC7B,OAAO;CAEN,OAAO,EAAE,QAAQ,CAAC,UAAU;CAE5B,MAAM,EAAE,QAAQ,CAAC,UAAU;CAC3B,MAAM,EAAE,QAAQ;CAChB,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC/C,CAAC,CACD,QAAQ,SAAS,KAAK,SAAS,KAAK,MAAM;CACzC,SAAS;CACT,MAAM,CAAC,QAAQ;CAChB,CAAC;;AAGJ,MAAa,eAAe,EAAE,OAAO;CAEnC,QAAQ,EAAE,QAAQ,CAAC,UAAU;CAE7B,MAAM,WAAW,UAAU;CAE3B,OAAO,EAAE,MAAM,iBAAiB,CAAC,UAAU;CAE3C,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAE5C,YAAY,EAAE,QAAQ,CAAC,UAAU;CAEjC,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC/C,CAAC;AAEF,MAAa,yBAAyB,EAAE,OAAO;CAC7C,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;CACvB,aAAa,EAAE,QAAQ,CAAC,UAAU;CAClC,YAAY,EAAE,QAAQ,CAAC,UAAU;CACjC,WAAW,EAAE,QAAQ,CAAC,UAAU;CAChC,SAAS,EAAE,KAAK,CAAC,UAAU;CAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU;CAC7B,eAAe,EAAE,KAAK,CAAC,UAAU,MAAM,CAAC,CAAC,UAAU;CACnD,SAAS,cAAc,UAAU;CAEjC,MAAM,mBAAmB,UAAU;CACnC,QAAQ,aAAa,UAAU;CAC/B,QAAQ,aAAa,UAAU;CAC/B,QAAQ,aAAa,UAAU;CAE/B,SAAS,EAAE,MAAM,mBAAmB,CAAC,UAAU;CAC/C,OAAO,YAAY,UAAU;CAC7B,QAAQ,aAAa,UAAU;CAC/B,KAAK,UAAU,UAAU;CACzB,aAAa,kBAAkB,UAAU;CACzC,MAAM,iBAAiB,UAAU;CACjC,SAAS,cAAc,UAAU;CAClC,CAAC;;;;AA4FF,SAAgB,sBAAsB,MAIpC;AACA,KAAI,OAAO,SAAS,SAClB,QAAO;EAAE,OAAO;EAAM,MAAM;EAAM,UAAU;EAAW;AAEzD,QAAO;EACL,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,UAAU,KAAK,YAAY;EAC5B;;;;;AC1RH,MAAM,iBAA4C;CAChD,YAAY;CACZ,WAAW;CACX,QAAQ;CACR,QAAQ,EAAE;CACV,QAAQ,EAAE;CACV,OAAO;EACL,YAAY;EACZ,UAAU;EACX;CACD,KAAK,EAAE;CACP,aAAa,EAAE,SAAS,MAAM;CAC/B;AAED,eAAsB,WAAW,MAAc,QAAQ,KAAK,EAA6B;CACvF,MAAM,aAAa,KAAK,KAAK,kBAAkB;CAE/C,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,SAAS,YAAY,QAAQ;SACvC;AACN,QAAM,IAAI,MAAM,gCAAgC,IAAI,sBAAsB;;CAG5E,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,QAAM,IAAI,MAAM,qCAAqC;;CAGvD,MAAM,SAAS,uBAAuB,UAAU,OAAO;AACvD,KAAI,CAAC,OAAO,SAAS;EACnB,MAAM,SAAS,OAAO,MAAM,OACzB,KAAK,MAAM,OAAO,EAAE,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,UAAU,CACnD,KAAK,KAAK;AACb,QAAM,IAAI,MAAM,uCAAuC,SAAS;;AAGlE,QAAO,cAAc,OAAO,KAAK;;AAGnC,SAAS,cAAc,QAA4C;CAEjE,MAAM,eAAe,OAAO,OAAO,sBAAsB,OAAO,KAAK,GAAG;CAGxE,MAAM,qBAAqB,eACvB,aAAa,UAAU,aAAa,OAClC,aAAa,QACb;EAAE,OAAO,aAAa;EAAO,MAAM,aAAa;EAAM,GACxD;AAEJ,QAAO;EACL,GAAG;EACH,eAAe,OAAO,iBAAiB;EACvC,YAAY,OAAO,cAAc,eAAe,cAAc;EAC9D,WAAW,OAAO,aAAa,eAAe,aAAa;EAC3D,QAAQ,OAAO,UAAU,eAAe,UAAU;EAElD,MAAM,eACF,OAAO,OAAO,SAAS,WACrB,OAAO,OACP;GAAE,OAAO,aAAa;GAAO,MAAM,aAAa;GAAM,UAAU,aAAa;GAAU,GACzF;EACJ,QAAQ;GACN,GAAG,eAAe;GAClB,GAAG,OAAO;GAGV,MACE,OAAO,QAAQ,SACd,gBAAgB,aAAa,aAAa,YACtC,sBAAsB,OAAO,OAC9B,OAAO;GACd;EACD,QAAQ,OAAO,SACX;GACE,GAAG,OAAO;GAGV,MACE,OAAO,OAAO,SACb,gBAAgB,aAAa,aAAa,WACtC,sBAAsB,SACvB;GACP,GACD;EACJ,QAAQ;GACN,GAAG,eAAe;GAClB,GAAG,OAAO;GACV,MAAM,OAAO,QAAQ,QAAQ,wBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,KAAK,OAAO,KAAK;GAC/E;EACD,OAAO;GACL,GAAG,eAAe;GAClB,GAAG,OAAO;GACX;EAED,QAAQ,OAAO,SAAS,EAAE,UAAU,OAAO,OAAO,YAAY,WAAW,GAAG;EAC5E,KAAK;GACH,GAAG,eAAe;GAClB,GAAG,OAAO;GACX;EACD,aAAa;GACX,GAAG,eAAe;GAClB,GAAG,OAAO;GACX;EACD,MAAM,OAAO,OACT;GACE,SAAS,OAAO,KAAK,WAAW;GAChC,iBAAiB,OAAO,KAAK,mBAAmB,OAAO,UAAU;GACjE,WAAW,OAAO,KAAK,aAAa,EAAE;GACtC,QAAQ,OAAO,KAAK,UAAU;GAC/B,GACD;EACJ,SAAS,OAAO,UACZ;GACE,UAAU,OAAO,QAAQ;GACzB,OAAO,OAAO,QAAQ;GACtB,OAAO,OAAO,QAAQ,SAAS;GAC/B,SAAS,OAAO,QAAQ,WAAW;GACnC,aAAa,OAAO,QAAQ,eAAe;GAC5C,GACD;EACL"}
|