openmanual 0.7.1 → 0.8.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 +293 -37
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -4,13 +4,13 @@ import "./chunk-EFTGR6G5.js";
|
|
|
4
4
|
|
|
5
5
|
// src/cli/bin.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
|
-
import { basename, dirname, join as
|
|
7
|
+
import { basename, dirname, join as join7 } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { Command as Command5 } from "commander";
|
|
10
10
|
|
|
11
11
|
// src/cli/commands/build.ts
|
|
12
12
|
import { spawn as spawn2 } from "child_process";
|
|
13
|
-
import { cp, mkdir as
|
|
13
|
+
import { cp, mkdir as mkdir4 } from "fs/promises";
|
|
14
14
|
import { resolve as resolve3 } from "path";
|
|
15
15
|
import { Command } from "commander";
|
|
16
16
|
|
|
@@ -55,6 +55,9 @@ var SearchSchema = z.object({
|
|
|
55
55
|
var MdxSchema = z.object({
|
|
56
56
|
latex: z.boolean().optional()
|
|
57
57
|
});
|
|
58
|
+
var PageActionsSchema = z.object({
|
|
59
|
+
enabled: z.boolean().optional()
|
|
60
|
+
});
|
|
58
61
|
var OpenManualConfigSchema = z.object({
|
|
59
62
|
name: z.string().min(1),
|
|
60
63
|
description: z.string().optional(),
|
|
@@ -68,7 +71,8 @@ var OpenManualConfigSchema = z.object({
|
|
|
68
71
|
sidebar: z.array(SidebarGroupSchema).optional(),
|
|
69
72
|
theme: ThemeSchema.optional(),
|
|
70
73
|
search: SearchSchema.optional(),
|
|
71
|
-
mdx: MdxSchema.optional()
|
|
74
|
+
mdx: MdxSchema.optional(),
|
|
75
|
+
pageActions: PageActionsSchema.optional()
|
|
72
76
|
});
|
|
73
77
|
|
|
74
78
|
// src/core/config/loader.ts
|
|
@@ -85,7 +89,8 @@ var DEFAULT_CONFIG = {
|
|
|
85
89
|
search: {
|
|
86
90
|
enabled: true
|
|
87
91
|
},
|
|
88
|
-
mdx: {}
|
|
92
|
+
mdx: {},
|
|
93
|
+
pageActions: { enabled: true }
|
|
89
94
|
};
|
|
90
95
|
async function loadConfig(cwd = process.cwd()) {
|
|
91
96
|
const configPath = join(cwd, "openmanual.json");
|
|
@@ -137,6 +142,10 @@ function mergeDefaults(config) {
|
|
|
137
142
|
mdx: {
|
|
138
143
|
...DEFAULT_CONFIG.mdx,
|
|
139
144
|
...config.mdx
|
|
145
|
+
},
|
|
146
|
+
pageActions: {
|
|
147
|
+
...DEFAULT_CONFIG.pageActions,
|
|
148
|
+
...config.pageActions
|
|
140
149
|
}
|
|
141
150
|
};
|
|
142
151
|
}
|
|
@@ -351,18 +360,23 @@ function MermaidContent({ chart }: { chart: string }) {
|
|
|
351
360
|
function generateNextConfig(ctx) {
|
|
352
361
|
const { config } = ctx;
|
|
353
362
|
const siteUrl = config.siteUrl ?? "";
|
|
363
|
+
const outputLine = !ctx.dev && siteUrl ? `
|
|
364
|
+
output: 'export',` : "";
|
|
365
|
+
const rewritesBlock = ctx.dev ? `
|
|
366
|
+
async rewrites() {
|
|
367
|
+
return [{ source: '/:path(.+)\\\\.md', destination: '/api/raw/:path' }];
|
|
368
|
+
},` : "";
|
|
354
369
|
return `import { createMDX } from 'fumadocs-mdx/next';
|
|
355
370
|
|
|
356
371
|
const withMDX = createMDX();
|
|
357
372
|
|
|
358
373
|
/** @type {import('next').NextConfig} */
|
|
359
374
|
const config = {
|
|
360
|
-
reactStrictMode: true,${
|
|
361
|
-
output: 'export',` : ""}
|
|
375
|
+
reactStrictMode: true,${outputLine}
|
|
362
376
|
serverExternalPackages: ['mermaid'],
|
|
363
377
|
images: {
|
|
364
378
|
unoptimized: true,
|
|
365
|
-
}
|
|
379
|
+
},${rewritesBlock}
|
|
366
380
|
};
|
|
367
381
|
|
|
368
382
|
export default withMDX(config);
|
|
@@ -391,7 +405,8 @@ function generatePackageJson(_ctx) {
|
|
|
391
405
|
postcss: "^8.5.8",
|
|
392
406
|
react: "^19.1.0",
|
|
393
407
|
"react-dom": "^19.1.0",
|
|
394
|
-
tailwindcss: "^4.1.15"
|
|
408
|
+
tailwindcss: "^4.1.15",
|
|
409
|
+
zod: "^4.0.0"
|
|
395
410
|
}
|
|
396
411
|
};
|
|
397
412
|
return `${JSON.stringify(pkg, null, 2)}
|
|
@@ -412,6 +427,7 @@ function buildAllowedSlugs(config) {
|
|
|
412
427
|
}
|
|
413
428
|
function generatePage(_ctx) {
|
|
414
429
|
const isStrict = _ctx.config.contentPolicy !== "all";
|
|
430
|
+
const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;
|
|
415
431
|
const allowedSlugsSnippet = isStrict ? `
|
|
416
432
|
const allowedSlugs = new Set(${JSON.stringify([...buildAllowedSlugs(_ctx.config)])});
|
|
417
433
|
|
|
@@ -442,6 +458,19 @@ export function generateStaticParams() {
|
|
|
442
458
|
}
|
|
443
459
|
return params;
|
|
444
460
|
}`;
|
|
461
|
+
const pageActionsImport = pageActionsEnabled ? "\nimport { PageActions } from '@/components/page-actions';" : "";
|
|
462
|
+
const pageTitleArea = pageActionsEnabled ? ` <div className="flex items-start justify-between gap-4">
|
|
463
|
+
<div>
|
|
464
|
+
<DocsTitle>{page.data.title}</DocsTitle>
|
|
465
|
+
{page.data.description && (
|
|
466
|
+
<DocsDescription>{page.data.description}</DocsDescription>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
<PageActions />
|
|
470
|
+
</div>` : ` <DocsTitle>{page.data.title}</DocsTitle>
|
|
471
|
+
{page.data.description && (
|
|
472
|
+
<DocsDescription>{page.data.description}</DocsDescription>
|
|
473
|
+
)}`;
|
|
445
474
|
return `import { source } from '@/lib/source';
|
|
446
475
|
import { notFound } from 'next/navigation';
|
|
447
476
|
import { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';
|
|
@@ -451,7 +480,7 @@ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
|
|
|
451
480
|
import { Files, File, Folder } from 'fumadocs-ui/components/files';
|
|
452
481
|
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
|
453
482
|
import { TypeTable } from 'fumadocs-ui/components/type-table';
|
|
454
|
-
import { Mermaid } from '@/components/mermaid'
|
|
483
|
+
import { Mermaid } from '@/components/mermaid';${pageActionsImport}
|
|
455
484
|
${allowedSlugsSnippet}
|
|
456
485
|
export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
|
|
457
486
|
const { slug } = await params;
|
|
@@ -465,11 +494,8 @@ ${filterInPage}
|
|
|
465
494
|
|
|
466
495
|
return (
|
|
467
496
|
<DocsPage toc={page.data.toc}>
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
<DocsDescription>{page.data.description}</DocsDescription>
|
|
471
|
-
)}
|
|
472
|
-
<DocsBody>
|
|
497
|
+
${pageTitleArea}
|
|
498
|
+
<DocsBody data-content-area>
|
|
473
499
|
<MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />
|
|
474
500
|
</DocsBody>
|
|
475
501
|
</DocsPage>
|
|
@@ -479,6 +505,177 @@ ${filterInStaticParams}
|
|
|
479
505
|
`;
|
|
480
506
|
}
|
|
481
507
|
|
|
508
|
+
// src/core/generator/page-actions-component.ts
|
|
509
|
+
function generatePageActionsComponent() {
|
|
510
|
+
return `'use client';
|
|
511
|
+
|
|
512
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
513
|
+
|
|
514
|
+
function cn(...classes: (string | undefined | false)[]) {
|
|
515
|
+
return classes.filter(Boolean).join(' ');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function getPageText(): string {
|
|
519
|
+
const article =
|
|
520
|
+
document.querySelector<HTMLElement>('[data-content-area]') ??
|
|
521
|
+
document.querySelector<HTMLElement>('article');
|
|
522
|
+
return article ? article.innerText : '';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function PageActions() {
|
|
526
|
+
const [copied, setCopied] = useState(false);
|
|
527
|
+
const [open, setOpen] = useState(false);
|
|
528
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
529
|
+
|
|
530
|
+
const handleClickOutside = useCallback((e: MouseEvent) => {
|
|
531
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
532
|
+
setOpen(false);
|
|
533
|
+
}
|
|
534
|
+
}, []);
|
|
535
|
+
|
|
536
|
+
useEffect(() => {
|
|
537
|
+
if (open) {
|
|
538
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
539
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
540
|
+
}
|
|
541
|
+
}, [open, handleClickOutside]);
|
|
542
|
+
|
|
543
|
+
const handleCopyFullText = useCallback(async () => {
|
|
544
|
+
const text = getPageText();
|
|
545
|
+
if (!text) return;
|
|
546
|
+
await navigator.clipboard.writeText(text);
|
|
547
|
+
setCopied(true);
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
setCopied(false);
|
|
550
|
+
setOpen(false);
|
|
551
|
+
}, 2000);
|
|
552
|
+
}, []);
|
|
553
|
+
|
|
554
|
+
const handleViewMarkdown = useCallback(() => {
|
|
555
|
+
const path = window.location.pathname;
|
|
556
|
+
const mdUrl = path === '/' ? '/index.md' : \`\${path}.md\`;
|
|
557
|
+
window.open(mdUrl, '_blank');
|
|
558
|
+
setOpen(false);
|
|
559
|
+
}, []);
|
|
560
|
+
|
|
561
|
+
const CopyIcon = (
|
|
562
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
563
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
564
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
565
|
+
</svg>
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const CheckIcon = (
|
|
569
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
570
|
+
<polyline points="20 6 9 17 4 12" />
|
|
571
|
+
</svg>
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const ChevronDownIcon = (
|
|
575
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
576
|
+
<path d="m6 9 6 6 6-6" />
|
|
577
|
+
</svg>
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const ChevronUpIcon = (
|
|
581
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
582
|
+
<path d="m18 15-6-6-6 6" />
|
|
583
|
+
</svg>
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
const FileTextIcon = (
|
|
587
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
588
|
+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
|
589
|
+
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
590
|
+
<path d="M10 9H8" />
|
|
591
|
+
<path d="M16 13H8" />
|
|
592
|
+
<path d="M16 17H8" />
|
|
593
|
+
</svg>
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
return (
|
|
597
|
+
<div ref={menuRef} className="relative">
|
|
598
|
+
<div className={cn(
|
|
599
|
+
'inline-flex items-stretch rounded-xl border border-fd-border',
|
|
600
|
+
'overflow-hidden',
|
|
601
|
+
)}>
|
|
602
|
+
<button
|
|
603
|
+
type="button"
|
|
604
|
+
onClick={handleCopyFullText}
|
|
605
|
+
className={cn(
|
|
606
|
+
'inline-flex items-center gap-1.5 px-3 py-1.5 text-sm cursor-pointer',
|
|
607
|
+
'text-fd-muted-foreground hover:text-fd-foreground',
|
|
608
|
+
'hover:bg-fd-secondary/80 transition-colors',
|
|
609
|
+
)}
|
|
610
|
+
>
|
|
611
|
+
{copied ? CheckIcon : CopyIcon}
|
|
612
|
+
{copied ? 'Copied!' : 'Copy page'}
|
|
613
|
+
</button>
|
|
614
|
+
<div className="w-px bg-fd-border" />
|
|
615
|
+
<button
|
|
616
|
+
type="button"
|
|
617
|
+
onClick={() => setOpen(!open)}
|
|
618
|
+
className={cn(
|
|
619
|
+
'inline-flex items-center justify-center px-2 py-1.5 cursor-pointer',
|
|
620
|
+
'text-fd-muted-foreground hover:text-fd-foreground',
|
|
621
|
+
'hover:bg-fd-secondary/80 transition-colors',
|
|
622
|
+
)}
|
|
623
|
+
aria-label={open ? '\u6536\u8D77\u83DC\u5355' : '\u5C55\u5F00\u83DC\u5355'}
|
|
624
|
+
>
|
|
625
|
+
{open ? ChevronUpIcon : ChevronDownIcon}
|
|
626
|
+
</button>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{open && (
|
|
630
|
+
<div
|
|
631
|
+
className={cn(
|
|
632
|
+
'absolute right-0 top-full mt-1 z-50',
|
|
633
|
+
'min-w-[280px] rounded-xl border border-fd-border',
|
|
634
|
+
'bg-fd-popover p-1 shadow-md',
|
|
635
|
+
)}
|
|
636
|
+
>
|
|
637
|
+
<button
|
|
638
|
+
type="button"
|
|
639
|
+
onClick={handleCopyFullText}
|
|
640
|
+
className={cn(
|
|
641
|
+
'flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-sm cursor-pointer',
|
|
642
|
+
'text-fd-popover-foreground',
|
|
643
|
+
'hover:bg-fd-secondary/80 transition-colors',
|
|
644
|
+
)}
|
|
645
|
+
>
|
|
646
|
+
<span className="mt-0.5 text-fd-muted-foreground">
|
|
647
|
+
{copied ? CheckIcon : CopyIcon}
|
|
648
|
+
</span>
|
|
649
|
+
<span className="flex flex-col items-start">
|
|
650
|
+
<span className="font-medium">{copied ? '\u5DF2\u590D\u5236' : '\u590D\u5236\u5168\u6587'}</span>
|
|
651
|
+
<span className="text-xs text-fd-muted-foreground">\u590D\u5236\u9875\u9762\u5185\u5BB9\uFF0C\u9002\u5408 AI \u5DE5\u5177\u4F7F\u7528</span>
|
|
652
|
+
</span>
|
|
653
|
+
</button>
|
|
654
|
+
<button
|
|
655
|
+
type="button"
|
|
656
|
+
onClick={handleViewMarkdown}
|
|
657
|
+
className={cn(
|
|
658
|
+
'flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-sm cursor-pointer',
|
|
659
|
+
'text-fd-popover-foreground',
|
|
660
|
+
'hover:bg-fd-secondary/80 transition-colors',
|
|
661
|
+
)}
|
|
662
|
+
>
|
|
663
|
+
<span className="mt-0.5 text-fd-muted-foreground">
|
|
664
|
+
{FileTextIcon}
|
|
665
|
+
</span>
|
|
666
|
+
<span className="flex flex-col items-start">
|
|
667
|
+
<span className="font-medium">\u67E5\u770B\u539F\u6587</span>
|
|
668
|
+
<span className="text-xs text-fd-muted-foreground">\u67E5\u770B\u539F\u59CB Markdown \u6E90\u6587\u4EF6</span>
|
|
669
|
+
</span>
|
|
670
|
+
</button>
|
|
671
|
+
</div>
|
|
672
|
+
)}
|
|
673
|
+
</div>
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
|
|
482
679
|
// src/core/generator/postcss-config.ts
|
|
483
680
|
function generatePostcssConfig() {
|
|
484
681
|
return `/** @type {import('postcss-load-config').Config} */
|
|
@@ -514,6 +711,34 @@ export function Provider({ children }: { children: ReactNode }) {
|
|
|
514
711
|
`;
|
|
515
712
|
}
|
|
516
713
|
|
|
714
|
+
// src/core/generator/raw-content-route.ts
|
|
715
|
+
function generateRawContentRoute() {
|
|
716
|
+
return `import { readFile } from 'node:fs/promises';
|
|
717
|
+
import { join } from 'node:path';
|
|
718
|
+
import { NextResponse } from 'next/server';
|
|
719
|
+
|
|
720
|
+
export async function GET(
|
|
721
|
+
_request: Request,
|
|
722
|
+
{ params }: { params: Promise<{ path: string[] }> },
|
|
723
|
+
) {
|
|
724
|
+
const { path: segments } = await params;
|
|
725
|
+
const slug = segments.join('/');
|
|
726
|
+
for (const ext of ['.mdx', '.md']) {
|
|
727
|
+
try {
|
|
728
|
+
const filePath = join(process.cwd(), 'content', \`\${slug}\${ext}\`);
|
|
729
|
+
const content = await readFile(filePath, 'utf-8');
|
|
730
|
+
return new NextResponse(content, {
|
|
731
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
732
|
+
});
|
|
733
|
+
} catch {
|
|
734
|
+
/* try next extension */
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return new NextResponse('Not found', { status: 404 });
|
|
738
|
+
}
|
|
739
|
+
`;
|
|
740
|
+
}
|
|
741
|
+
|
|
517
742
|
// src/core/generator/source-config.ts
|
|
518
743
|
function buildTitleMap(config) {
|
|
519
744
|
const map = {};
|
|
@@ -685,6 +910,12 @@ async function generateAll(ctx) {
|
|
|
685
910
|
path: "components/mermaid.tsx",
|
|
686
911
|
content: generateMermaidComponent()
|
|
687
912
|
},
|
|
913
|
+
{
|
|
914
|
+
path: "components/page-actions.tsx",
|
|
915
|
+
content: generatePageActionsComponent()
|
|
916
|
+
},
|
|
917
|
+
// 仅在 dev 模式生成 API 路由(生产构建中 output: 'export' 不兼容 API 路由)
|
|
918
|
+
...ctx.dev ? [{ path: "app/api/raw/[...path]/route.ts", content: generateRawContentRoute() }] : [],
|
|
688
919
|
{
|
|
689
920
|
path: "app/layout.tsx",
|
|
690
921
|
content: generateRootLayout()
|
|
@@ -891,6 +1122,28 @@ async function generateMetaFiles(ctx) {
|
|
|
891
1122
|
}
|
|
892
1123
|
}
|
|
893
1124
|
|
|
1125
|
+
// src/utils/copy-raw-markdown.ts
|
|
1126
|
+
import { copyFile, mkdir as mkdir2, readdir, stat } from "fs/promises";
|
|
1127
|
+
import { join as join3, parse } from "path";
|
|
1128
|
+
async function copyRawMarkdown(contentDir, targetDir) {
|
|
1129
|
+
await mkdir2(targetDir, { recursive: true });
|
|
1130
|
+
const entries = await readdir(contentDir);
|
|
1131
|
+
for (const entry of entries) {
|
|
1132
|
+
const srcPath = join3(contentDir, entry);
|
|
1133
|
+
const srcStat = await stat(srcPath);
|
|
1134
|
+
if (srcStat.isDirectory()) {
|
|
1135
|
+
await copyRawMarkdown(srcPath, join3(targetDir, entry));
|
|
1136
|
+
} else {
|
|
1137
|
+
const ext = parse(entry).ext.toLowerCase();
|
|
1138
|
+
if (ext === ".mdx" || ext === ".md") {
|
|
1139
|
+
const { name } = parse(entry);
|
|
1140
|
+
const destPath = join3(targetDir, `${name}.md`);
|
|
1141
|
+
await copyFile(srcPath, destPath);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
894
1147
|
// src/utils/install-deps.ts
|
|
895
1148
|
import { spawn } from "child_process";
|
|
896
1149
|
import { existsSync } from "fs";
|
|
@@ -964,20 +1217,20 @@ var logger = {
|
|
|
964
1217
|
|
|
965
1218
|
// src/utils/temp-dir.ts
|
|
966
1219
|
import { existsSync as existsSync2 } from "fs";
|
|
967
|
-
import { lstat, mkdir as
|
|
968
|
-
import { join as
|
|
1220
|
+
import { lstat, mkdir as mkdir3, rm, symlink } from "fs/promises";
|
|
1221
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
969
1222
|
var TEMP_DIR_NAME = ".openmanual";
|
|
970
1223
|
function getTempDir(cwd) {
|
|
971
|
-
return
|
|
1224
|
+
return join4(cwd, TEMP_DIR_NAME);
|
|
972
1225
|
}
|
|
973
1226
|
function getAppDir(cwd) {
|
|
974
|
-
return
|
|
1227
|
+
return join4(getTempDir(cwd), "app");
|
|
975
1228
|
}
|
|
976
1229
|
async function ensureTempDir(cwd) {
|
|
977
1230
|
const tempDir = getTempDir(cwd);
|
|
978
1231
|
const appDir = getAppDir(cwd);
|
|
979
|
-
await
|
|
980
|
-
await
|
|
1232
|
+
await mkdir3(tempDir, { recursive: true });
|
|
1233
|
+
await mkdir3(join4(appDir, "app"), { recursive: true });
|
|
981
1234
|
return tempDir;
|
|
982
1235
|
}
|
|
983
1236
|
async function cleanTempDir(cwd) {
|
|
@@ -1017,8 +1270,8 @@ var buildCommand = new Command("build").description("\u6784\u5EFA\u9759\u6001\u7
|
|
|
1017
1270
|
await createSymlink(contentDir, resolve3(appDir, "content"));
|
|
1018
1271
|
const publicDir = resolve3(cwd, "public");
|
|
1019
1272
|
try {
|
|
1020
|
-
const { stat } = await import("fs/promises");
|
|
1021
|
-
await
|
|
1273
|
+
const { stat: stat2 } = await import("fs/promises");
|
|
1274
|
+
await stat2(publicDir);
|
|
1022
1275
|
await createSymlink(publicDir, resolve3(appDir, "public"));
|
|
1023
1276
|
} catch {
|
|
1024
1277
|
}
|
|
@@ -1041,7 +1294,7 @@ var buildCommand = new Command("build").description("\u6784\u5EFA\u9759\u6001\u7
|
|
|
1041
1294
|
});
|
|
1042
1295
|
});
|
|
1043
1296
|
const outputDir = resolve3(cwd, config.outputDir ?? "dist");
|
|
1044
|
-
await
|
|
1297
|
+
await mkdir4(outputDir, { recursive: true });
|
|
1045
1298
|
const nextOutput = resolve3(appDir, "out");
|
|
1046
1299
|
try {
|
|
1047
1300
|
await cp(nextOutput, outputDir, { recursive: true });
|
|
@@ -1049,6 +1302,8 @@ var buildCommand = new Command("build").description("\u6784\u5EFA\u9759\u6001\u7
|
|
|
1049
1302
|
} catch {
|
|
1050
1303
|
logger.warn('\u672A\u627E\u5230\u9759\u6001\u5BFC\u51FA\u4EA7\u7269\uFF0C\u8BF7\u68C0\u67E5 next.config.mjs \u4E2D output: "export" \u914D\u7F6E');
|
|
1051
1304
|
}
|
|
1305
|
+
logger.step("\u590D\u5236\u539F\u59CB Markdown \u6587\u4EF6...");
|
|
1306
|
+
await copyRawMarkdown(contentDir, outputDir);
|
|
1052
1307
|
logger.step("\u6E05\u7406\u4E34\u65F6\u6587\u4EF6...");
|
|
1053
1308
|
await cleanTempDir(cwd);
|
|
1054
1309
|
logger.success("\u6784\u5EFA\u5B8C\u6210\uFF01");
|
|
@@ -1065,8 +1320,8 @@ import { extname, resolve as resolve4 } from "path";
|
|
|
1065
1320
|
import { Command as Command2 } from "commander";
|
|
1066
1321
|
|
|
1067
1322
|
// src/utils/check-code-langs.ts
|
|
1068
|
-
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
1069
|
-
import { join as
|
|
1323
|
+
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
1324
|
+
import { join as join5, relative, sep } from "path";
|
|
1070
1325
|
async function checkCodeLangs(contentDir) {
|
|
1071
1326
|
const { bundledLanguages } = await import("./dist-IK4UEFEW.js");
|
|
1072
1327
|
const supportedLangs = new Set(Object.keys(bundledLanguages));
|
|
@@ -1100,10 +1355,10 @@ async function checkCodeLangs(contentDir) {
|
|
|
1100
1355
|
return results;
|
|
1101
1356
|
}
|
|
1102
1357
|
async function collectMdFiles(dir) {
|
|
1103
|
-
const entries = await
|
|
1358
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1104
1359
|
const files = [];
|
|
1105
1360
|
for (const entry of entries) {
|
|
1106
|
-
const fullPath =
|
|
1361
|
+
const fullPath = join5(dir, entry.name);
|
|
1107
1362
|
if (entry.isDirectory()) {
|
|
1108
1363
|
files.push(...await collectMdFiles(fullPath));
|
|
1109
1364
|
} else if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) {
|
|
@@ -1127,7 +1382,8 @@ var devCommand = new Command2("dev").description("\u542F\u52A8\u5F00\u53D1\u670D
|
|
|
1127
1382
|
config,
|
|
1128
1383
|
projectDir: cwd,
|
|
1129
1384
|
appDir,
|
|
1130
|
-
contentDir: config.contentDir ?? "content"
|
|
1385
|
+
contentDir: config.contentDir ?? "content",
|
|
1386
|
+
dev: true
|
|
1131
1387
|
};
|
|
1132
1388
|
await generateAll(ctx);
|
|
1133
1389
|
try {
|
|
@@ -1144,8 +1400,8 @@ var devCommand = new Command2("dev").description("\u542F\u52A8\u5F00\u53D1\u670D
|
|
|
1144
1400
|
await createSymlink(contentDir, resolve4(appDir, "content"));
|
|
1145
1401
|
const publicDir = resolve4(cwd, "public");
|
|
1146
1402
|
try {
|
|
1147
|
-
const { stat } = await import("fs/promises");
|
|
1148
|
-
await
|
|
1403
|
+
const { stat: stat2 } = await import("fs/promises");
|
|
1404
|
+
await stat2(publicDir);
|
|
1149
1405
|
await createSymlink(publicDir, resolve4(appDir, "public"));
|
|
1150
1406
|
} catch {
|
|
1151
1407
|
}
|
|
@@ -1285,7 +1541,7 @@ var previewCommand = new Command3("preview").description("\u9884\u89C8\u6784\u5E
|
|
|
1285
1541
|
|
|
1286
1542
|
// src/cli/commands/regenerate.ts
|
|
1287
1543
|
import { rm as rm2 } from "fs/promises";
|
|
1288
|
-
import { join as
|
|
1544
|
+
import { join as join6, resolve as resolve6 } from "path";
|
|
1289
1545
|
import { Command as Command4 } from "commander";
|
|
1290
1546
|
var regenerateCommand = new Command4("_regenerate").description("\u5185\u90E8\u547D\u4EE4\uFF1A\u91CD\u65B0\u751F\u6210\u6587\u4EF6").helpOption(false).option("--cwd <path>", "\u9879\u76EE\u76EE\u5F55").action(async (options) => {
|
|
1291
1547
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -1311,14 +1567,14 @@ var regenerateCommand = new Command4("_regenerate").description("\u5185\u90E8\u5
|
|
|
1311
1567
|
"postcss.config.mjs"
|
|
1312
1568
|
];
|
|
1313
1569
|
for (const entry of entriesToClean) {
|
|
1314
|
-
await rm2(
|
|
1570
|
+
await rm2(join6(appDir, entry), { recursive: true, force: true });
|
|
1315
1571
|
}
|
|
1316
1572
|
await generateAll(ctx);
|
|
1317
1573
|
await createSymlink(contentDir, resolve6(appDir, "content"));
|
|
1318
1574
|
const publicDir = resolve6(cwd, "public");
|
|
1319
1575
|
try {
|
|
1320
|
-
const { stat } = await import("fs/promises");
|
|
1321
|
-
await
|
|
1576
|
+
const { stat: stat2 } = await import("fs/promises");
|
|
1577
|
+
await stat2(publicDir);
|
|
1322
1578
|
await createSymlink(publicDir, resolve6(appDir, "public"));
|
|
1323
1579
|
} catch {
|
|
1324
1580
|
}
|
|
@@ -1333,17 +1589,17 @@ var regenerateCommand = new Command4("_regenerate").description("\u5185\u90E8\u5
|
|
|
1333
1589
|
// src/cli/bin.ts
|
|
1334
1590
|
function getVersion() {
|
|
1335
1591
|
if (true) {
|
|
1336
|
-
return "0.
|
|
1592
|
+
return "0.8.0";
|
|
1337
1593
|
}
|
|
1338
1594
|
try {
|
|
1339
1595
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1340
|
-
const pkgPath =
|
|
1596
|
+
const pkgPath = join7(__dirname, "..", "package.json");
|
|
1341
1597
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1342
1598
|
return pkg.version;
|
|
1343
1599
|
} catch {
|
|
1344
1600
|
try {
|
|
1345
1601
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1346
|
-
const pkgPath =
|
|
1602
|
+
const pkgPath = join7(__dirname, "..", "..", "package.json");
|
|
1347
1603
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1348
1604
|
return pkg.version;
|
|
1349
1605
|
} catch {
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/bin.ts","../src/cli/commands/build.ts","../src/core/config/loader.ts","../src/core/config/schema.ts","../src/core/generator/index.ts","../src/core/generator/global-css.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/next-config.ts","../src/core/generator/package-json.ts","../src/core/generator/page.ts","../src/core/generator/postcss-config.ts","../src/core/generator/provider.ts","../src/core/generator/source-config.ts","../src/core/generator/tsconfig.ts","../src/utils/install-deps.ts","../src/utils/logger.ts","../src/utils/temp-dir.ts","../src/cli/commands/dev.ts","../src/utils/check-code-langs.ts","../src/cli/commands/preview.ts","../src/cli/commands/regenerate.ts"],"sourcesContent":["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","import { spawn } from 'node:child_process';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\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 const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n };\n\n await generateAll(ctx);\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.step('构建静态站点...');\n const buildResult = spawn('npx', ['next', 'build'], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n await new Promise<void>((resolve, reject) => {\n buildResult.on('error', reject);\n buildResult.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Build failed with code ${code}`));\n }\n });\n });\n\n // Copy output to user's output dir\n const outputDir = resolve(cwd, config.outputDir ?? 'dist');\n await mkdir(outputDir, { recursive: true });\n\n const nextOutput = resolve(appDir, 'out');\n try {\n await cp(nextOutput, outputDir, { recursive: true });\n logger.success(`静态站点已输出到: ${outputDir}`);\n } catch {\n // If no 'out' dir, check .next/static\n logger.warn('未找到静态导出产物,请检查 next.config.mjs 中 output: \"export\" 配置');\n }\n\n logger.step('清理临时文件...');\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 { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n };\n}\n","import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n","import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateNextConfig } from './next-config.js';\nimport { generatePackageJson } from './package-json.js';\nimport { generatePage } from './page.js';\nimport { generatePostcssConfig } from './postcss-config.js';\nimport { generateProvider } from './provider.js';\nimport { generateSourceConfig } from './source-config.js';\nimport { generateTsconfig } from './tsconfig.js';\n\nexport interface GenerateContext {\n config: OpenManualConfig;\n /** Absolute path to user's project root */\n projectDir: string;\n /** Absolute path to .openmanual/app */\n appDir: string;\n /** Content directory relative to project root */\n contentDir: string;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const files: Array<{ path: string; content: string }> = [\n {\n path: 'source.config.ts',\n content: generateSourceConfig(ctx),\n },\n {\n path: 'next.config.mjs',\n content: generateNextConfig(ctx),\n },\n {\n path: 'global.css',\n content: generateGlobalCss(ctx),\n },\n {\n path: 'package.json',\n content: generatePackageJson(ctx),\n },\n {\n path: 'tsconfig.json',\n content: generateTsconfig(),\n },\n {\n path: 'postcss.config.mjs',\n content: generatePostcssConfig(),\n },\n {\n path: 'lib/source.ts',\n content: generateLibSource(),\n },\n {\n path: 'lib/layout.tsx',\n content: generateLayout(ctx),\n },\n {\n path: 'components/mermaid.tsx',\n content: generateMermaidComponent(),\n },\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n\n for (const file of files) {\n const fullPath = join(ctx.appDir, file.path);\n const dir = join(fullPath, '..');\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, file.content, 'utf-8');\n }\n\n // Generate logo SVG in public/ when logo is an image path\n const logo = ctx.config.navbar?.logo;\n if (logo && typeof logo === 'string' && isImagePath(logo)) {\n await ensureLogoFile(ctx, logo, 'light');\n } else if (logo && typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (isImagePath(light)) {\n await ensureLogoFile(ctx, light, 'light');\n }\n if (isImagePath(dark) && dark !== light) {\n await ensureLogoFile(ctx, dark, 'dark');\n }\n }\n\n // Generate meta.json for each sidebar group directory\n await generateMetaFiles(ctx);\n}\n\nfunction generateRootLayout(): string {\n return `import { Provider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <html lang=\"zh\" suppressHydrationWarning>\n <body className=\"flex flex-col min-h-screen\">\n <Provider>{children}</Provider>\n </body>\n </html>\n );\n}\n`;\n}\n\nfunction generateDocsLayout(ctx: GenerateContext): string {\n const { config } = ctx;\n const githubLink = config.navbar?.github ?? '';\n const navLinks = config.navbar?.links ?? [];\n const footerText = config.footer?.text ?? '';\n\n const linksArray = navLinks.map((l) => ({\n text: l.label,\n url: l.href,\n external: true,\n }));\n\n const githubLine = githubLink ? `\\n github: '${githubLink}',` : '';\n\n const linksLine = linksArray.length > 0 ? `\\n links: ${JSON.stringify(linksArray)},` : '';\n\n const footerLine = footerText\n ? `\\n footer: { children: '${footerText.replace(/'/g, \"\\\\'\")}' },`\n : '';\n\n // Build sidebar config for tree restructuring (only needed fields)\n const sidebar = config.sidebar;\n const sidebarSnippet =\n sidebar && sidebar.length > 0\n ? `\\nconst sidebarConfig = ${JSON.stringify(\n sidebar.map((g) => ({\n group: g.group,\n collapsed: g.collapsed,\n pages: g.pages.map((p) => ({ slug: p.slug })),\n })),\n null,\n 2\n )} as const;\n\nfunction slugToUrl(slug: string): string {\n return slug === 'index' ? '/' : \\`/\\${slug}\\`;\n}\n\nfunction restructureTree(tree: PageTree.Root): PageTree.Root {\n const consumed = new Set<number>();\n const newChildren: PageTree.Node[] = [];\n\n for (const group of sidebarConfig) {\n const isRootGroup = group.pages.every((p) => !p.slug.includes('/'));\n\n if (isRootGroup) {\n const folderChildren: PageTree.Node[] = [];\n for (const page of group.pages) {\n const url = slugToUrl(page.slug);\n const idx = (tree.children ?? []).findIndex(\n (c, i) => !consumed.has(i) && c.type === 'page' && c.url === url\n );\n if (idx >= 0) {\n folderChildren.push(tree.children![idx]);\n consumed.add(idx);\n }\n }\n if (folderChildren.length > 0) {\n newChildren.push({\n type: 'folder',\n name: group.group,\n defaultOpen: group.collapsed !== true,\n children: folderChildren,\n });\n }\n } else {\n const dirPrefix = group.pages.find((p) => p.slug.includes('/'))?.slug.split('/')[0];\n if (dirPrefix) {\n const idx = (tree.children ?? []).findIndex(\n (child, i) =>\n !consumed.has(i) &&\n child.type === 'folder' &&\n child.children?.some(\n (c) => c.type === 'page' && c.url?.startsWith(\\`/\\${dirPrefix}/\\`)\n )\n );\n if (idx >= 0) {\n consumed.add(idx);\n newChildren.push({\n ...(tree.children![idx] as PageTree.Folder),\n name: group.group,\n defaultOpen: group.collapsed !== true,\n });\n }\n }\n }\n }\n\n for (let i = 0; i < (tree.children ?? []).length; i++) {\n if (!consumed.has(i)) {\n newChildren.push(tree.children![i]);\n }\n }\n\n return { ...tree, children: newChildren };\n}\n`\n : '';\n\n const treeLine = sidebarSnippet\n ? 'tree: restructureTree(source.getPageTree()),'\n : 'tree: source.getPageTree(),';\n\n const pageTreeImport = sidebarSnippet\n ? \"\\nimport type * as PageTree from 'fumadocs-core/page-tree';\"\n : '';\n\n return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout';\nimport { source } from '@/lib/source';\nimport type { ReactNode } from 'react';${pageTreeImport}\n${sidebarSnippet}\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${githubLine}${linksLine}${footerLine}\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\nexport function generateOpenManualLogoSvg(\n name: string,\n variant: 'light' | 'dark' = 'light'\n): string {\n const textColor = variant === 'dark' ? '#E8E0D4' : '#000000';\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 190 32\" width=\"190\" height=\"32\">\n <text x=\"0\" y=\"25\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\" font-size=\"32\" font-weight=\"700\">\n <tspan fill=\"#2B7A4B\" font-size=\"34\">${name.charAt(0)}</tspan><tspan fill=\"${textColor}\">${name.slice(1)}</tspan>\n </text>\n</svg>\n`;\n}\n\nasync function ensureLogoFile(\n ctx: GenerateContext,\n logoPath: string,\n variant: 'light' | 'dark'\n): Promise<void> {\n const userLogoPath = join(ctx.projectDir, 'public', logoPath.replace(/^\\//, ''));\n try {\n await access(userLogoPath);\n } catch {\n const publicDir = join(ctx.appDir, 'public');\n await mkdir(publicDir, { recursive: true });\n const fullPath = join(publicDir, logoPath.replace(/^\\//, ''));\n await mkdir(join(fullPath, '..'), { recursive: true });\n await writeFile(fullPath, generateOpenManualLogoSvg(ctx.config.name, variant), 'utf-8');\n }\n}\n\n/**\n * Generate meta.json for each sidebar group directory so that\n * fumadocs displays the configured Chinese group name instead of\n * auto-capitalizing the English directory name.\n */\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\n const sidebar = ctx.config.sidebar;\n if (!sidebar || sidebar.length === 0) return;\n\n const contentAbsDir = join(ctx.projectDir, ctx.contentDir);\n\n for (const group of sidebar) {\n // Extract directory prefix from the first page slug that contains \"/\"\n const dirPrefix = group.pages\n .map((p) => p.slug)\n .find((slug) => slug.includes('/'))\n ?.split('/')[0];\n\n if (!dirPrefix) continue; // Root-level pages, no meta.json needed\n\n const dirPath = join(contentAbsDir, dirPrefix);\n const metaPath = join(dirPath, 'meta.json');\n\n // Skip if meta.json already exists\n try {\n await access(metaPath);\n continue;\n } catch {\n // File doesn't exist, proceed to create it\n }\n\n await mkdir(dirPath, { recursive: true });\n await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\\n`, 'utf-8');\n }\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateGlobalCss(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const primaryHue = config.theme?.primaryHue ?? 213;\n const darkMode = config.theme?.darkMode ?? true;\n\n const darkBlock = darkMode\n ? `\n.dark {\n --primary-hue: ${primaryHue};\n\n /* 温暖的深色皮革背景 */\n --color-fd-background: hsl(30, 18%, 10%);\n --color-fd-foreground: hsl(35, 15%, 90%);\n --color-fd-muted: hsl(30, 14%, 14%);\n --color-fd-muted-foreground: hsla(30, 10%, 65%, 0.8);\n --color-fd-popover: hsl(30, 16%, 13%);\n --color-fd-popover-foreground: hsl(35, 12%, 87%);\n --color-fd-card: hsl(30, 15%, 12%);\n --color-fd-card-foreground: hsl(35, 15%, 93%);\n --color-fd-border: hsla(30, 12%, 35%, 25%);\n --color-fd-primary: hsl(35, 20%, 92%);\n --color-fd-primary-foreground: hsl(30, 25%, 10%);\n --color-fd-secondary: hsl(30, 12%, 16%);\n --color-fd-secondary-foreground: hsl(35, 10%, 88%);\n --color-fd-accent: hsla(30, 15%, 30%, 35%);\n --color-fd-accent-foreground: hsl(35, 12%, 88%);\n --color-fd-ring: hsl(30, 30%, 50%);\n --color-fd-overlay: hsla(25, 20%, 5%, 0.5);\n}\n\n.dark body {\n background: linear-gradient(hsla(30, 30%, 15%, 0.4), transparent 20rem, transparent);\n}\n`\n : '';\n\n return `@import 'tailwindcss';\n@import 'fumadocs-ui/style.css';\n@custom-variant dark (&:is(.dark, .dark *));\n\n:root {\n --primary-hue: ${primaryHue};\n\n /* 护眼暖色阅读背景 */\n --color-fd-background: hsl(40, 22%, 96.5%); /* #faf9f6 纸张白 */\n --color-fd-foreground: hsl(0, 0%, 17.3%); /* #2c2c2c 柔黑 */\n --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */\n --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */\n --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */\n}\n${darkBlock}`;\n}\n","import type { LogoConfig, OpenManualConfig } from '../config/schema.js';\n\nconst IMAGE_EXTENSIONS = ['.svg', '.png', '.jpg', '.jpeg', '.webp'];\n\nexport function isImagePath(value: string): boolean {\n if (value.startsWith('/')) return true;\n return IMAGE_EXTENSIONS.some((ext) => value.toLowerCase().endsWith(ext));\n}\n\nexport function resolveLogoPaths(logo: LogoConfig): { light: string; dark: string } {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo };\n }\n return { light: logo.light, dark: logo.dark };\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const logo = config.navbar?.logo ?? config.name;\n\n // String logo that is an image path — backward compatible single image\n if (typeof logo === 'string' && isImagePath(logo)) {\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${logo}\" alt=\"${config.name}\" style={{ height: 28 }} />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Object logo { light, dark }\n if (typeof logo === 'object') {\n const { light, dark } = logo;\n\n // Same path — treat as single image\n if (light === dark) {\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${light}\" alt=\"${config.name}\" style={{ height: 28 }} />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Different paths — generate two images with dark mode toggle\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${light}\" alt=\"${config.name}\" style={{ height: 28 }} className=\"dark:hidden\" />\n <img src=\"${dark}\" alt=\"${config.name}\" style={{ height: 28 }} className=\"hidden dark:block\" />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Plain text logo\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: '${logo}',\n },\n };\n}\n`;\n}\n","export function generateLibSource(): string {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n});\n`;\n}\n","export function generateMermaidComponent(): string {\n return `'use client';\n\nimport { use, useEffect, useId, useState } from 'react';\nimport { useTheme } from 'next-themes';\n\nexport function Mermaid({ chart }: { chart: string }) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n if (!mounted) return;\n return <MermaidContent chart={chart} />;\n}\n\nconst cache = new Map<string, Promise<unknown>>();\n\nfunction cachePromise<T>(key: string, setPromise: () => Promise<T>): Promise<T> {\n const cached = cache.get(key);\n if (cached) return cached as Promise<T>;\n\n const promise = setPromise();\n cache.set(key, promise);\n return promise;\n}\n\nfunction MermaidContent({ chart }: { chart: string }) {\n const id = useId();\n const { resolvedTheme } = useTheme();\n const { default: mermaid } = use(cachePromise('mermaid', () => import('mermaid')));\n\n mermaid.initialize({\n startOnLoad: false,\n securityLevel: 'loose',\n fontFamily: 'inherit',\n themeCSS: 'margin: 1.5rem auto 0;',\n theme: resolvedTheme === 'dark' ? 'dark' : 'default',\n });\n\n const { svg, bindFunctions } = use(\n cachePromise(\\`\\${chart}-\\${resolvedTheme}\\`, () => {\n return mermaid.render(id, chart.replaceAll('\\\\\\\\\\\\\\\\n', '\\\\n'));\n }),\n );\n\n return (\n <div\n ref={(container) => {\n if (container) bindFunctions?.(container);\n }}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n\n return `import { createMDX } from 'fumadocs-mdx/next';\n\nconst withMDX = createMDX();\n\n/** @type {import('next').NextConfig} */\nconst config = {\n reactStrictMode: true,${siteUrl ? `\\n output: 'export',` : ''}\n serverExternalPackages: ['mermaid'],\n images: {\n unoptimized: true,\n },\n};\n\nexport default withMDX(config);\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generatePackageJson(_ctx: {\n config: OpenManualConfig;\n projectDir: string;\n}): string {\n const pkg = {\n name: 'openmanual-app',\n type: 'module',\n private: true,\n scripts: {\n dev: 'next dev',\n build: 'next build',\n start: 'next start',\n },\n dependencies: {\n '@tailwindcss/postcss': '^4.1.15',\n 'fumadocs-core': '^16.7.7',\n 'fumadocs-mdx': '^14.2.11',\n 'fumadocs-ui': '^16.7.7',\n mermaid: '^11.4.0',\n next: '^16.2.1',\n 'next-themes': '^0.4.6',\n postcss: '^8.5.8',\n react: '^19.1.0',\n 'react-dom': '^19.1.0',\n tailwindcss: '^4.1.15',\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nfunction buildAllowedSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function generatePage(_ctx: { config: OpenManualConfig }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set(${JSON.stringify([...buildAllowedSlugs(_ctx.config)])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug ? slug.join('/') : 'index';\n return allowedSlugs.has(key);\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug)) {\n notFound();\n }\n`\n : '';\n\n const filterInStaticParams = isStrict\n ? `\nexport function generateStaticParams() {\n let params = source.generateParams();\n params = params.filter((p: { slug: string[] }) => isAllowed(p.slug));\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`;\n\n 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';\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n const page = source.getPage(slug);\n${filterInPage}\n if (!page) {\n notFound();\n }\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc}>\n <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}\n <DocsBody>\n <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />\n </DocsBody>\n </DocsPage>\n );\n}\n${filterInStaticParams}\n`;\n}\n","export function generatePostcssConfig(): string {\n return `/** @type {import('postcss-load-config').Config} */\nconst config = {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n};\n\nexport default config;\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n\n return `'use client';\n\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport type { ReactNode } from 'react';\n\nexport function Provider({ children }: { children: ReactNode }) {\n return (\n <RootProvider\n search={{\n enabled: ${searchEnabled},\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nfunction buildTitleMap(config: OpenManualConfig): Record<string, string> {\n const map: Record<string, string> = {};\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n map[page.slug] = page.title;\n }\n }\n }\n return map;\n}\n\nfunction buildAllowedSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function generateSourceConfig(_ctx: { config: OpenManualConfig }): string {\n const titleMap = buildTitleMap(_ctx.config);\n const titleMapEntries = Object.entries(titleMap)\n .map(([slug, title]) => ` '${slug}': '${title.replace(/'/g, \"\\\\'\")}'`)\n .join(',\\n');\n const titleMapStr = titleMapEntries ? `{\\n${titleMapEntries}\\n}` : '{}';\n\n const isStrict = _ctx.config.contentPolicy !== 'all';\n\n const allowedSlugsSnippet = isStrict\n ? `\n\nconst allowedSlugs = new Set(${JSON.stringify([...buildAllowedSlugs(_ctx.config)])});\n\nfunction slugFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n return relative.replace(/\\\\.(md|mdx)$/i, '');\n}\n`\n : '';\n\n const filterSnippet = isStrict\n ? `\n .refine((_data) => {\n const slug = slugFromPath(ctx.path);\n if (allowedSlugs.size > 0 && !allowedSlugs.has(slug)) {\n return false;\n }\n return true;\n })`\n : '';\n\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\nimport { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';\nimport { z } from 'zod';\n\nconst titleMap: Record<string, string> = ${titleMapStr};${allowedSlugsSnippet}\nfunction titleFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n const slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;\n}\n\nexport const docs = defineDocs({\n dir: 'content',\n docs: {\n schema: (ctx) =>\n z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n full: z.boolean().optional(),\n }).transform((data) => ({\n ...data,\n title: data.title ?? titleFromPath(ctx.path),\n }))${filterSnippet},\n },\n});\n\nexport default defineConfig({\n mdxOptions: {\n remarkPlugins: [remarkMdxMermaid],\n rehypeCodeOptions: {\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n defaultColor: false,\n fallbackLanguage: 'text',\n },\n },\n});\n`;\n}\n","export function generateTsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n lib: ['dom', 'dom.iterable', 'esnext'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n jsx: 'react-jsx',\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n isolatedModules: true,\n incremental: true,\n plugins: [{ name: 'next' }],\n paths: {\n '@/*': ['./*'],\n },\n },\n include: [\n '**/*.ts',\n '**/*.tsx',\n 'next-env.d.ts',\n '.next/types/**/*.ts',\n '.next/dev/types/**/*.ts',\n ],\n exclude: ['node_modules'],\n },\n null,\n 2\n )}\\n`;\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport async function installDeps(appDir: string): Promise<void> {\n const nodeModules = resolve(appDir, 'node_modules');\n\n // Skip install if node_modules already exists\n if (existsSync(nodeModules)) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn('pnpm', ['install', '--no-frozen-lockfile', '--ignore-workspace'], {\n cwd: appDir,\n stdio: 'pipe',\n env: { ...process.env },\n });\n\n let stderr = '';\n child.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`pnpm install failed: ${stderr}`));\n }\n });\n });\n}\n","const COLORS = {\n reset: '\\x1b[0m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString('zh-CN', { hour12: false });\n}\n\nexport const logger = {\n info(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}info${COLORS.reset} ${msg}`\n );\n },\n\n success(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.green}done${COLORS.reset} ${msg}`\n );\n },\n\n warn(msg: string): void {\n console.warn(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}warn${COLORS.reset} ${msg}`\n );\n },\n\n error(msg: string): void {\n console.error(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.red}error${COLORS.reset} ${msg}`\n );\n },\n\n step(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.bold}→${COLORS.reset} ${msg}`\n );\n },\n};\n","import { existsSync } from 'node:fs';\nimport { lstat, mkdir, rm, symlink } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst TEMP_DIR_NAME = '.openmanual';\n\nexport function getTempDir(cwd: string): string {\n return join(cwd, TEMP_DIR_NAME);\n}\n\nexport function getAppDir(cwd: string): string {\n return join(getTempDir(cwd), 'app');\n}\n\nexport async function ensureTempDir(cwd: string): Promise<string> {\n const tempDir = getTempDir(cwd);\n const appDir = getAppDir(cwd);\n\n await mkdir(tempDir, { recursive: true });\n await mkdir(join(appDir, 'app'), { recursive: true });\n\n return tempDir;\n}\n\nexport async function cleanTempDir(cwd: string): Promise<void> {\n const tempDir = getTempDir(cwd);\n if (existsSync(tempDir)) {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\nexport async function createSymlink(target: string, linkPath: string): Promise<void> {\n const resolvedTarget = resolve(target);\n const resolvedLink = resolve(linkPath);\n\n try {\n await lstat(resolvedLink);\n // Remove existing symlink or directory\n await rm(resolvedLink, { recursive: true, force: true });\n } catch {\n // link doesn't exist, that's fine\n }\n\n await symlink(resolvedTarget, resolvedLink, 'junction');\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { extname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { checkCodeLangs } from '../../utils/check-code-langs.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const devCommand = new Command('dev')\n .description('启动开发服务器')\n .option('-p, --port <port>', '端口号', '3000')\n .option('--watch', '监听框架源码变更并自动重新生成', false)\n .option('--cwd <path>', '项目目录(watch 模式下使用)')\n .action(async (options) => {\n const cwd = options.cwd ? resolve(options.cwd) : process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const tempDir = await ensureTempDir(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n };\n\n await generateAll(ctx);\n\n // Check for unsupported code block languages\n try {\n const unknownLangs = await checkCodeLangs(contentDir);\n if (unknownLangs.length > 0) {\n logger.warn('以下文件使用了不认识的代码块语言:');\n for (const item of unknownLangs) {\n logger.warn(` ${item.file}:${item.line} - \"${item.lang}\"`);\n }\n logger.warn('建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值');\n }\n } catch {\n // skip check if shiki is not available\n }\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.success('开发服务器启动中...');\n logger.info(`内容目录: ${contentDir}`);\n logger.info(`临时目录: ${tempDir}`);\n logger.info(`端口: ${options.port}`);\n\n const nextChild = spawn('npx', ['next', 'dev', '--port', options.port], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n nextChild.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n nextChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n process.exit(code);\n }\n });\n\n // Watch mode: monitor framework source and config changes\n let watcher: InstanceType<typeof import('chokidar').FSWatcher> | undefined;\n let regenTimer: ReturnType<typeof setTimeout> | undefined;\n\n if (options.watch) {\n const openmanualRoot = process.env.OPENMANUAL_ROOT;\n if (!openmanualRoot) {\n logger.warn('OPENMANUAL_ROOT 未设置,无法监听框架源码变更');\n } else {\n const chokidar = await import('chokidar');\n const srcDir = resolve(openmanualRoot, 'src');\n const configFile = resolve(cwd, 'openmanual.json');\n\n watcher = chokidar.watch(srcDir, {\n ignoreInitial: true,\n ignored: [\n '**/__tests__/**',\n '**/*.test.ts',\n (path: string) => {\n const ext = extname(path);\n if (!ext) return false; // 无扩展名 = 目录,不忽略\n return ext !== '.ts' && ext !== '.tsx';\n },\n ],\n });\n watcher.add(configFile);\n\n watcher.on('all', (event, filePath) => {\n if (event === 'add' || event === 'change' || event === 'unlink') {\n logger.info(`检测到变更: ${filePath}`);\n clearTimeout(regenTimer);\n regenTimer = setTimeout(() => {\n spawnRegenerate(openmanualRoot, cwd, nextChild);\n }, 300);\n }\n });\n\n logger.success('Watch 模式已启用,监听框架源码和配置变更');\n }\n }\n\n // Handle graceful shutdown\n const cleanup = () => {\n clearTimeout(regenTimer);\n watcher?.close();\n nextChild.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n\nfunction spawnRegenerate(openmanualRoot: string, cwd: string, nextChild: ChildProcess): void {\n if (nextChild.exitCode !== null) {\n logger.warn('Next.js 进程已退出,跳过重新生成');\n return;\n }\n\n logger.step('重新生成文件...');\n\n const binPath = resolve(openmanualRoot, 'src/cli/bin.ts');\n const tsxPath = resolve(openmanualRoot, 'node_modules/.bin/tsx');\n const child = spawn(tsxPath, [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 { 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 { 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 };\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"],"mappings":"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","names":["join","Command","spawn","mkdir","resolve","join","buildAllowedSlugs","join","resolve","existsSync","mkdir","join","resolve","resolve","spawn","mkdir","spawn","resolve","Command","readFile","join","Command","resolve","spawn","spawn","existsSync","resolve","Command","Command","resolve","existsSync","spawn","rm","join","resolve","Command","Command","resolve","rm","join","join","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/bin.ts","../src/cli/commands/build.ts","../src/core/config/loader.ts","../src/core/config/schema.ts","../src/core/generator/index.ts","../src/core/generator/global-css.ts","../src/core/generator/layout.ts","../src/core/generator/lib-source.ts","../src/core/generator/mermaid-component.ts","../src/core/generator/next-config.ts","../src/core/generator/package-json.ts","../src/core/generator/page.ts","../src/core/generator/page-actions-component.ts","../src/core/generator/postcss-config.ts","../src/core/generator/provider.ts","../src/core/generator/raw-content-route.ts","../src/core/generator/source-config.ts","../src/core/generator/tsconfig.ts","../src/utils/copy-raw-markdown.ts","../src/utils/install-deps.ts","../src/utils/logger.ts","../src/utils/temp-dir.ts","../src/cli/commands/dev.ts","../src/utils/check-code-langs.ts","../src/cli/commands/preview.ts","../src/cli/commands/regenerate.ts"],"sourcesContent":["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","import { spawn } from 'node:child_process';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { copyRawMarkdown } from '../../utils/copy-raw-markdown.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { cleanTempDir, createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const buildCommand = new Command('build').description('构建静态站点').action(async () => {\n const cwd = process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n await ensureTempDir(cwd);\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n };\n\n await generateAll(ctx);\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.step('构建静态站点...');\n const buildResult = spawn('npx', ['next', 'build'], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n await new Promise<void>((resolve, reject) => {\n buildResult.on('error', reject);\n buildResult.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Build failed with code ${code}`));\n }\n });\n });\n\n // Copy output to user's output dir\n const outputDir = resolve(cwd, config.outputDir ?? 'dist');\n await mkdir(outputDir, { recursive: true });\n\n const nextOutput = resolve(appDir, 'out');\n try {\n await cp(nextOutput, outputDir, { recursive: true });\n logger.success(`静态站点已输出到: ${outputDir}`);\n } catch {\n // If no 'out' dir, check .next/static\n logger.warn('未找到静态导出产物,请检查 next.config.mjs 中 output: \"export\" 配置');\n }\n\n logger.step('复制原始 Markdown 文件...');\n await copyRawMarkdown(contentDir, outputDir);\n\n logger.step('清理临时文件...');\n await cleanTempDir(cwd);\n\n logger.success('构建完成!');\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n});\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n };\n}\n","import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n","import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { OpenManualConfig } from '../config/schema.js';\nimport { generateGlobalCss } from './global-css.js';\nimport { generateLayout, isImagePath, resolveLogoPaths } from './layout.js';\nimport { generateLibSource } from './lib-source.js';\nimport { generateMermaidComponent } from './mermaid-component.js';\nimport { generateNextConfig } from './next-config.js';\nimport { generatePackageJson } from './package-json.js';\nimport { generatePage } from './page.js';\nimport { generatePageActionsComponent } from './page-actions-component.js';\nimport { generatePostcssConfig } from './postcss-config.js';\nimport { generateProvider } from './provider.js';\nimport { generateRawContentRoute } from './raw-content-route.js';\nimport { generateSourceConfig } from './source-config.js';\nimport { generateTsconfig } from './tsconfig.js';\n\nexport interface GenerateContext {\n config: OpenManualConfig;\n /** Absolute path to user's project root */\n projectDir: string;\n /** Absolute path to .openmanual/app */\n appDir: string;\n /** Content directory relative to project root */\n contentDir: string;\n /** 开发模式标志,dev 模式下不设置 output: 'export',生成 API 路由和 rewrites */\n dev?: boolean;\n}\n\nexport async function generateAll(ctx: GenerateContext): Promise<void> {\n const files: Array<{ path: string; content: string }> = [\n {\n path: 'source.config.ts',\n content: generateSourceConfig(ctx),\n },\n {\n path: 'next.config.mjs',\n content: generateNextConfig(ctx),\n },\n {\n path: 'global.css',\n content: generateGlobalCss(ctx),\n },\n {\n path: 'package.json',\n content: generatePackageJson(ctx),\n },\n {\n path: 'tsconfig.json',\n content: generateTsconfig(),\n },\n {\n path: 'postcss.config.mjs',\n content: generatePostcssConfig(),\n },\n {\n path: 'lib/source.ts',\n content: generateLibSource(),\n },\n {\n path: 'lib/layout.tsx',\n content: generateLayout(ctx),\n },\n {\n path: 'components/mermaid.tsx',\n content: generateMermaidComponent(),\n },\n {\n path: 'components/page-actions.tsx',\n content: generatePageActionsComponent(),\n },\n // 仅在 dev 模式生成 API 路由(生产构建中 output: 'export' 不兼容 API 路由)\n ...(ctx.dev\n ? [{ path: 'app/api/raw/[...path]/route.ts', content: generateRawContentRoute() }]\n : []),\n {\n path: 'app/layout.tsx',\n content: generateRootLayout(),\n },\n {\n path: 'app/provider.tsx',\n content: generateProvider(ctx),\n },\n {\n path: 'app/[[...slug]]/layout.tsx',\n content: generateDocsLayout(ctx),\n },\n {\n path: 'app/[[...slug]]/page.tsx',\n content: generatePage(ctx),\n },\n ];\n\n for (const file of files) {\n const fullPath = join(ctx.appDir, file.path);\n const dir = join(fullPath, '..');\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, file.content, 'utf-8');\n }\n\n // Generate logo SVG in public/ when logo is an image path\n const logo = ctx.config.navbar?.logo;\n if (logo && typeof logo === 'string' && isImagePath(logo)) {\n await ensureLogoFile(ctx, logo, 'light');\n } else if (logo && typeof logo === 'object') {\n const { light, dark } = resolveLogoPaths(logo);\n if (isImagePath(light)) {\n await ensureLogoFile(ctx, light, 'light');\n }\n if (isImagePath(dark) && dark !== light) {\n await ensureLogoFile(ctx, dark, 'dark');\n }\n }\n\n // Generate meta.json for each sidebar group directory\n await generateMetaFiles(ctx);\n}\n\nfunction generateRootLayout(): string {\n return `import { Provider } from './provider';\nimport type { ReactNode } from 'react';\nimport '../global.css';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <html lang=\"zh\" suppressHydrationWarning>\n <body className=\"flex flex-col min-h-screen\">\n <Provider>{children}</Provider>\n </body>\n </html>\n );\n}\n`;\n}\n\nfunction generateDocsLayout(ctx: GenerateContext): string {\n const { config } = ctx;\n const githubLink = config.navbar?.github ?? '';\n const navLinks = config.navbar?.links ?? [];\n const footerText = config.footer?.text ?? '';\n\n const linksArray = navLinks.map((l) => ({\n text: l.label,\n url: l.href,\n external: true,\n }));\n\n const githubLine = githubLink ? `\\n github: '${githubLink}',` : '';\n\n const linksLine = linksArray.length > 0 ? `\\n links: ${JSON.stringify(linksArray)},` : '';\n\n const footerLine = footerText\n ? `\\n footer: { children: '${footerText.replace(/'/g, \"\\\\'\")}' },`\n : '';\n\n // Build sidebar config for tree restructuring (only needed fields)\n const sidebar = config.sidebar;\n const sidebarSnippet =\n sidebar && sidebar.length > 0\n ? `\\nconst sidebarConfig = ${JSON.stringify(\n sidebar.map((g) => ({\n group: g.group,\n collapsed: g.collapsed,\n pages: g.pages.map((p) => ({ slug: p.slug })),\n })),\n null,\n 2\n )} as const;\n\nfunction slugToUrl(slug: string): string {\n return slug === 'index' ? '/' : \\`/\\${slug}\\`;\n}\n\nfunction restructureTree(tree: PageTree.Root): PageTree.Root {\n const consumed = new Set<number>();\n const newChildren: PageTree.Node[] = [];\n\n for (const group of sidebarConfig) {\n const isRootGroup = group.pages.every((p) => !p.slug.includes('/'));\n\n if (isRootGroup) {\n const folderChildren: PageTree.Node[] = [];\n for (const page of group.pages) {\n const url = slugToUrl(page.slug);\n const idx = (tree.children ?? []).findIndex(\n (c, i) => !consumed.has(i) && c.type === 'page' && c.url === url\n );\n if (idx >= 0) {\n folderChildren.push(tree.children![idx]);\n consumed.add(idx);\n }\n }\n if (folderChildren.length > 0) {\n newChildren.push({\n type: 'folder',\n name: group.group,\n defaultOpen: group.collapsed !== true,\n children: folderChildren,\n });\n }\n } else {\n const dirPrefix = group.pages.find((p) => p.slug.includes('/'))?.slug.split('/')[0];\n if (dirPrefix) {\n const idx = (tree.children ?? []).findIndex(\n (child, i) =>\n !consumed.has(i) &&\n child.type === 'folder' &&\n child.children?.some(\n (c) => c.type === 'page' && c.url?.startsWith(\\`/\\${dirPrefix}/\\`)\n )\n );\n if (idx >= 0) {\n consumed.add(idx);\n newChildren.push({\n ...(tree.children![idx] as PageTree.Folder),\n name: group.group,\n defaultOpen: group.collapsed !== true,\n });\n }\n }\n }\n }\n\n for (let i = 0; i < (tree.children ?? []).length; i++) {\n if (!consumed.has(i)) {\n newChildren.push(tree.children![i]);\n }\n }\n\n return { ...tree, children: newChildren };\n}\n`\n : '';\n\n const treeLine = sidebarSnippet\n ? 'tree: restructureTree(source.getPageTree()),'\n : 'tree: source.getPageTree(),';\n\n const pageTreeImport = sidebarSnippet\n ? \"\\nimport type * as PageTree from 'fumadocs-core/page-tree';\"\n : '';\n\n return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';\nimport { baseOptions } from '@/lib/layout';\nimport { source } from '@/lib/source';\nimport type { ReactNode } from 'react';${pageTreeImport}\n${sidebarSnippet}\nconst docsOptions = {\n ...baseOptions(),\n ${treeLine}${githubLine}${linksLine}${footerLine}\n};\n\nexport default function DocsLayoutWrapper({ children }: { children: ReactNode }) {\n return (\n <DocsLayout {...docsOptions}>\n {children}\n </DocsLayout>\n );\n}\n`;\n}\n\nexport function generateOpenManualLogoSvg(\n name: string,\n variant: 'light' | 'dark' = 'light'\n): string {\n const textColor = variant === 'dark' ? '#E8E0D4' : '#000000';\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 190 32\" width=\"190\" height=\"32\">\n <text x=\"0\" y=\"25\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\" font-size=\"32\" font-weight=\"700\">\n <tspan fill=\"#2B7A4B\" font-size=\"34\">${name.charAt(0)}</tspan><tspan fill=\"${textColor}\">${name.slice(1)}</tspan>\n </text>\n</svg>\n`;\n}\n\nasync function ensureLogoFile(\n ctx: GenerateContext,\n logoPath: string,\n variant: 'light' | 'dark'\n): Promise<void> {\n const userLogoPath = join(ctx.projectDir, 'public', logoPath.replace(/^\\//, ''));\n try {\n await access(userLogoPath);\n } catch {\n const publicDir = join(ctx.appDir, 'public');\n await mkdir(publicDir, { recursive: true });\n const fullPath = join(publicDir, logoPath.replace(/^\\//, ''));\n await mkdir(join(fullPath, '..'), { recursive: true });\n await writeFile(fullPath, generateOpenManualLogoSvg(ctx.config.name, variant), 'utf-8');\n }\n}\n\n/**\n * Generate meta.json for each sidebar group directory so that\n * fumadocs displays the configured Chinese group name instead of\n * auto-capitalizing the English directory name.\n */\nasync function generateMetaFiles(ctx: GenerateContext): Promise<void> {\n const sidebar = ctx.config.sidebar;\n if (!sidebar || sidebar.length === 0) return;\n\n const contentAbsDir = join(ctx.projectDir, ctx.contentDir);\n\n for (const group of sidebar) {\n // Extract directory prefix from the first page slug that contains \"/\"\n const dirPrefix = group.pages\n .map((p) => p.slug)\n .find((slug) => slug.includes('/'))\n ?.split('/')[0];\n\n if (!dirPrefix) continue; // Root-level pages, no meta.json needed\n\n const dirPath = join(contentAbsDir, dirPrefix);\n const metaPath = join(dirPath, 'meta.json');\n\n // Skip if meta.json already exists\n try {\n await access(metaPath);\n continue;\n } catch {\n // File doesn't exist, proceed to create it\n }\n\n await mkdir(dirPath, { recursive: true });\n await writeFile(metaPath, `${JSON.stringify({ title: group.group }, null, 2)}\\n`, 'utf-8');\n }\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateGlobalCss(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const primaryHue = config.theme?.primaryHue ?? 213;\n const darkMode = config.theme?.darkMode ?? true;\n\n const darkBlock = darkMode\n ? `\n.dark {\n --primary-hue: ${primaryHue};\n\n /* 温暖的深色皮革背景 */\n --color-fd-background: hsl(30, 18%, 10%);\n --color-fd-foreground: hsl(35, 15%, 90%);\n --color-fd-muted: hsl(30, 14%, 14%);\n --color-fd-muted-foreground: hsla(30, 10%, 65%, 0.8);\n --color-fd-popover: hsl(30, 16%, 13%);\n --color-fd-popover-foreground: hsl(35, 12%, 87%);\n --color-fd-card: hsl(30, 15%, 12%);\n --color-fd-card-foreground: hsl(35, 15%, 93%);\n --color-fd-border: hsla(30, 12%, 35%, 25%);\n --color-fd-primary: hsl(35, 20%, 92%);\n --color-fd-primary-foreground: hsl(30, 25%, 10%);\n --color-fd-secondary: hsl(30, 12%, 16%);\n --color-fd-secondary-foreground: hsl(35, 10%, 88%);\n --color-fd-accent: hsla(30, 15%, 30%, 35%);\n --color-fd-accent-foreground: hsl(35, 12%, 88%);\n --color-fd-ring: hsl(30, 30%, 50%);\n --color-fd-overlay: hsla(25, 20%, 5%, 0.5);\n}\n\n.dark body {\n background: linear-gradient(hsla(30, 30%, 15%, 0.4), transparent 20rem, transparent);\n}\n`\n : '';\n\n return `@import 'tailwindcss';\n@import 'fumadocs-ui/style.css';\n@custom-variant dark (&:is(.dark, .dark *));\n\n:root {\n --primary-hue: ${primaryHue};\n\n /* 护眼暖色阅读背景 */\n --color-fd-background: hsl(40, 22%, 96.5%); /* #faf9f6 纸张白 */\n --color-fd-foreground: hsl(0, 0%, 17.3%); /* #2c2c2c 柔黑 */\n --color-fd-muted: hsl(40, 15%, 95%); /* 柔和的暖灰背景 */\n --color-fd-card: hsl(40, 18%, 94%); /* 卡片背景 */\n --color-fd-popover: hsl(40, 20%, 97.5%); /* 弹窗背景 */\n}\n${darkBlock}`;\n}\n","import type { LogoConfig, OpenManualConfig } from '../config/schema.js';\n\nconst IMAGE_EXTENSIONS = ['.svg', '.png', '.jpg', '.jpeg', '.webp'];\n\nexport function isImagePath(value: string): boolean {\n if (value.startsWith('/')) return true;\n return IMAGE_EXTENSIONS.some((ext) => value.toLowerCase().endsWith(ext));\n}\n\nexport function resolveLogoPaths(logo: LogoConfig): { light: string; dark: string } {\n if (typeof logo === 'string') {\n return { light: logo, dark: logo };\n }\n return { light: logo.light, dark: logo.dark };\n}\n\nexport function generateLayout(ctx: { config: OpenManualConfig }): string {\n const { config } = ctx;\n const logo = config.navbar?.logo ?? config.name;\n\n // String logo that is an image path — backward compatible single image\n if (typeof logo === 'string' && isImagePath(logo)) {\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${logo}\" alt=\"${config.name}\" style={{ height: 28 }} />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Object logo { light, dark }\n if (typeof logo === 'object') {\n const { light, dark } = logo;\n\n // Same path — treat as single image\n if (light === dark) {\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${light}\" alt=\"${config.name}\" style={{ height: 28 }} />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Different paths — generate two images with dark mode toggle\n return `import type { ReactNode } from 'react';\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: (\n <>\n <img src=\"${light}\" alt=\"${config.name}\" style={{ height: 28 }} className=\"dark:hidden\" />\n <img src=\"${dark}\" alt=\"${config.name}\" style={{ height: 28 }} className=\"hidden dark:block\" />\n </>\n ) as ReactNode,\n },\n };\n}\n`;\n }\n\n // Plain text logo\n return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';\n\nexport function baseOptions(): BaseLayoutProps {\n return {\n nav: {\n title: '${logo}',\n },\n };\n}\n`;\n}\n","export function generateLibSource(): string {\n return `import { docs } from '@/.source/server';\nimport { loader } from 'fumadocs-core/source';\n\nexport const source = loader({\n baseUrl: '/',\n source: docs.toFumadocsSource(),\n});\n`;\n}\n","export function generateMermaidComponent(): string {\n return `'use client';\n\nimport { use, useEffect, useId, useState } from 'react';\nimport { useTheme } from 'next-themes';\n\nexport function Mermaid({ chart }: { chart: string }) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n if (!mounted) return;\n return <MermaidContent chart={chart} />;\n}\n\nconst cache = new Map<string, Promise<unknown>>();\n\nfunction cachePromise<T>(key: string, setPromise: () => Promise<T>): Promise<T> {\n const cached = cache.get(key);\n if (cached) return cached as Promise<T>;\n\n const promise = setPromise();\n cache.set(key, promise);\n return promise;\n}\n\nfunction MermaidContent({ chart }: { chart: string }) {\n const id = useId();\n const { resolvedTheme } = useTheme();\n const { default: mermaid } = use(cachePromise('mermaid', () => import('mermaid')));\n\n mermaid.initialize({\n startOnLoad: false,\n securityLevel: 'loose',\n fontFamily: 'inherit',\n themeCSS: 'margin: 1.5rem auto 0;',\n theme: resolvedTheme === 'dark' ? 'dark' : 'default',\n });\n\n const { svg, bindFunctions } = use(\n cachePromise(\\`\\${chart}-\\${resolvedTheme}\\`, () => {\n return mermaid.render(id, chart.replaceAll('\\\\\\\\\\\\\\\\n', '\\\\n'));\n }),\n );\n\n return (\n <div\n ref={(container) => {\n if (container) bindFunctions?.(container);\n }}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateNextConfig(ctx: { config: OpenManualConfig; dev?: boolean }): string {\n const { config } = ctx;\n const siteUrl = config.siteUrl ?? '';\n\n // dev 模式下不设置 output: 'export'(不兼容 API 路由和 rewrites)\n const outputLine = !ctx.dev && siteUrl ? `\\n output: 'export',` : '';\n // dev 模式下添加 rewrites 将 .md 请求代理到 API 路由\n const rewritesBlock = ctx.dev\n ? `\\n async rewrites() {\\n return [{ source: '/:path(.+)\\\\\\\\.md', destination: '/api/raw/:path' }];\\n },`\n : '';\n\n return `import { createMDX } from 'fumadocs-mdx/next';\n\nconst withMDX = createMDX();\n\n/** @type {import('next').NextConfig} */\nconst config = {\n reactStrictMode: true,${outputLine}\n serverExternalPackages: ['mermaid'],\n images: {\n unoptimized: true,\n },${rewritesBlock}\n};\n\nexport default withMDX(config);\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generatePackageJson(_ctx: {\n config: OpenManualConfig;\n projectDir: string;\n}): string {\n const pkg = {\n name: 'openmanual-app',\n type: 'module',\n private: true,\n scripts: {\n dev: 'next dev',\n build: 'next build',\n start: 'next start',\n },\n dependencies: {\n '@tailwindcss/postcss': '^4.1.15',\n 'fumadocs-core': '^16.7.7',\n 'fumadocs-mdx': '^14.2.11',\n 'fumadocs-ui': '^16.7.7',\n mermaid: '^11.4.0',\n next: '^16.2.1',\n 'next-themes': '^0.4.6',\n postcss: '^8.5.8',\n react: '^19.1.0',\n 'react-dom': '^19.1.0',\n tailwindcss: '^4.1.15',\n zod: '^4.0.0',\n },\n };\n\n return `${JSON.stringify(pkg, null, 2)}\\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nfunction buildAllowedSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function generatePage(_ctx: { config: OpenManualConfig }): string {\n const isStrict = _ctx.config.contentPolicy !== 'all';\n const pageActionsEnabled = _ctx.config.pageActions?.enabled !== false;\n\n const allowedSlugsSnippet = isStrict\n ? `\nconst allowedSlugs = new Set(${JSON.stringify([...buildAllowedSlugs(_ctx.config)])});\n\nfunction isAllowed(slug: string[] | undefined): boolean {\n if (allowedSlugs.size === 0) return true;\n const key = slug ? slug.join('/') : 'index';\n return allowedSlugs.has(key);\n}\n`\n : '';\n\n const filterInPage = isStrict\n ? `\n if (!isAllowed(slug)) {\n notFound();\n }\n`\n : '';\n\n const filterInStaticParams = isStrict\n ? `\nexport function generateStaticParams() {\n let params = source.generateParams();\n params = params.filter((p: { slug: string[] }) => isAllowed(p.slug));\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`\n : `\nexport function generateStaticParams() {\n const params = source.generateParams();\n if (!params.some((p: { slug: string[] }) => p.slug.length === 0)) {\n params.unshift({ ...params[0], slug: [] });\n }\n return params;\n}`;\n\n const pageActionsImport = pageActionsEnabled\n ? \"\\nimport { PageActions } from '@/components/page-actions';\"\n : '';\n\n const pageTitleArea = pageActionsEnabled\n ? ` <div className=\"flex items-start justify-between gap-4\">\n <div>\n <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}\n </div>\n <PageActions />\n </div>`\n : ` <DocsTitle>{page.data.title}</DocsTitle>\n {page.data.description && (\n <DocsDescription>{page.data.description}</DocsDescription>\n )}`;\n\n return `import { source } from '@/lib/source';\nimport { notFound } from 'next/navigation';\nimport { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';\nimport defaultMdxComponents from 'fumadocs-ui/mdx';\nimport { Steps, Step } from 'fumadocs-ui/components/steps';\nimport { Tabs, Tab } from 'fumadocs-ui/components/tabs';\nimport { Files, File, Folder } from 'fumadocs-ui/components/files';\nimport { Accordion, Accordions } from 'fumadocs-ui/components/accordion';\nimport { TypeTable } from 'fumadocs-ui/components/type-table';\nimport { Mermaid } from '@/components/mermaid';${pageActionsImport}\n${allowedSlugsSnippet}\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const { slug } = await params;\n const page = source.getPage(slug);\n${filterInPage}\n if (!page) {\n notFound();\n }\n\n const MDX = page.data.body;\n\n return (\n <DocsPage toc={page.data.toc}>\n${pageTitleArea}\n <DocsBody data-content-area>\n <MDX components={{ ...defaultMdxComponents, Steps, Step, Tabs, Tab, Files, File, Folder, Accordion, Accordions, TypeTable, Mermaid }} />\n </DocsBody>\n </DocsPage>\n );\n}\n${filterInStaticParams}\n`;\n}\n","export function generatePageActionsComponent(): string {\n return `'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\n\nfunction cn(...classes: (string | undefined | false)[]) {\n return classes.filter(Boolean).join(' ');\n}\n\nfunction getPageText(): string {\n const article =\n document.querySelector<HTMLElement>('[data-content-area]') ??\n document.querySelector<HTMLElement>('article');\n return article ? article.innerText : '';\n}\n\nexport function PageActions() {\n const [copied, setCopied] = useState(false);\n const [open, setOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const handleClickOutside = useCallback((e: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [open, handleClickOutside]);\n\n const handleCopyFullText = useCallback(async () => {\n const text = getPageText();\n if (!text) return;\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => {\n setCopied(false);\n setOpen(false);\n }, 2000);\n }, []);\n\n const handleViewMarkdown = useCallback(() => {\n const path = window.location.pathname;\n const mdUrl = path === '/' ? '/index.md' : \\`\\${path}.md\\`;\n window.open(mdUrl, '_blank');\n setOpen(false);\n }, []);\n\n const CopyIcon = (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n\n const CheckIcon = (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n );\n\n const ChevronDownIcon = (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n );\n\n const ChevronUpIcon = (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n );\n\n const FileTextIcon = (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\" />\n <path d=\"M14 2v4a2 2 0 0 0 2 2h4\" />\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </svg>\n );\n\n return (\n <div ref={menuRef} className=\"relative\">\n <div className={cn(\n 'inline-flex items-stretch rounded-xl border border-fd-border',\n 'overflow-hidden',\n )}>\n <button\n type=\"button\"\n onClick={handleCopyFullText}\n className={cn(\n 'inline-flex items-center gap-1.5 px-3 py-1.5 text-sm cursor-pointer',\n 'text-fd-muted-foreground hover:text-fd-foreground',\n 'hover:bg-fd-secondary/80 transition-colors',\n )}\n >\n {copied ? CheckIcon : CopyIcon}\n {copied ? 'Copied!' : 'Copy page'}\n </button>\n <div className=\"w-px bg-fd-border\" />\n <button\n type=\"button\"\n onClick={() => setOpen(!open)}\n className={cn(\n 'inline-flex items-center justify-center px-2 py-1.5 cursor-pointer',\n 'text-fd-muted-foreground hover:text-fd-foreground',\n 'hover:bg-fd-secondary/80 transition-colors',\n )}\n aria-label={open ? '收起菜单' : '展开菜单'}\n >\n {open ? ChevronUpIcon : ChevronDownIcon}\n </button>\n </div>\n\n {open && (\n <div\n className={cn(\n 'absolute right-0 top-full mt-1 z-50',\n 'min-w-[280px] rounded-xl border border-fd-border',\n 'bg-fd-popover p-1 shadow-md',\n )}\n >\n <button\n type=\"button\"\n onClick={handleCopyFullText}\n className={cn(\n 'flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-sm cursor-pointer',\n 'text-fd-popover-foreground',\n 'hover:bg-fd-secondary/80 transition-colors',\n )}\n >\n <span className=\"mt-0.5 text-fd-muted-foreground\">\n {copied ? CheckIcon : CopyIcon}\n </span>\n <span className=\"flex flex-col items-start\">\n <span className=\"font-medium\">{copied ? '已复制' : '复制全文'}</span>\n <span className=\"text-xs text-fd-muted-foreground\">复制页面内容,适合 AI 工具使用</span>\n </span>\n </button>\n <button\n type=\"button\"\n onClick={handleViewMarkdown}\n className={cn(\n 'flex w-full items-start gap-3 rounded-md px-3 py-2.5 text-sm cursor-pointer',\n 'text-fd-popover-foreground',\n 'hover:bg-fd-secondary/80 transition-colors',\n )}\n >\n <span className=\"mt-0.5 text-fd-muted-foreground\">\n {FileTextIcon}\n </span>\n <span className=\"flex flex-col items-start\">\n <span className=\"font-medium\">查看原文</span>\n <span className=\"text-xs text-fd-muted-foreground\">查看原始 Markdown 源文件</span>\n </span>\n </button>\n </div>\n )}\n </div>\n );\n}\n`;\n}\n","export function generatePostcssConfig(): string {\n return `/** @type {import('postcss-load-config').Config} */\nconst config = {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n};\n\nexport default config;\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nexport function generateProvider(ctx: { config: OpenManualConfig }): string {\n const searchEnabled = ctx.config.search?.enabled !== false;\n\n return `'use client';\n\nimport { RootProvider } from 'fumadocs-ui/provider/next';\nimport type { ReactNode } from 'react';\n\nexport function Provider({ children }: { children: ReactNode }) {\n return (\n <RootProvider\n search={{\n enabled: ${searchEnabled},\n }}\n >\n {children}\n </RootProvider>\n );\n}\n`;\n}\n","export function generateRawContentRoute(): string {\n return `import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { NextResponse } from 'next/server';\n\nexport async function GET(\n _request: Request,\n { params }: { params: Promise<{ path: string[] }> },\n) {\n const { path: segments } = await params;\n const slug = segments.join('/');\n for (const ext of ['.mdx', '.md']) {\n try {\n const filePath = join(process.cwd(), 'content', \\`\\${slug}\\${ext}\\`);\n const content = await readFile(filePath, 'utf-8');\n return new NextResponse(content, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n });\n } catch {\n /* try next extension */\n }\n }\n return new NextResponse('Not found', { status: 404 });\n}\n`;\n}\n","import type { OpenManualConfig } from '../config/schema.js';\n\nfunction buildTitleMap(config: OpenManualConfig): Record<string, string> {\n const map: Record<string, string> = {};\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n map[page.slug] = page.title;\n }\n }\n }\n return map;\n}\n\nfunction buildAllowedSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n\nexport function generateSourceConfig(_ctx: { config: OpenManualConfig }): string {\n const titleMap = buildTitleMap(_ctx.config);\n const titleMapEntries = Object.entries(titleMap)\n .map(([slug, title]) => ` '${slug}': '${title.replace(/'/g, \"\\\\'\")}'`)\n .join(',\\n');\n const titleMapStr = titleMapEntries ? `{\\n${titleMapEntries}\\n}` : '{}';\n\n const isStrict = _ctx.config.contentPolicy !== 'all';\n\n const allowedSlugsSnippet = isStrict\n ? `\n\nconst allowedSlugs = new Set(${JSON.stringify([...buildAllowedSlugs(_ctx.config)])});\n\nfunction slugFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n return relative.replace(/\\\\.(md|mdx)$/i, '');\n}\n`\n : '';\n\n const filterSnippet = isStrict\n ? `\n .refine((_data) => {\n const slug = slugFromPath(ctx.path);\n if (allowedSlugs.size > 0 && !allowedSlugs.has(slug)) {\n return false;\n }\n return true;\n })`\n : '';\n\n return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';\nimport { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';\nimport { z } from 'zod';\n\nconst titleMap: Record<string, string> = ${titleMapStr};${allowedSlugsSnippet}\nfunction titleFromPath(path: string): string {\n const normalized = path.replace(/\\\\\\\\/g, '/');\n const idx = normalized.indexOf('content/');\n const relative = idx >= 0 ? normalized.slice(idx + 'content/'.length) : normalized;\n const slug = relative.replace(/\\\\.(md|mdx)$/i, '');\n return titleMap[slug] || slug.split('/').pop() || slug;\n}\n\nexport const docs = defineDocs({\n dir: 'content',\n docs: {\n schema: (ctx) =>\n z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n full: z.boolean().optional(),\n }).transform((data) => ({\n ...data,\n title: data.title ?? titleFromPath(ctx.path),\n }))${filterSnippet},\n },\n});\n\nexport default defineConfig({\n mdxOptions: {\n remarkPlugins: [remarkMdxMermaid],\n rehypeCodeOptions: {\n themes: {\n light: 'github-light',\n dark: 'github-dark',\n },\n defaultColor: false,\n fallbackLanguage: 'text',\n },\n },\n});\n`;\n}\n","export function generateTsconfig(): string {\n return `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n lib: ['dom', 'dom.iterable', 'esnext'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n jsx: 'react-jsx',\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n isolatedModules: true,\n incremental: true,\n plugins: [{ name: 'next' }],\n paths: {\n '@/*': ['./*'],\n },\n },\n include: [\n '**/*.ts',\n '**/*.tsx',\n 'next-env.d.ts',\n '.next/types/**/*.ts',\n '.next/dev/types/**/*.ts',\n ],\n exclude: ['node_modules'],\n },\n null,\n 2\n )}\\n`;\n}\n","import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';\nimport { join, parse } from 'node:path';\n\n/**\n * Recursively copy .mdx/.md files from contentDir to targetDir,\n * renaming extensions to .md. Non-markdown files are skipped.\n */\nexport async function copyRawMarkdown(contentDir: string, targetDir: string): Promise<void> {\n await mkdir(targetDir, { recursive: true });\n\n const entries = await readdir(contentDir);\n for (const entry of entries) {\n const srcPath = join(contentDir, entry);\n const srcStat = await stat(srcPath);\n\n if (srcStat.isDirectory()) {\n await copyRawMarkdown(srcPath, join(targetDir, entry));\n } else {\n const ext = parse(entry).ext.toLowerCase();\n if (ext === '.mdx' || ext === '.md') {\n const { name } = parse(entry);\n const destPath = join(targetDir, `${name}.md`);\n await copyFile(srcPath, destPath);\n }\n }\n }\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport async function installDeps(appDir: string): Promise<void> {\n const nodeModules = resolve(appDir, 'node_modules');\n\n // Skip install if node_modules already exists\n if (existsSync(nodeModules)) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn('pnpm', ['install', '--no-frozen-lockfile', '--ignore-workspace'], {\n cwd: appDir,\n stdio: 'pipe',\n env: { ...process.env },\n });\n\n let stderr = '';\n child.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`pnpm install failed: ${stderr}`));\n }\n });\n });\n}\n","const COLORS = {\n reset: '\\x1b[0m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString('zh-CN', { hour12: false });\n}\n\nexport const logger = {\n info(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}info${COLORS.reset} ${msg}`\n );\n },\n\n success(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.green}done${COLORS.reset} ${msg}`\n );\n },\n\n warn(msg: string): void {\n console.warn(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}warn${COLORS.reset} ${msg}`\n );\n },\n\n error(msg: string): void {\n console.error(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.red}error${COLORS.reset} ${msg}`\n );\n },\n\n step(msg: string): void {\n console.log(\n `${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.bold}→${COLORS.reset} ${msg}`\n );\n },\n};\n","import { existsSync } from 'node:fs';\nimport { lstat, mkdir, rm, symlink } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst TEMP_DIR_NAME = '.openmanual';\n\nexport function getTempDir(cwd: string): string {\n return join(cwd, TEMP_DIR_NAME);\n}\n\nexport function getAppDir(cwd: string): string {\n return join(getTempDir(cwd), 'app');\n}\n\nexport async function ensureTempDir(cwd: string): Promise<string> {\n const tempDir = getTempDir(cwd);\n const appDir = getAppDir(cwd);\n\n await mkdir(tempDir, { recursive: true });\n await mkdir(join(appDir, 'app'), { recursive: true });\n\n return tempDir;\n}\n\nexport async function cleanTempDir(cwd: string): Promise<void> {\n const tempDir = getTempDir(cwd);\n if (existsSync(tempDir)) {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\nexport async function createSymlink(target: string, linkPath: string): Promise<void> {\n const resolvedTarget = resolve(target);\n const resolvedLink = resolve(linkPath);\n\n try {\n await lstat(resolvedLink);\n // Remove existing symlink or directory\n await rm(resolvedLink, { recursive: true, force: true });\n } catch {\n // link doesn't exist, that's fine\n }\n\n await symlink(resolvedTarget, resolvedLink, 'junction');\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { extname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadConfig } from '../../core/config/loader.js';\nimport { generateAll } from '../../core/generator/index.js';\nimport { checkCodeLangs } from '../../utils/check-code-langs.js';\nimport { installDeps } from '../../utils/install-deps.js';\nimport { logger } from '../../utils/logger.js';\nimport { createSymlink, ensureTempDir, getAppDir } from '../../utils/temp-dir.js';\n\nexport const devCommand = new Command('dev')\n .description('启动开发服务器')\n .option('-p, --port <port>', '端口号', '3000')\n .option('--watch', '监听框架源码变更并自动重新生成', false)\n .option('--cwd <path>', '项目目录(watch 模式下使用)')\n .action(async (options) => {\n const cwd = options.cwd ? resolve(options.cwd) : process.cwd();\n\n try {\n logger.step('读取配置文件...');\n const config = await loadConfig(cwd);\n\n logger.step('生成临时应用...');\n const tempDir = await ensureTempDir(cwd);\n const appDir = getAppDir(cwd);\n const contentDir = resolve(cwd, config.contentDir ?? 'content');\n\n const ctx = {\n config,\n projectDir: cwd,\n appDir,\n contentDir: config.contentDir ?? 'content',\n dev: true,\n };\n\n await generateAll(ctx);\n\n // Check for unsupported code block languages\n try {\n const unknownLangs = await checkCodeLangs(contentDir);\n if (unknownLangs.length > 0) {\n logger.warn('以下文件使用了不认识的代码块语言:');\n for (const item of unknownLangs) {\n logger.warn(` ${item.file}:${item.line} - \"${item.lang}\"`);\n }\n logger.warn('建议将这些语言改为受支持的类型,或使用 \"text\" 作为默认值');\n }\n } catch {\n // skip check if shiki is not available\n }\n\n // Symlink content directory\n await createSymlink(contentDir, resolve(appDir, 'content'));\n\n // Symlink public directory if exists\n const publicDir = resolve(cwd, 'public');\n try {\n const { stat } = await import('node:fs/promises');\n await stat(publicDir);\n await createSymlink(publicDir, resolve(appDir, 'public'));\n } catch {\n // no public dir, that's fine\n }\n\n logger.step('安装依赖...');\n await installDeps(appDir);\n\n logger.success('开发服务器启动中...');\n logger.info(`内容目录: ${contentDir}`);\n logger.info(`临时目录: ${tempDir}`);\n logger.info(`端口: ${options.port}`);\n\n const nextChild = spawn('npx', ['next', 'dev', '--port', options.port], {\n cwd: appDir,\n stdio: 'inherit',\n env: { ...process.env },\n });\n\n nextChild.on('error', (err) => {\n logger.error(`启动失败: ${err.message}`);\n process.exit(1);\n });\n\n nextChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n process.exit(code);\n }\n });\n\n // Watch mode: monitor framework source and config changes\n let watcher: InstanceType<typeof import('chokidar').FSWatcher> | undefined;\n let regenTimer: ReturnType<typeof setTimeout> | undefined;\n\n if (options.watch) {\n const openmanualRoot = process.env.OPENMANUAL_ROOT;\n if (!openmanualRoot) {\n logger.warn('OPENMANUAL_ROOT 未设置,无法监听框架源码变更');\n } else {\n const chokidar = await import('chokidar');\n const srcDir = resolve(openmanualRoot, 'src');\n const configFile = resolve(cwd, 'openmanual.json');\n\n watcher = chokidar.watch(srcDir, {\n ignoreInitial: true,\n ignored: [\n '**/__tests__/**',\n '**/*.test.ts',\n (path: string) => {\n const ext = extname(path);\n if (!ext) return false; // 无扩展名 = 目录,不忽略\n return ext !== '.ts' && ext !== '.tsx';\n },\n ],\n });\n watcher.add(configFile);\n\n watcher.on('all', (event, filePath) => {\n if (event === 'add' || event === 'change' || event === 'unlink') {\n logger.info(`检测到变更: ${filePath}`);\n clearTimeout(regenTimer);\n regenTimer = setTimeout(() => {\n spawnRegenerate(openmanualRoot, cwd, nextChild);\n }, 300);\n }\n });\n\n logger.success('Watch 模式已启用,监听框架源码和配置变更');\n }\n }\n\n // Handle graceful shutdown\n const cleanup = () => {\n clearTimeout(regenTimer);\n watcher?.close();\n nextChild.kill();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(1);\n }\n });\n\nfunction spawnRegenerate(openmanualRoot: string, cwd: string, nextChild: ChildProcess): void {\n if (nextChild.exitCode !== null) {\n logger.warn('Next.js 进程已退出,跳过重新生成');\n return;\n }\n\n logger.step('重新生成文件...');\n\n const binPath = resolve(openmanualRoot, 'src/cli/bin.ts');\n const tsxPath = resolve(openmanualRoot, 'node_modules/.bin/tsx');\n const child = spawn(tsxPath, [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 { 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 { 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 };\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"],"mappings":"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","names":["join","Command","spawn","mkdir","resolve","join","buildAllowedSlugs","join","mkdir","join","resolve","existsSync","mkdir","join","resolve","resolve","stat","spawn","mkdir","spawn","resolve","Command","readdir","readFile","join","Command","resolve","stat","spawn","spawn","existsSync","resolve","Command","Command","resolve","existsSync","spawn","rm","join","resolve","Command","Command","resolve","rm","join","stat","join","Command"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,9 @@ declare const OpenManualConfigSchema: z.ZodObject<{
|
|
|
45
45
|
mdx: z.ZodOptional<z.ZodObject<{
|
|
46
46
|
latex: z.ZodOptional<z.ZodBoolean>;
|
|
47
47
|
}, z.core.$strip>>;
|
|
48
|
+
pageActions: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
50
|
+
}, z.core.$strip>>;
|
|
48
51
|
}, z.core.$strip>;
|
|
49
52
|
type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;
|
|
50
53
|
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,9 @@ var SearchSchema = z.object({
|
|
|
39
39
|
var MdxSchema = z.object({
|
|
40
40
|
latex: z.boolean().optional()
|
|
41
41
|
});
|
|
42
|
+
var PageActionsSchema = z.object({
|
|
43
|
+
enabled: z.boolean().optional()
|
|
44
|
+
});
|
|
42
45
|
var OpenManualConfigSchema = z.object({
|
|
43
46
|
name: z.string().min(1),
|
|
44
47
|
description: z.string().optional(),
|
|
@@ -52,7 +55,8 @@ var OpenManualConfigSchema = z.object({
|
|
|
52
55
|
sidebar: z.array(SidebarGroupSchema).optional(),
|
|
53
56
|
theme: ThemeSchema.optional(),
|
|
54
57
|
search: SearchSchema.optional(),
|
|
55
|
-
mdx: MdxSchema.optional()
|
|
58
|
+
mdx: MdxSchema.optional(),
|
|
59
|
+
pageActions: PageActionsSchema.optional()
|
|
56
60
|
});
|
|
57
61
|
|
|
58
62
|
// src/core/config/loader.ts
|
|
@@ -69,7 +73,8 @@ var DEFAULT_CONFIG = {
|
|
|
69
73
|
search: {
|
|
70
74
|
enabled: true
|
|
71
75
|
},
|
|
72
|
-
mdx: {}
|
|
76
|
+
mdx: {},
|
|
77
|
+
pageActions: { enabled: true }
|
|
73
78
|
};
|
|
74
79
|
async function loadConfig(cwd = process.cwd()) {
|
|
75
80
|
const configPath = join(cwd, "openmanual.json");
|
|
@@ -121,6 +126,10 @@ function mergeDefaults(config) {
|
|
|
121
126
|
mdx: {
|
|
122
127
|
...DEFAULT_CONFIG.mdx,
|
|
123
128
|
...config.mdx
|
|
129
|
+
},
|
|
130
|
+
pageActions: {
|
|
131
|
+
...DEFAULT_CONFIG.pageActions,
|
|
132
|
+
...config.pageActions
|
|
124
133
|
}
|
|
125
134
|
};
|
|
126
135
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config/loader.ts","../src/core/config/schema.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n };\n}\n","import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,YAAY;;;ACDrB,SAAS,SAAS;AAEX,IAAM,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAE1F,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,MAAM,WAAW,SAAS;AAAA,EAC1B,QAAQ,EAAE,IAAI,EAAE,SAAS;AAAA,EACzB,OAAO,EACJ;AAAA,IACC,EAAE,OAAO;AAAA,MACP,OAAO,EAAE,OAAO;AAAA,MAChB,MAAM,EAAE,OAAO;AAAA,IACjB,CAAC;AAAA,EACH,EACC,SAAS;AACd,CAAC;AAEM,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;AAEM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;AAEM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,OAAO,EAAE,OAAO;AAAA,EAChB,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,OAAO,EAAE,MAAM,iBAAiB;AAClC,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChD,UAAU,EAAE,QAAQ,EAAE,SAAS;AACjC,CAAC;AAEM,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,SAAS,EAAE,QAAQ,EAAE,SAAS;AAChC,CAAC;AAEM,IAAM,YAAY,EAAE,OAAO;AAAA,EAChC,OAAO,EAAE,QAAQ,EAAE,SAAS;AAC9B,CAAC;AAEM,IAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,IAAI,EAAE,SAAS;AAAA,EAC1B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,eAAe,EAAE,KAAK,CAAC,UAAU,KAAK,CAAC,EAAE,SAAS;AAAA,EAClD,QAAQ,aAAa,SAAS;AAAA,EAC9B,QAAQ,aAAa,SAAS;AAAA,EAC9B,SAAS,EAAE,MAAM,kBAAkB,EAAE,SAAS;AAAA,EAC9C,OAAO,YAAY,SAAS;AAAA,EAC5B,QAAQ,aAAa,SAAS;AAAA,EAC9B,KAAK,UAAU,SAAS;
|
|
1
|
+
{"version":3,"sources":["../src/core/config/loader.ts","../src/core/config/schema.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type OpenManualConfig, OpenManualConfigSchema } from './schema.js';\n\nconst DEFAULT_CONFIG: Partial<OpenManualConfig> = {\n contentDir: 'content',\n outputDir: 'dist',\n locale: 'zh',\n navbar: {},\n footer: {},\n theme: {\n primaryHue: 213,\n darkMode: true,\n },\n search: {\n enabled: true,\n },\n mdx: {},\n pageActions: { enabled: true },\n};\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<OpenManualConfig> {\n const configPath = join(cwd, 'openmanual.json');\n\n let rawJson: string;\n try {\n rawJson = await readFile(configPath, 'utf-8');\n } catch {\n throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawJson);\n } catch {\n throw new Error('openmanual.json is not valid JSON.');\n }\n\n const result = OpenManualConfigSchema.safeParse(parsed);\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`openmanual.json validation failed:\\n${errors}`);\n }\n\n return mergeDefaults(result.data);\n}\n\nfunction mergeDefaults(config: OpenManualConfig): OpenManualConfig {\n return {\n ...config,\n contentPolicy: config.contentPolicy ?? 'strict',\n contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? 'content',\n outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? 'dist',\n locale: config.locale ?? DEFAULT_CONFIG.locale ?? 'zh',\n navbar: {\n ...DEFAULT_CONFIG.navbar,\n ...config.navbar,\n logo: config.navbar?.logo ?? config.name,\n },\n footer: {\n ...DEFAULT_CONFIG.footer,\n ...config.footer,\n text: config.footer?.text ?? `MIT ${new Date().getFullYear()} © ${config.name}.`,\n },\n theme: {\n ...DEFAULT_CONFIG.theme,\n ...config.theme,\n },\n search: {\n ...DEFAULT_CONFIG.search,\n ...config.search,\n },\n mdx: {\n ...DEFAULT_CONFIG.mdx,\n ...config.mdx,\n },\n pageActions: {\n ...DEFAULT_CONFIG.pageActions,\n ...config.pageActions,\n },\n };\n}\n","import { z } from 'zod';\n\nexport const LogoSchema = z.union([z.string(), z.object({ light: z.string(), dark: z.string() })]);\n\nexport const NavbarSchema = z.object({\n logo: LogoSchema.optional(),\n github: z.url().optional(),\n links: z\n .array(\n z.object({\n label: z.string(),\n href: z.string(),\n })\n )\n .optional(),\n});\n\nexport const FooterSchema = z.object({\n text: z.string().optional(),\n});\n\nexport const SidebarPageSchema = z.object({\n slug: z.string(),\n title: z.string(),\n icon: z.string().optional(),\n});\n\nexport const SidebarGroupSchema = z.object({\n group: z.string(),\n icon: z.string().optional(),\n collapsed: z.boolean().optional(),\n pages: z.array(SidebarPageSchema),\n});\n\nexport const ThemeSchema = z.object({\n primaryHue: z.number().min(0).max(360).optional(),\n darkMode: z.boolean().optional(),\n});\n\nexport const SearchSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const MdxSchema = z.object({\n latex: z.boolean().optional(),\n});\n\nexport const PageActionsSchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nexport const OpenManualConfigSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n contentDir: z.string().optional(),\n outputDir: z.string().optional(),\n siteUrl: z.url().optional(),\n locale: z.string().optional(),\n contentPolicy: z.enum(['strict', 'all']).optional(),\n navbar: NavbarSchema.optional(),\n footer: FooterSchema.optional(),\n sidebar: z.array(SidebarGroupSchema).optional(),\n theme: ThemeSchema.optional(),\n search: SearchSchema.optional(),\n mdx: MdxSchema.optional(),\n pageActions: PageActionsSchema.optional(),\n});\n\nexport type OpenManualConfig = z.infer<typeof OpenManualConfigSchema>;\nexport type NavbarConfig = z.infer<typeof NavbarSchema>;\nexport type FooterConfig = z.infer<typeof FooterSchema>;\nexport type SidebarGroup = z.infer<typeof SidebarGroupSchema>;\nexport type SidebarPage = z.infer<typeof SidebarPageSchema>;\nexport type ThemeConfig = z.infer<typeof ThemeSchema>;\nexport type LogoConfig = z.infer<typeof LogoSchema>;\n\nexport function collectConfiguredSlugs(config: OpenManualConfig): Set<string> {\n const slugs = new Set<string>();\n if (config.sidebar) {\n for (const group of config.sidebar) {\n for (const page of group.pages) {\n slugs.add(page.slug);\n }\n }\n }\n return slugs;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmanual",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"author": "shenjingnan <sjn.code@gmail.com>",
|
|
5
5
|
"description": "AI 友好的开源文档系统框架",
|
|
6
6
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@types/react": "^19.1.6",
|
|
63
63
|
"@types/react-dom": "^19.1.6",
|
|
64
64
|
"@vitest/coverage-v8": "^2.1.8",
|
|
65
|
-
"cspell": "^
|
|
65
|
+
"cspell": "^10.0.0",
|
|
66
66
|
"husky": "^9.1.7",
|
|
67
67
|
"lint-staged": "^16.4.0",
|
|
68
68
|
"release-it": "^17.10.0",
|