@farming-labs/docs 0.0.2-beta.2 → 0.0.2-beta.21

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.
@@ -10,10 +10,14 @@ function detectFramework(cwd) {
10
10
  const pkgPath = path.join(cwd, "package.json");
11
11
  if (!fs.existsSync(pkgPath)) return null;
12
12
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
13
- if ({
13
+ const allDeps = {
14
14
  ...pkg.dependencies,
15
15
  ...pkg.devDependencies
16
- }["next"]) return "nextjs";
16
+ };
17
+ if (allDeps["next"]) return "nextjs";
18
+ if (allDeps["@sveltejs/kit"]) return "sveltekit";
19
+ if (allDeps["astro"]) return "astro";
20
+ if (allDeps["nuxt"]) return "nuxt";
17
21
  return null;
18
22
  }
19
23
  function detectPackageManager(cwd) {
@@ -53,6 +57,27 @@ function readFileSafe(filePath) {
53
57
  if (!fs.existsSync(filePath)) return null;
54
58
  return fs.readFileSync(filePath, "utf-8");
55
59
  }
60
+ /** Common locations where global CSS files live in Next.js / SvelteKit projects. */
61
+ const GLOBAL_CSS_CANDIDATES = [
62
+ "app/globals.css",
63
+ "app/global.css",
64
+ "src/app/globals.css",
65
+ "src/app/global.css",
66
+ "src/app.css",
67
+ "styles/globals.css",
68
+ "styles/global.css",
69
+ "src/styles/globals.css",
70
+ "src/styles/global.css",
71
+ "assets/css/main.css",
72
+ "assets/main.css"
73
+ ];
74
+ /**
75
+ * Find existing global CSS files in the project.
76
+ * Returns relative paths that exist.
77
+ */
78
+ function detectGlobalCssFiles(cwd) {
79
+ return GLOBAL_CSS_CANDIDATES.filter((rel) => fs.existsSync(path.join(cwd, rel)));
80
+ }
56
81
  /**
57
82
  * Run a shell command synchronously, inheriting stdio.
58
83
  */
@@ -107,14 +132,132 @@ function spawnAndWaitFor(command, args, cwd, waitFor, timeoutMs = 6e4) {
107
132
 
108
133
  //#endregion
109
134
  //#region src/cli/templates.ts
135
+ const THEME_INFO = {
136
+ fumadocs: {
137
+ factory: "fumadocs",
138
+ nextImport: "@farming-labs/theme",
139
+ svelteImport: "@farming-labs/svelte-theme",
140
+ astroImport: "@farming-labs/astro-theme",
141
+ nuxtImport: "@farming-labs/nuxt-theme",
142
+ nextCssImport: "default",
143
+ svelteCssTheme: "fumadocs",
144
+ astroCssTheme: "fumadocs",
145
+ nuxtCssTheme: "fumadocs"
146
+ },
147
+ darksharp: {
148
+ factory: "darksharp",
149
+ nextImport: "@farming-labs/theme/darksharp",
150
+ svelteImport: "@farming-labs/svelte-theme/darksharp",
151
+ astroImport: "@farming-labs/astro-theme/darksharp",
152
+ nuxtImport: "@farming-labs/nuxt-theme/darksharp",
153
+ nextCssImport: "darksharp",
154
+ svelteCssTheme: "darksharp",
155
+ astroCssTheme: "darksharp",
156
+ nuxtCssTheme: "darksharp"
157
+ },
158
+ "pixel-border": {
159
+ factory: "pixelBorder",
160
+ nextImport: "@farming-labs/theme/pixel-border",
161
+ svelteImport: "@farming-labs/svelte-theme/pixel-border",
162
+ astroImport: "@farming-labs/astro-theme/pixel-border",
163
+ nuxtImport: "@farming-labs/nuxt-theme/pixel-border",
164
+ nextCssImport: "pixel-border",
165
+ svelteCssTheme: "pixel-border",
166
+ astroCssTheme: "pixel-border",
167
+ nuxtCssTheme: "pixel-border"
168
+ },
169
+ colorful: {
170
+ factory: "colorful",
171
+ nextImport: "@farming-labs/theme/colorful",
172
+ svelteImport: "@farming-labs/svelte-theme/colorful",
173
+ astroImport: "@farming-labs/astro-theme/colorful",
174
+ nuxtImport: "@farming-labs/nuxt-theme/colorful",
175
+ nextCssImport: "colorful",
176
+ svelteCssTheme: "colorful",
177
+ astroCssTheme: "colorful",
178
+ nuxtCssTheme: "colorful"
179
+ },
180
+ darkbold: {
181
+ factory: "darkbold",
182
+ nextImport: "@farming-labs/theme/darkbold",
183
+ svelteImport: "@farming-labs/svelte-theme/darkbold",
184
+ astroImport: "@farming-labs/astro-theme/darkbold",
185
+ nuxtImport: "@farming-labs/nuxt-theme/darkbold",
186
+ nextCssImport: "darkbold",
187
+ svelteCssTheme: "darkbold",
188
+ astroCssTheme: "darkbold",
189
+ nuxtCssTheme: "darkbold"
190
+ },
191
+ shiny: {
192
+ factory: "shiny",
193
+ nextImport: "@farming-labs/theme/shiny",
194
+ svelteImport: "@farming-labs/svelte-theme/shiny",
195
+ astroImport: "@farming-labs/astro-theme/shiny",
196
+ nuxtImport: "@farming-labs/nuxt-theme/shiny",
197
+ nextCssImport: "shiny",
198
+ svelteCssTheme: "shiny",
199
+ astroCssTheme: "shiny",
200
+ nuxtCssTheme: "shiny"
201
+ },
202
+ greentree: {
203
+ factory: "greentree",
204
+ nextImport: "@farming-labs/theme/greentree",
205
+ svelteImport: "@farming-labs/svelte-theme/greentree",
206
+ astroImport: "@farming-labs/astro-theme/greentree",
207
+ nuxtImport: "@farming-labs/nuxt-theme/greentree",
208
+ nextCssImport: "greentree",
209
+ svelteCssTheme: "greentree",
210
+ astroCssTheme: "greentree",
211
+ nuxtCssTheme: "greentree"
212
+ }
213
+ };
214
+ function getThemeInfo(theme) {
215
+ return THEME_INFO[theme] ?? THEME_INFO.fumadocs;
216
+ }
217
+ /** Config import for Next.js app/layout.tsx → root docs.config */
218
+ function nextRootLayoutConfigImport(useAlias) {
219
+ return useAlias ? "@/docs.config" : "../docs.config";
220
+ }
221
+ /** Config import for Next.js app/{entry}/layout.tsx → root docs.config */
222
+ function nextDocsLayoutConfigImport(useAlias) {
223
+ return useAlias ? "@/docs.config" : "../../docs.config";
224
+ }
225
+ /** Config import for SvelteKit src/lib/docs.server.ts → src/lib/docs.config */
226
+ function svelteServerConfigImport(useAlias) {
227
+ return useAlias ? "$lib/docs.config" : "./docs.config";
228
+ }
229
+ /** Config import for SvelteKit src/routes/{entry}/+layout.svelte → src/lib/docs.config */
230
+ function svelteLayoutConfigImport(useAlias) {
231
+ return useAlias ? "$lib/docs.config" : "../../lib/docs.config";
232
+ }
233
+ /** Config import for SvelteKit src/routes/{entry}/[...slug]/+page.svelte → src/lib/docs.config */
234
+ function sveltePageConfigImport(useAlias) {
235
+ return useAlias ? "$lib/docs.config" : "../../../lib/docs.config";
236
+ }
237
+ /** Server import for SvelteKit +layout.server.js → src/lib/docs.server */
238
+ function svelteLayoutServerImport(useAlias) {
239
+ return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
240
+ }
241
+ function astroServerConfigImport(useAlias) {
242
+ return useAlias ? "@/lib/docs.config" : "./docs.config";
243
+ }
244
+ function astroPageConfigImport(useAlias, depth) {
245
+ if (useAlias) return "@/lib/docs.config";
246
+ return `${"../".repeat(depth)}lib/docs.config`;
247
+ }
248
+ function astroPageServerImport(useAlias, depth) {
249
+ if (useAlias) return "@/lib/docs.server";
250
+ return `${"../".repeat(depth)}lib/docs.server`;
251
+ }
110
252
  function docsConfigTemplate(cfg) {
253
+ const t = getThemeInfo(cfg.theme);
111
254
  return `\
112
255
  import { defineDocs } from "@farming-labs/docs";
113
- import { fumadocs } from "@farming-labs/fumadocs";
256
+ import { ${t.factory} } from "${t.nextImport}";
114
257
 
115
258
  export default defineDocs({
116
259
  entry: "${cfg.entry}",
117
- theme: fumadocs({
260
+ theme: ${t.factory}({
118
261
  ui: {
119
262
  colors: { primary: "#6366f1" },
120
263
  },
@@ -151,12 +294,16 @@ function nextConfigMergedTemplate(existingContent) {
151
294
  }
152
295
  return lines.join("\n");
153
296
  }
154
- function rootLayoutTemplate() {
297
+ function rootLayoutTemplate(cfg, globalCssRelPath = "app/globals.css") {
298
+ let cssImport;
299
+ if (globalCssRelPath.startsWith("app/")) cssImport = "./" + globalCssRelPath.slice(4);
300
+ else if (globalCssRelPath.startsWith("src/app/")) cssImport = "./" + globalCssRelPath.slice(8);
301
+ else cssImport = "../" + globalCssRelPath;
155
302
  return `\
156
303
  import type { Metadata } from "next";
157
- import { RootProvider } from "@farming-labs/fumadocs";
158
- import docsConfig from "@/docs.config";
159
- import "./global.css";
304
+ import { RootProvider } from "@farming-labs/theme";
305
+ import docsConfig from "${nextRootLayoutConfigImport(cfg.useAlias)}";
306
+ import "${cssImport}";
160
307
 
161
308
  export const metadata: Metadata = {
162
309
  title: {
@@ -184,26 +331,23 @@ export default function RootLayout({
184
331
  function globalCssTemplate(theme) {
185
332
  return `\
186
333
  @import "tailwindcss";
187
- @import "@farming-labs/${theme}/css";
334
+ @import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
188
335
  `;
189
336
  }
190
- /**
191
- * Inject the fumadocs CSS import into an existing global.css.
192
- * Returns the modified content, or null if already present.
193
- */
194
337
  function injectCssImport(existingContent, theme) {
195
- const importLine = `@import "@farming-labs/${theme}/css";`;
338
+ const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
196
339
  if (existingContent.includes(importLine)) return null;
340
+ if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
197
341
  const lines = existingContent.split("\n");
198
342
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
199
343
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
200
344
  else lines.unshift(importLine);
201
345
  return lines.join("\n");
202
346
  }
203
- function docsLayoutTemplate() {
347
+ function docsLayoutTemplate(cfg) {
204
348
  return `\
205
- import docsConfig from "@/docs.config";
206
- import { createDocsLayout } from "@farming-labs/fumadocs";
349
+ import docsConfig from "${nextDocsLayoutConfigImport(cfg.useAlias)}";
350
+ import { createDocsLayout } from "@farming-labs/theme";
207
351
 
208
352
  export default createDocsLayout(docsConfig);
209
353
  `;
@@ -282,6 +426,7 @@ Start by reading the [Installation](/${cfg.entry}/installation) guide, then foll
282
426
  `;
283
427
  }
284
428
  function installationPageTemplate(cfg) {
429
+ const t = getThemeInfo(cfg.theme);
285
430
  return `\
286
431
  ---
287
432
  title: "Installation"
@@ -308,11 +453,11 @@ Your project includes a \`docs.config.ts\` at the root:
308
453
 
309
454
  \`\`\`ts
310
455
  import { defineDocs } from "@farming-labs/docs";
311
- import { fumadocs } from "@farming-labs/fumadocs";
456
+ import { ${t.factory} } from "${t.nextImport}";
312
457
 
313
458
  export default defineDocs({
314
459
  entry: "${cfg.entry}",
315
- theme: fumadocs({
460
+ theme: ${t.factory}({
316
461
  ui: { colors: { primary: "#6366f1" } },
317
462
  }),
318
463
  });
@@ -339,6 +484,7 @@ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your f
339
484
  `;
340
485
  }
341
486
  function quickstartPageTemplate(cfg) {
487
+ const t = getThemeInfo(cfg.theme);
342
488
  return `\
343
489
  ---
344
490
  title: "Quickstart"
@@ -401,7 +547,7 @@ console.log(greet("World"));
401
547
  Edit \`docs.config.ts\` to change colors, typography, and component defaults:
402
548
 
403
549
  \`\`\`ts
404
- theme: fumadocs({
550
+ theme: ${t.factory}({
405
551
  ui: {
406
552
  colors: { primary: "#22c55e" },
407
553
  },
@@ -419,178 +565,1358 @@ pnpm build
419
565
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
420
566
  `;
421
567
  }
568
+ function svelteDocsConfigTemplate(cfg) {
569
+ const t = getThemeInfo(cfg.theme);
570
+ return `\
571
+ import { defineDocs } from "@farming-labs/docs";
572
+ import { ${t.factory} } from "${t.svelteImport}";
422
573
 
423
- //#endregion
424
- //#region src/cli/init.ts
425
- async function init() {
426
- const cwd = process.cwd();
427
- p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
428
- if (!detectFramework(cwd)) {
429
- p.log.error("Could not detect a supported framework.\n Make sure you have a " + pc.cyan("package.json") + " with " + pc.cyan("next") + " installed.\n Supported frameworks: Next.js");
430
- p.outro(pc.red("Init cancelled."));
431
- process.exit(1);
432
- }
433
- p.log.success(`Detected framework: ${pc.cyan("Next.js")}`);
434
- const theme = await p.select({
435
- message: "Which theme would you like to use?",
436
- options: [{
437
- value: "fumadocs",
438
- label: "Fumadocs",
439
- hint: "Clean, modern docs theme with sidebar, search, and dark mode"
440
- }]
441
- });
442
- if (p.isCancel(theme)) {
443
- p.outro(pc.red("Init cancelled."));
444
- process.exit(0);
445
- }
446
- const entry = await p.text({
447
- message: "Where should your docs live?",
448
- placeholder: "docs",
449
- defaultValue: "docs",
450
- validate: (value) => {
451
- if (!value) return "Entry path is required";
452
- if (value.startsWith("/")) return "Use a relative path (no leading /)";
453
- if (value.includes(" ")) return "Path cannot contain spaces";
454
- }
455
- });
456
- if (p.isCancel(entry)) {
457
- p.outro(pc.red("Init cancelled."));
458
- process.exit(0);
459
- }
460
- const entryPath = entry;
461
- const pkgJson = JSON.parse(readFileSafe(path.join(cwd, "package.json")));
462
- const cfg = {
463
- entry: entryPath,
464
- theme,
465
- projectName: pkgJson.name || "My Project"
466
- };
467
- const s = p.spinner();
468
- s.start("Scaffolding docs files");
469
- const written = [];
470
- const skipped = [];
471
- function write(rel, content, overwrite = false) {
472
- if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
473
- else skipped.push(rel);
474
- }
475
- write("docs.config.ts", docsConfigTemplate(cfg));
476
- const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
477
- if (existingNextConfig) {
478
- const configFile = fileExists(path.join(cwd, "next.config.ts")) ? "next.config.ts" : fileExists(path.join(cwd, "next.config.mjs")) ? "next.config.mjs" : "next.config.js";
479
- const merged = nextConfigMergedTemplate(existingNextConfig);
480
- if (merged !== existingNextConfig) {
481
- writeFileSafe(path.join(cwd, configFile), merged, true);
482
- written.push(configFile + " (updated)");
483
- } else skipped.push(configFile + " (already configured)");
484
- } else write("next.config.ts", nextConfigTemplate());
485
- write("app/layout.tsx", rootLayoutTemplate());
486
- const globalCssPath = path.join(cwd, "app/global.css");
487
- const existingGlobalCss = readFileSafe(globalCssPath);
488
- if (existingGlobalCss) {
489
- const injected = injectCssImport(existingGlobalCss, theme);
490
- if (injected) {
491
- writeFileSafe(globalCssPath, injected, true);
492
- written.push("app/global.css (updated)");
493
- } else skipped.push("app/global.css (already configured)");
494
- } else write("app/global.css", globalCssTemplate(theme));
495
- write(`app/${entryPath}/layout.tsx`, docsLayoutTemplate());
496
- write("postcss.config.mjs", postcssConfigTemplate());
497
- if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
498
- write(`app/${entryPath}/page.mdx`, welcomePageTemplate(cfg));
499
- write(`app/${entryPath}/installation/page.mdx`, installationPageTemplate(cfg));
500
- write(`app/${entryPath}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
501
- s.stop("Files scaffolded");
502
- if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
503
- if (skipped.length > 0) p.log.info(`Skipped ${skipped.length} existing file${skipped.length > 1 ? "s" : ""}:\n` + skipped.map((f) => ` ${pc.dim("-")} ${f}`).join("\n"));
504
- const pm = detectPackageManager(cwd);
505
- p.log.info(`Using ${pc.cyan(pm)} as package manager`);
506
- const s2 = p.spinner();
507
- s2.start("Installing dependencies");
508
- try {
509
- exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/fumadocs`, cwd);
510
- const devDeps = [
511
- "@tailwindcss/postcss",
512
- "postcss",
513
- "tailwindcss",
514
- "@types/mdx",
515
- "@types/node"
516
- ];
517
- const allDeps = {
518
- ...pkgJson.dependencies,
519
- ...pkgJson.devDependencies
520
- };
521
- const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
522
- if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
523
- } catch {
524
- s2.stop("Failed to install dependencies");
525
- p.log.error(`Dependency installation failed. Run the install command manually:
526
- ${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
527
- p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
528
- process.exit(1);
529
- }
530
- s2.stop("Dependencies installed");
531
- const startDev = await p.confirm({
532
- message: "Start the dev server now?",
533
- initialValue: true
534
- });
535
- if (p.isCancel(startDev) || !startDev) {
536
- p.log.info(`You can start the dev server later with:
537
- ${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
538
- p.outro(pc.green("Done! Happy documenting."));
539
- process.exit(0);
540
- }
541
- p.log.step("Starting dev server...");
542
- try {
543
- const child = await spawnAndWaitFor("npx", [
544
- "next",
545
- "dev",
546
- "--webpack"
547
- ], cwd, "Ready", 6e4);
548
- const url = `http://localhost:3000/${entryPath}`;
549
- console.log();
550
- p.log.success(`Dev server is running! Your docs are live at:\n\n ${pc.cyan(pc.underline(url))}\n\n Press ${pc.dim("Ctrl+C")} to stop the server.`);
551
- p.outro(pc.green("Happy documenting!"));
552
- await new Promise((resolve) => {
553
- child.on("close", () => resolve());
554
- process.on("SIGINT", () => {
555
- child.kill("SIGINT");
556
- resolve();
557
- });
558
- process.on("SIGTERM", () => {
559
- child.kill("SIGTERM");
560
- resolve();
561
- });
562
- });
563
- } catch (err) {
564
- p.log.error(`Could not start dev server. Try running manually:
565
- ${pc.cyan("npx next dev --webpack")}`);
566
- p.outro(pc.yellow("Setup complete. Start the server manually."));
567
- process.exit(1);
568
- }
574
+ export default defineDocs({
575
+ entry: "${cfg.entry}",
576
+ theme: ${t.factory}({
577
+ ui: {
578
+ colors: { primary: "#6366f1" },
579
+ },
580
+ }),
581
+
582
+ nav: {
583
+ title: "${cfg.projectName}",
584
+ url: "/${cfg.entry}",
585
+ },
586
+
587
+ breadcrumb: { enabled: true },
588
+
589
+ metadata: {
590
+ titleTemplate: "%s ${cfg.projectName}",
591
+ description: "Documentation for ${cfg.projectName}",
592
+ },
593
+ });
594
+ `;
595
+ }
596
+ function svelteDocsServerTemplate(cfg) {
597
+ return `\
598
+ import { createDocsServer } from "@farming-labs/svelte/server";
599
+ import config from "${svelteServerConfigImport(cfg.useAlias)}";
600
+
601
+ // preload for production
602
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx,svx}", {
603
+ query: "?raw",
604
+ import: "default",
605
+ eager: true,
606
+ }) as Record<string, string>;
607
+
608
+ export const { load, GET, POST } = createDocsServer({
609
+ ...config,
610
+ _preloadedContent: contentFiles,
611
+ });
612
+ `;
569
613
  }
614
+ function svelteDocsLayoutTemplate(cfg) {
615
+ return `\
616
+ <script>
617
+ import { DocsLayout } from "@farming-labs/svelte-theme";
618
+ import config from "${svelteLayoutConfigImport(cfg.useAlias)}";
570
619
 
571
- //#endregion
572
- //#region src/cli/index.ts
573
- const command = process.argv.slice(2)[0];
574
- async function main() {
575
- if (!command || command === "init") await init();
576
- else if (command === "--help" || command === "-h") printHelp();
577
- else if (command === "--version" || command === "-v") printVersion();
578
- else {
579
- console.error(pc.red(`Unknown command: ${command}`));
580
- console.error();
581
- printHelp();
582
- process.exit(1);
583
- }
620
+ let { data, children } = $props();
621
+ <\/script>
622
+
623
+ <DocsLayout tree={data.tree} {config}>
624
+ {@render children()}
625
+ </DocsLayout>
626
+ `;
584
627
  }
585
- function printHelp() {
586
- console.log(`
587
- ${pc.bold("@farming-labs/docs")} — Documentation framework CLI
628
+ function svelteDocsLayoutServerTemplate(cfg) {
629
+ return `\
630
+ export { load } from "${svelteLayoutServerImport(cfg.useAlias)}";
631
+ `;
632
+ }
633
+ function svelteDocsPageTemplate(cfg) {
634
+ return `\
635
+ <script>
636
+ import { DocsContent } from "@farming-labs/svelte-theme";
637
+ import config from "${sveltePageConfigImport(cfg.useAlias)}";
588
638
 
589
- ${pc.dim("Usage:")}
590
- npx @farming-labs/docs ${pc.cyan("<command>")}
639
+ let { data } = $props();
640
+ <\/script>
591
641
 
592
- ${pc.dim("Commands:")}
593
- ${pc.cyan("init")} Scaffold docs in your project (default)
642
+ <DocsContent {data} {config} />
643
+ `;
644
+ }
645
+ function svelteRootLayoutTemplate(globalCssRelPath) {
646
+ let cssImport;
647
+ if (globalCssRelPath.startsWith("src/")) cssImport = "./" + globalCssRelPath.slice(4);
648
+ else cssImport = "../" + globalCssRelPath;
649
+ return `\
650
+ <script>
651
+ import "${cssImport}";
652
+
653
+ let { children } = $props();
654
+ <\/script>
655
+
656
+ {@render children()}
657
+ `;
658
+ }
659
+ function svelteGlobalCssTemplate(theme) {
660
+ return `\
661
+ @import "@farming-labs/svelte-theme/${theme}/css";
662
+ `;
663
+ }
664
+ function svelteCssImportLine(theme) {
665
+ return `@import "@farming-labs/svelte-theme/${theme}/css";`;
666
+ }
667
+ function injectSvelteCssImport(existingContent, theme) {
668
+ const importLine = svelteCssImportLine(theme);
669
+ if (existingContent.includes(importLine)) return null;
670
+ const lines = existingContent.split("\n");
671
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
672
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
673
+ else lines.unshift(importLine);
674
+ return lines.join("\n");
675
+ }
676
+ function svelteWelcomePageTemplate(cfg) {
677
+ return `\
678
+ ---
679
+ title: "Documentation"
680
+ description: "Welcome to ${cfg.projectName} documentation"
681
+ ---
682
+
683
+ # Welcome to ${cfg.projectName}
684
+
685
+ Get started with our documentation. Browse the pages on the left to learn more.
686
+
687
+ ## Overview
688
+
689
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
690
+
691
+ ## Features
692
+
693
+ - **Markdown Support** — Write docs with standard Markdown
694
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
695
+ - **Dark Mode** — Built-in theme switching
696
+ - **Search** — Full-text search across all pages
697
+ - **Responsive** — Works on any screen size
698
+
699
+ ---
700
+
701
+ ## Next Steps
702
+
703
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
704
+ `;
705
+ }
706
+ function svelteInstallationPageTemplate(cfg) {
707
+ const t = getThemeInfo(cfg.theme);
708
+ return `\
709
+ ---
710
+ title: "Installation"
711
+ description: "How to install and set up ${cfg.projectName}"
712
+ ---
713
+
714
+ # Installation
715
+
716
+ Follow these steps to install and configure ${cfg.projectName}.
717
+
718
+ ## Prerequisites
719
+
720
+ - Node.js 18+
721
+ - A package manager (pnpm, npm, or yarn)
722
+
723
+ ## Install Dependencies
724
+
725
+ \`\`\`bash
726
+ pnpm add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
727
+ \`\`\`
728
+
729
+ ## Configuration
730
+
731
+ Your project includes a \`docs.config.ts\` in \`src/lib/\`:
732
+
733
+ \`\`\`ts title="src/lib/docs.config.ts"
734
+ import { defineDocs } from "@farming-labs/docs";
735
+ import { ${t.factory} } from "${t.svelteImport}";
736
+
737
+ export default defineDocs({
738
+ entry: "${cfg.entry}",
739
+ contentDir: "${cfg.entry}",
740
+ theme: ${t.factory}({
741
+ ui: { colors: { primary: "#6366f1" } },
742
+ }),
743
+ });
744
+ \`\`\`
745
+
746
+ ## Project Structure
747
+
748
+ \`\`\`
749
+ ${cfg.entry}/ # Markdown content
750
+ page.md # /${cfg.entry}
751
+ installation/
752
+ page.md # /${cfg.entry}/installation
753
+ quickstart/
754
+ page.md # /${cfg.entry}/quickstart
755
+ src/
756
+ lib/
757
+ docs.config.ts # Docs configuration
758
+ docs.server.ts # Server-side docs loader
759
+ routes/
760
+ ${cfg.entry}/
761
+ +layout.svelte # Docs layout
762
+ +layout.server.js # Layout data loader
763
+ [...slug]/
764
+ +page.svelte # Dynamic doc page
765
+ \`\`\`
766
+
767
+ ## What's Next?
768
+
769
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
770
+ `;
771
+ }
772
+ function svelteQuickstartPageTemplate(cfg) {
773
+ const t = getThemeInfo(cfg.theme);
774
+ return `\
775
+ ---
776
+ title: "Quickstart"
777
+ description: "Get up and running in minutes"
778
+ ---
779
+
780
+ # Quickstart
781
+
782
+ This guide walks you through creating your first documentation page.
783
+
784
+ ## Creating a Page
785
+
786
+ Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
787
+
788
+ \`\`\`bash
789
+ mkdir -p ${cfg.entry}/my-page
790
+ \`\`\`
791
+
792
+ Then create \`${cfg.entry}/my-page/page.md\`:
793
+
794
+ \`\`\`md
795
+ ---
796
+ title: "My Page"
797
+ description: "A custom documentation page"
798
+ ---
799
+
800
+ # My Page
801
+
802
+ Write your content here using **Markdown**.
803
+ \`\`\`
804
+
805
+ Your page is now available at \`/${cfg.entry}/my-page\`.
806
+
807
+ ## Code Blocks
808
+
809
+ Code blocks are automatically syntax-highlighted:
810
+
811
+ \`\`\`typescript
812
+ function greet(name: string): string {
813
+ return \\\`Hello, \\\${name}!\\\`;
814
+ }
815
+
816
+ console.log(greet("World"));
817
+ \`\`\`
818
+
819
+ ## Customizing the Theme
820
+
821
+ Edit \`src/lib/docs.config.ts\` to change colors, typography, and component defaults:
822
+
823
+ \`\`\`ts title="src/lib/docs.config.ts"
824
+ theme: ${t.factory}({
825
+ ui: {
826
+ colors: { primary: "#22c55e" },
827
+ },
828
+ }),
829
+ \`\`\`
830
+
831
+ ## Deploying
832
+
833
+ Build your docs for production:
834
+
835
+ \`\`\`bash
836
+ pnpm build
837
+ \`\`\`
838
+
839
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
840
+ `;
841
+ }
842
+ function astroDocsConfigTemplate(cfg) {
843
+ const t = getThemeInfo(cfg.theme);
844
+ return `\
845
+ import { defineDocs } from "@farming-labs/docs";
846
+ import { ${t.factory} } from "${t.astroImport}";
847
+
848
+ export default defineDocs({
849
+ entry: "${cfg.entry}",
850
+ contentDir: "${cfg.entry}",
851
+ theme: ${t.factory}({
852
+ ui: {
853
+ colors: { primary: "#6366f1" },
854
+ },
855
+ }),
856
+
857
+ nav: {
858
+ title: "${cfg.projectName}",
859
+ url: "/${cfg.entry}",
860
+ },
861
+
862
+ breadcrumb: { enabled: true },
863
+
864
+ metadata: {
865
+ titleTemplate: "%s – ${cfg.projectName}",
866
+ description: "Documentation for ${cfg.projectName}",
867
+ },
868
+ });
869
+ `;
870
+ }
871
+ function astroDocsServerTemplate(cfg) {
872
+ return `\
873
+ import { createDocsServer } from "@farming-labs/astro/server";
874
+ import config from "${astroServerConfigImport(cfg.useAlias)}";
875
+
876
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
877
+ query: "?raw",
878
+ import: "default",
879
+ eager: true,
880
+ }) as Record<string, string>;
881
+
882
+ export const { load, GET, POST } = createDocsServer({
883
+ ...config,
884
+ _preloadedContent: contentFiles,
885
+ });
886
+ `;
887
+ }
888
+ const ASTRO_ADAPTER_INFO = {
889
+ vercel: {
890
+ pkg: "@astrojs/vercel",
891
+ import: "@astrojs/vercel"
892
+ },
893
+ netlify: {
894
+ pkg: "@astrojs/netlify",
895
+ import: "@astrojs/netlify"
896
+ },
897
+ node: {
898
+ pkg: "@astrojs/node",
899
+ import: "@astrojs/node"
900
+ },
901
+ cloudflare: {
902
+ pkg: "@astrojs/cloudflare",
903
+ import: "@astrojs/cloudflare"
904
+ }
905
+ };
906
+ function getAstroAdapterPkg(adapter) {
907
+ return ASTRO_ADAPTER_INFO[adapter]?.pkg ?? ASTRO_ADAPTER_INFO.vercel.pkg;
908
+ }
909
+ function astroConfigTemplate(adapter = "vercel") {
910
+ const info = ASTRO_ADAPTER_INFO[adapter] ?? ASTRO_ADAPTER_INFO.vercel;
911
+ const adapterCall = adapter === "node" ? `${adapter}({ mode: "standalone" })` : `${adapter}()`;
912
+ return `\
913
+ import { defineConfig } from "astro/config";
914
+ import ${adapter} from "${info.import}";
915
+
916
+ export default defineConfig({
917
+ output: "server",
918
+ adapter: ${adapterCall},
919
+ });
920
+ `;
921
+ }
922
+ function astroDocsPageTemplate(cfg) {
923
+ return `\
924
+ ---
925
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
926
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
927
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
928
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
929
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
930
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
931
+
932
+ const data = await load(Astro.url.pathname);
933
+ ---
934
+
935
+ <html lang="en">
936
+ <head>
937
+ <meta charset="utf-8" />
938
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
939
+ <title>{data.title} – Docs</title>
940
+ </head>
941
+ <body>
942
+ <DocsLayout tree={data.tree} config={config}>
943
+ <DocsContent data={data} config={config} />
944
+ </DocsLayout>
945
+ <SearchDialog config={config} />
946
+ </body>
947
+ </html>
948
+ `;
949
+ }
950
+ function astroDocsIndexTemplate(cfg) {
951
+ return `\
952
+ ---
953
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
954
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
955
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
956
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
957
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
958
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
959
+
960
+ const data = await load(Astro.url.pathname);
961
+ ---
962
+
963
+ <html lang="en">
964
+ <head>
965
+ <meta charset="utf-8" />
966
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
967
+ <title>{data.title} – Docs</title>
968
+ </head>
969
+ <body>
970
+ <DocsLayout tree={data.tree} config={config}>
971
+ <DocsContent data={data} config={config} />
972
+ </DocsLayout>
973
+ <SearchDialog config={config} />
974
+ </body>
975
+ </html>
976
+ `;
977
+ }
978
+ function astroApiRouteTemplate(cfg) {
979
+ return `\
980
+ import type { APIRoute } from "astro";
981
+ import { GET as docsGET, POST as docsPOST } from "${astroPageServerImport(cfg.useAlias, 2)}";
982
+
983
+ export const GET: APIRoute = async ({ request }) => {
984
+ return docsGET({ request });
985
+ };
986
+
987
+ export const POST: APIRoute = async ({ request }) => {
988
+ return docsPOST({ request });
989
+ };
990
+ `;
991
+ }
992
+ function astroGlobalCssTemplate(theme) {
993
+ return `\
994
+ @import "@farming-labs/astro-theme/${theme}/css";
995
+ `;
996
+ }
997
+ function astroCssImportLine(theme) {
998
+ return `@import "@farming-labs/astro-theme/${theme}/css";`;
999
+ }
1000
+ function injectAstroCssImport(existingContent, theme) {
1001
+ const importLine = astroCssImportLine(theme);
1002
+ if (existingContent.includes(importLine)) return null;
1003
+ const lines = existingContent.split("\n");
1004
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1005
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
1006
+ else lines.unshift(importLine);
1007
+ return lines.join("\n");
1008
+ }
1009
+ function astroWelcomePageTemplate(cfg) {
1010
+ return `\
1011
+ ---
1012
+ title: "Documentation"
1013
+ description: "Welcome to ${cfg.projectName} documentation"
1014
+ ---
1015
+
1016
+ # Welcome to ${cfg.projectName}
1017
+
1018
+ Get started with our documentation. Browse the pages on the left to learn more.
1019
+
1020
+ ## Overview
1021
+
1022
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
1023
+
1024
+ ## Features
1025
+
1026
+ - **Markdown Support** — Write docs with standard Markdown
1027
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
1028
+ - **Dark Mode** — Built-in theme switching
1029
+ - **Search** — Full-text search across all pages
1030
+ - **Responsive** — Works on any screen size
1031
+
1032
+ ---
1033
+
1034
+ ## Next Steps
1035
+
1036
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
1037
+ `;
1038
+ }
1039
+ function astroInstallationPageTemplate(cfg) {
1040
+ const t = getThemeInfo(cfg.theme);
1041
+ return `\
1042
+ ---
1043
+ title: "Installation"
1044
+ description: "How to install and set up ${cfg.projectName}"
1045
+ ---
1046
+
1047
+ # Installation
1048
+
1049
+ Follow these steps to install and configure ${cfg.projectName}.
1050
+
1051
+ ## Prerequisites
1052
+
1053
+ - Node.js 18+
1054
+ - A package manager (pnpm, npm, or yarn)
1055
+
1056
+ ## Install Dependencies
1057
+
1058
+ \\\`\\\`\\\`bash
1059
+ pnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
1060
+ \\\`\\\`\\\`
1061
+
1062
+ ## Configuration
1063
+
1064
+ Your project includes a \\\`docs.config.ts\\\` in \\\`src/lib/\\\`:
1065
+
1066
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1067
+ import { defineDocs } from "@farming-labs/docs";
1068
+ import { ${t.factory} } from "${t.astroImport}";
1069
+
1070
+ export default defineDocs({
1071
+ entry: "${cfg.entry}",
1072
+ contentDir: "${cfg.entry}",
1073
+ theme: ${t.factory}({
1074
+ ui: { colors: { primary: "#6366f1" } },
1075
+ }),
1076
+ });
1077
+ \\\`\\\`\\\`
1078
+
1079
+ ## Project Structure
1080
+
1081
+ \\\`\\\`\\\`
1082
+ ${cfg.entry}/ # Markdown content
1083
+ page.md # /${cfg.entry}
1084
+ installation/
1085
+ page.md # /${cfg.entry}/installation
1086
+ quickstart/
1087
+ page.md # /${cfg.entry}/quickstart
1088
+ src/
1089
+ lib/
1090
+ docs.config.ts # Docs configuration
1091
+ docs.server.ts # Server-side docs loader
1092
+ pages/
1093
+ ${cfg.entry}/
1094
+ index.astro # Docs index page
1095
+ [...slug].astro # Dynamic doc page
1096
+ api/
1097
+ ${cfg.entry}.ts # Search/AI API route
1098
+ \\\`\\\`\\\`
1099
+
1100
+ ## What's Next?
1101
+
1102
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1103
+ `;
1104
+ }
1105
+ function astroQuickstartPageTemplate(cfg) {
1106
+ const t = getThemeInfo(cfg.theme);
1107
+ return `\
1108
+ ---
1109
+ title: "Quickstart"
1110
+ description: "Get up and running in minutes"
1111
+ ---
1112
+
1113
+ # Quickstart
1114
+
1115
+ This guide walks you through creating your first documentation page.
1116
+
1117
+ ## Creating a Page
1118
+
1119
+ Create a new folder under \\\`${cfg.entry}/\\\` with a \\\`page.md\\\` file:
1120
+
1121
+ \\\`\\\`\\\`bash
1122
+ mkdir -p ${cfg.entry}/my-page
1123
+ \\\`\\\`\\\`
1124
+
1125
+ Then create \\\`${cfg.entry}/my-page/page.md\\\`:
1126
+
1127
+ \\\`\\\`\\\`md
1128
+ ---
1129
+ title: "My Page"
1130
+ description: "A custom documentation page"
1131
+ ---
1132
+
1133
+ # My Page
1134
+
1135
+ Write your content here using **Markdown**.
1136
+ \\\`\\\`\\\`
1137
+
1138
+ Your page is now available at \\\`/${cfg.entry}/my-page\\\`.
1139
+
1140
+ ## Customizing the Theme
1141
+
1142
+ Edit \\\`src/lib/docs.config.ts\\\` to change colors, typography, and component defaults:
1143
+
1144
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1145
+ theme: ${t.factory}({
1146
+ ui: {
1147
+ colors: { primary: "#22c55e" },
1148
+ },
1149
+ }),
1150
+ \\\`\\\`\\\`
1151
+
1152
+ ## Deploying
1153
+
1154
+ Build your docs for production:
1155
+
1156
+ \\\`\\\`\\\`bash
1157
+ pnpm build
1158
+ \\\`\\\`\\\`
1159
+
1160
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1161
+ `;
1162
+ }
1163
+ function nuxtDocsConfigTemplate(cfg) {
1164
+ const t = getThemeInfo(cfg.theme);
1165
+ return `\
1166
+ import { defineDocs } from "@farming-labs/docs";
1167
+ import { ${t.factory} } from "${t.nuxtImport}";
1168
+
1169
+ export default defineDocs({
1170
+ entry: "${cfg.entry}",
1171
+ contentDir: "${cfg.entry}",
1172
+ theme: ${t.factory}({
1173
+ ui: {
1174
+ colors: { primary: "#6366f1" },
1175
+ },
1176
+ }),
1177
+
1178
+ nav: {
1179
+ title: "${cfg.projectName}",
1180
+ url: "/${cfg.entry}",
1181
+ },
1182
+
1183
+ breadcrumb: { enabled: true },
1184
+
1185
+ metadata: {
1186
+ titleTemplate: "%s – ${cfg.projectName}",
1187
+ description: "Documentation for ${cfg.projectName}",
1188
+ },
1189
+ });
1190
+ `;
1191
+ }
1192
+ function nuxtDocsServerTemplate(cfg) {
1193
+ return `\
1194
+ import { createDocsServer } from "@farming-labs/nuxt/server";
1195
+ import config from "../../docs.config";
1196
+
1197
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
1198
+ query: "?raw",
1199
+ import: "default",
1200
+ eager: true,
1201
+ }) as Record<string, string>;
1202
+
1203
+ export const docsServer = createDocsServer({
1204
+ ...config,
1205
+ _preloadedContent: contentFiles,
1206
+ });
1207
+ `;
1208
+ }
1209
+ function nuxtServerApiDocsGetTemplate() {
1210
+ return `\
1211
+ import { getRequestURL } from "h3";
1212
+ import { docsServer } from "../utils/docs-server";
1213
+
1214
+ export default defineEventHandler((event) => {
1215
+ const url = getRequestURL(event);
1216
+ const request = new Request(url.href, {
1217
+ method: event.method,
1218
+ headers: event.headers,
1219
+ });
1220
+ return docsServer.GET({ request });
1221
+ });
1222
+ `;
1223
+ }
1224
+ function nuxtServerApiDocsPostTemplate() {
1225
+ return `\
1226
+ import { getRequestURL, readRawBody } from "h3";
1227
+ import { docsServer } from "../utils/docs-server";
1228
+
1229
+ export default defineEventHandler(async (event) => {
1230
+ const url = getRequestURL(event);
1231
+ const body = await readRawBody(event);
1232
+ const request = new Request(url.href, {
1233
+ method: "POST",
1234
+ headers: event.headers,
1235
+ body: body ?? undefined,
1236
+ });
1237
+ return docsServer.POST({ request });
1238
+ });
1239
+ `;
1240
+ }
1241
+ function nuxtServerApiDocsLoadTemplate() {
1242
+ return `\
1243
+ import { getQuery } from "h3";
1244
+ import { docsServer } from "../../utils/docs-server";
1245
+
1246
+ export default defineEventHandler(async (event) => {
1247
+ const query = getQuery(event);
1248
+ const pathname = (query.pathname as string) ?? "/docs";
1249
+ return docsServer.load(pathname);
1250
+ });
1251
+ `;
1252
+ }
1253
+ function nuxtDocsPageTemplate(cfg) {
1254
+ return `\
1255
+ <script setup lang="ts">
1256
+ import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
1257
+ import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
1258
+
1259
+ const route = useRoute();
1260
+ const pathname = computed(() => route.path);
1261
+
1262
+ const { data, error } = await useAsyncData(\`docs-\${pathname.value}\`, () =>
1263
+ $fetch("/api/docs/load", {
1264
+ query: { pathname: pathname.value },
1265
+ })
1266
+ );
1267
+
1268
+ if (error.value) {
1269
+ throw createError({
1270
+ statusCode: 404,
1271
+ statusMessage: "Page not found",
1272
+ });
1273
+ }
1274
+ <\/script>
1275
+
1276
+ <template>
1277
+ <div v-if="data" class="fd-docs-wrapper">
1278
+ <DocsLayout :tree="data.tree" :config="config">
1279
+ <DocsContent :data="data" :config="config" />
1280
+ </DocsLayout>
1281
+ </div>
1282
+ </template>
1283
+ `;
1284
+ }
1285
+ function nuxtConfigTemplate(cfg) {
1286
+ return `\
1287
+ export default defineNuxtConfig({
1288
+ compatibilityDate: "2024-11-01",
1289
+
1290
+ css: ["@farming-labs/nuxt-theme/${getThemeInfo(cfg.theme).nuxtCssTheme}/css"],
1291
+
1292
+ vite: {
1293
+ optimizeDeps: {
1294
+ include: ["@farming-labs/docs", "@farming-labs/nuxt", "@farming-labs/nuxt-theme"],
1295
+ },
1296
+ },
1297
+
1298
+ nitro: {
1299
+ moduleSideEffects: ["@farming-labs/nuxt/server"],
1300
+ },
1301
+ });
1302
+ `;
1303
+ }
1304
+ function nuxtWelcomePageTemplate(cfg) {
1305
+ return `\
1306
+ ---
1307
+ order: 1
1308
+ title: Documentation
1309
+ description: Welcome to ${cfg.projectName} documentation
1310
+ icon: book
1311
+ ---
1312
+
1313
+ # Welcome to ${cfg.projectName}
1314
+
1315
+ Get started with our documentation. Browse the pages on the left to learn more.
1316
+
1317
+ ## Overview
1318
+
1319
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
1320
+
1321
+ ## Features
1322
+
1323
+ - **Markdown Support** — Write docs with standard Markdown
1324
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
1325
+ - **Dark Mode** — Built-in theme switching
1326
+ - **Search** — Full-text search across all pages (⌘K)
1327
+ - **Responsive** — Works on any screen size
1328
+
1329
+ ---
1330
+
1331
+ ## Next Steps
1332
+
1333
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
1334
+ `;
1335
+ }
1336
+ function nuxtInstallationPageTemplate(cfg) {
1337
+ const t = getThemeInfo(cfg.theme);
1338
+ return `\
1339
+ ---
1340
+ order: 3
1341
+ title: Installation
1342
+ description: How to install and set up ${cfg.projectName}
1343
+ icon: terminal
1344
+ ---
1345
+
1346
+ # Installation
1347
+
1348
+ Follow these steps to install and configure ${cfg.projectName}.
1349
+
1350
+ ## Prerequisites
1351
+
1352
+ - Node.js 18+
1353
+ - A package manager (pnpm, npm, or yarn)
1354
+
1355
+ ## Install Dependencies
1356
+
1357
+ \`\`\`bash
1358
+ pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
1359
+ \`\`\`
1360
+
1361
+ ## Configuration
1362
+
1363
+ Your project includes a \`docs.config.ts\` at the root:
1364
+
1365
+ \`\`\`ts title="docs.config.ts"
1366
+ import { defineDocs } from "@farming-labs/docs";
1367
+ import { ${t.factory} } from "${t.nuxtImport}";
1368
+
1369
+ export default defineDocs({
1370
+ entry: "${cfg.entry}",
1371
+ contentDir: "${cfg.entry}",
1372
+ theme: ${t.factory}({
1373
+ ui: { colors: { primary: "#6366f1" } },
1374
+ }),
1375
+ });
1376
+ \`\`\`
1377
+
1378
+ ## Project Structure
1379
+
1380
+ \`\`\`
1381
+ ${cfg.entry}/ # Markdown content
1382
+ page.md
1383
+ installation/page.md
1384
+ quickstart/page.md
1385
+ server/
1386
+ utils/docs-server.ts # createDocsServer + preloaded content
1387
+ api/docs/
1388
+ load.get.ts # Page data API
1389
+ docs.get.ts # Search API
1390
+ docs.post.ts # AI chat API
1391
+ pages/
1392
+ ${cfg.entry}/[[...slug]].vue # Docs catch-all page
1393
+ docs.config.ts
1394
+ nuxt.config.ts
1395
+ \`\`\`
1396
+
1397
+ ## What's Next?
1398
+
1399
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1400
+ `;
1401
+ }
1402
+ function nuxtQuickstartPageTemplate(cfg) {
1403
+ const t = getThemeInfo(cfg.theme);
1404
+ return `\
1405
+ ---
1406
+ order: 2
1407
+ title: Quickstart
1408
+ description: Get up and running in minutes
1409
+ icon: rocket
1410
+ ---
1411
+
1412
+ # Quickstart
1413
+
1414
+ This guide walks you through creating your first documentation page.
1415
+
1416
+ ## Creating a Page
1417
+
1418
+ Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
1419
+
1420
+ \`\`\`bash
1421
+ mkdir -p ${cfg.entry}/my-page
1422
+ \`\`\`
1423
+
1424
+ Then create \`${cfg.entry}/my-page/page.md\`:
1425
+
1426
+ \`\`\`md
1427
+ ---
1428
+ title: "My Page"
1429
+ description: "A custom documentation page"
1430
+ ---
1431
+
1432
+ # My Page
1433
+
1434
+ Write your content here using **Markdown**.
1435
+ \`\`\`
1436
+
1437
+ Your page is now available at \`/${cfg.entry}/my-page\`.
1438
+
1439
+ ## Customizing the Theme
1440
+
1441
+ Edit \`docs.config.ts\` to change colors and typography:
1442
+
1443
+ \`\`\`ts
1444
+ theme: ${t.factory}({
1445
+ ui: {
1446
+ colors: { primary: "#22c55e" },
1447
+ },
1448
+ }),
1449
+ \`\`\`
1450
+
1451
+ ## Deploying
1452
+
1453
+ Build your docs for production:
1454
+
1455
+ \`\`\`bash
1456
+ pnpm build
1457
+ \`\`\`
1458
+
1459
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1460
+ `;
1461
+ }
1462
+ function nuxtGlobalCssTemplate(theme) {
1463
+ return `\
1464
+ @import "@farming-labs/nuxt-theme/${theme}/css";
1465
+ `;
1466
+ }
1467
+ function nuxtCssImportLine(theme) {
1468
+ return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
1469
+ }
1470
+ function injectNuxtCssImport(existingContent, theme) {
1471
+ const importLine = nuxtCssImportLine(theme);
1472
+ if (existingContent.includes(importLine)) return null;
1473
+ const lines = existingContent.split("\n");
1474
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1475
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
1476
+ else lines.unshift(importLine);
1477
+ return lines.join("\n");
1478
+ }
1479
+
1480
+ //#endregion
1481
+ //#region src/cli/init.ts
1482
+ async function init() {
1483
+ const cwd = process.cwd();
1484
+ p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
1485
+ let framework = detectFramework(cwd);
1486
+ if (framework) {
1487
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
1488
+ p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
1489
+ } else {
1490
+ p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
1491
+ const picked = await p.select({
1492
+ message: "Which framework are you using?",
1493
+ options: [
1494
+ {
1495
+ value: "nextjs",
1496
+ label: "Next.js",
1497
+ hint: "React framework with App Router"
1498
+ },
1499
+ {
1500
+ value: "sveltekit",
1501
+ label: "SvelteKit",
1502
+ hint: "Svelte framework with file-based routing"
1503
+ },
1504
+ {
1505
+ value: "astro",
1506
+ label: "Astro",
1507
+ hint: "Content-focused framework with island architecture"
1508
+ },
1509
+ {
1510
+ value: "nuxt",
1511
+ label: "Nuxt",
1512
+ hint: "Vue 3 framework with file-based routing and Nitro server"
1513
+ }
1514
+ ]
1515
+ });
1516
+ if (p.isCancel(picked)) {
1517
+ p.outro(pc.red("Init cancelled."));
1518
+ process.exit(0);
1519
+ }
1520
+ framework = picked;
1521
+ }
1522
+ const theme = await p.select({
1523
+ message: "Which theme would you like to use?",
1524
+ options: [
1525
+ {
1526
+ value: "fumadocs",
1527
+ label: "Fumadocs (Default)",
1528
+ hint: "Clean, modern docs theme with sidebar, search, and dark mode"
1529
+ },
1530
+ {
1531
+ value: "darksharp",
1532
+ label: "Darksharp",
1533
+ hint: "All-black, sharp edges, zero-radius look"
1534
+ },
1535
+ {
1536
+ value: "pixel-border",
1537
+ label: "Pixel Border",
1538
+ hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
1539
+ },
1540
+ {
1541
+ value: "colorful",
1542
+ label: "Colorful",
1543
+ hint: "Fumadocs-style neutral theme with description support"
1544
+ },
1545
+ {
1546
+ value: "darkbold",
1547
+ label: "DarkBold",
1548
+ hint: "Pure monochrome, Geist typography, clean minimalism"
1549
+ },
1550
+ {
1551
+ value: "shiny",
1552
+ label: "Shiny",
1553
+ hint: "Glossy, modern look with subtle shimmer effects"
1554
+ },
1555
+ {
1556
+ value: "greentree",
1557
+ label: "GreenTree",
1558
+ hint: "Emerald green accent, Inter font, Mintlify-inspired"
1559
+ }
1560
+ ]
1561
+ });
1562
+ if (p.isCancel(theme)) {
1563
+ p.outro(pc.red("Init cancelled."));
1564
+ process.exit(0);
1565
+ }
1566
+ const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : framework === "sveltekit" ? `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)` : framework === "nuxt" ? `Uses ${pc.cyan("~/")} prefix (Nuxt built-in)` : `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)`;
1567
+ const useAlias = await p.confirm({
1568
+ message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
1569
+ initialValue: false
1570
+ });
1571
+ if (p.isCancel(useAlias)) {
1572
+ p.outro(pc.red("Init cancelled."));
1573
+ process.exit(0);
1574
+ }
1575
+ let astroAdapter;
1576
+ if (framework === "astro") {
1577
+ const adapter = await p.select({
1578
+ message: "Where will you deploy?",
1579
+ options: [
1580
+ {
1581
+ value: "vercel",
1582
+ label: "Vercel",
1583
+ hint: "Recommended for most projects"
1584
+ },
1585
+ {
1586
+ value: "netlify",
1587
+ label: "Netlify"
1588
+ },
1589
+ {
1590
+ value: "cloudflare",
1591
+ label: "Cloudflare Pages"
1592
+ },
1593
+ {
1594
+ value: "node",
1595
+ label: "Node.js / Docker",
1596
+ hint: "Self-hosted standalone server"
1597
+ }
1598
+ ]
1599
+ });
1600
+ if (p.isCancel(adapter)) {
1601
+ p.outro(pc.red("Init cancelled."));
1602
+ process.exit(0);
1603
+ }
1604
+ astroAdapter = adapter;
1605
+ }
1606
+ const entry = await p.text({
1607
+ message: "Where should your docs live?",
1608
+ placeholder: "docs",
1609
+ defaultValue: "docs",
1610
+ validate: (value) => {
1611
+ if (!value) return "Entry path is required";
1612
+ if (value.startsWith("/")) return "Use a relative path (no leading /)";
1613
+ if (value.includes(" ")) return "Path cannot contain spaces";
1614
+ }
1615
+ });
1616
+ if (p.isCancel(entry)) {
1617
+ p.outro(pc.red("Init cancelled."));
1618
+ process.exit(0);
1619
+ }
1620
+ const entryPath = entry;
1621
+ const detectedCssFiles = detectGlobalCssFiles(cwd);
1622
+ let globalCssRelPath;
1623
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
1624
+ if (detectedCssFiles.length === 1) {
1625
+ globalCssRelPath = detectedCssFiles[0];
1626
+ p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
1627
+ } else if (detectedCssFiles.length > 1) {
1628
+ const picked = await p.select({
1629
+ message: "Multiple global CSS files found. Which one should we use?",
1630
+ options: detectedCssFiles.map((f) => ({
1631
+ value: f,
1632
+ label: f
1633
+ }))
1634
+ });
1635
+ if (p.isCancel(picked)) {
1636
+ p.outro(pc.red("Init cancelled."));
1637
+ process.exit(0);
1638
+ }
1639
+ globalCssRelPath = picked;
1640
+ } else {
1641
+ const cssPath = await p.text({
1642
+ message: "Where is your global CSS file?",
1643
+ placeholder: defaultCssPath,
1644
+ defaultValue: defaultCssPath,
1645
+ validate: (value) => {
1646
+ if (!value) return "CSS file path is required";
1647
+ if (!value.endsWith(".css")) return "Path must end with .css";
1648
+ }
1649
+ });
1650
+ if (p.isCancel(cssPath)) {
1651
+ p.outro(pc.red("Init cancelled."));
1652
+ process.exit(0);
1653
+ }
1654
+ globalCssRelPath = cssPath;
1655
+ }
1656
+ const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
1657
+ const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
1658
+ const cfg = {
1659
+ entry: entryPath,
1660
+ theme,
1661
+ projectName: pkgJson.name || "My Project",
1662
+ framework,
1663
+ useAlias,
1664
+ astroAdapter
1665
+ };
1666
+ const s = p.spinner();
1667
+ s.start("Scaffolding docs files");
1668
+ const written = [];
1669
+ const skipped = [];
1670
+ function write(rel, content, overwrite = false) {
1671
+ if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
1672
+ else skipped.push(rel);
1673
+ }
1674
+ if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
1675
+ else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
1676
+ else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
1677
+ else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
1678
+ s.stop("Files scaffolded");
1679
+ if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
1680
+ if (skipped.length > 0) p.log.info(`Skipped ${skipped.length} existing file${skipped.length > 1 ? "s" : ""}:\n` + skipped.map((f) => ` ${pc.dim("-")} ${f}`).join("\n"));
1681
+ const pm = detectPackageManager(cwd);
1682
+ p.log.info(`Using ${pc.cyan(pm)} as package manager`);
1683
+ const s2 = p.spinner();
1684
+ s2.start("Installing dependencies");
1685
+ try {
1686
+ if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
1687
+ else if (framework === "astro") {
1688
+ const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
1689
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
1690
+ } else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
1691
+ else {
1692
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
1693
+ const devDeps = [
1694
+ "@tailwindcss/postcss",
1695
+ "postcss",
1696
+ "tailwindcss",
1697
+ "@types/mdx",
1698
+ "@types/node"
1699
+ ];
1700
+ const allDeps = {
1701
+ ...pkgJson.dependencies,
1702
+ ...pkgJson.devDependencies
1703
+ };
1704
+ const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
1705
+ if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
1706
+ }
1707
+ } catch {
1708
+ s2.stop("Failed to install dependencies");
1709
+ p.log.error(`Dependency installation failed. Run the install command manually:
1710
+ ${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
1711
+ p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
1712
+ process.exit(1);
1713
+ }
1714
+ s2.stop("Dependencies installed");
1715
+ const startDev = await p.confirm({
1716
+ message: "Start the dev server now?",
1717
+ initialValue: true
1718
+ });
1719
+ if (p.isCancel(startDev) || !startDev) {
1720
+ p.log.info(`You can start the dev server later with:
1721
+ ${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
1722
+ p.outro(pc.green("Done! Happy documenting."));
1723
+ process.exit(0);
1724
+ }
1725
+ p.log.step("Starting dev server...");
1726
+ const devCommand = framework === "sveltekit" ? {
1727
+ cmd: "npx",
1728
+ args: ["vite", "dev"],
1729
+ waitFor: "ready"
1730
+ } : framework === "astro" ? {
1731
+ cmd: "npx",
1732
+ args: ["astro", "dev"],
1733
+ waitFor: "ready"
1734
+ } : framework === "nuxt" ? {
1735
+ cmd: "npx",
1736
+ args: ["nuxt", "dev"],
1737
+ waitFor: "Local"
1738
+ } : {
1739
+ cmd: "npx",
1740
+ args: [
1741
+ "next",
1742
+ "dev",
1743
+ "--webpack"
1744
+ ],
1745
+ waitFor: "Ready"
1746
+ };
1747
+ const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
1748
+ try {
1749
+ const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
1750
+ const url = `http://localhost:${defaultPort}/${entryPath}`;
1751
+ console.log();
1752
+ p.log.success(`Dev server is running! Your docs are live at:\n\n ${pc.cyan(pc.underline(url))}\n\n Press ${pc.dim("Ctrl+C")} to stop the server.`);
1753
+ p.outro(pc.green("Happy documenting!"));
1754
+ await new Promise((resolve) => {
1755
+ child.on("close", () => resolve());
1756
+ process.on("SIGINT", () => {
1757
+ child.kill("SIGINT");
1758
+ resolve();
1759
+ });
1760
+ process.on("SIGTERM", () => {
1761
+ child.kill("SIGTERM");
1762
+ resolve();
1763
+ });
1764
+ });
1765
+ } catch (err) {
1766
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
1767
+ p.log.error(`Could not start dev server. Try running manually:
1768
+ ${pc.cyan(manualCmd)}`);
1769
+ p.outro(pc.yellow("Setup complete. Start the server manually."));
1770
+ process.exit(1);
1771
+ }
1772
+ }
1773
+ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
1774
+ write("docs.config.ts", docsConfigTemplate(cfg));
1775
+ const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
1776
+ if (existingNextConfig) {
1777
+ const configFile = fileExists(path.join(cwd, "next.config.ts")) ? "next.config.ts" : fileExists(path.join(cwd, "next.config.mjs")) ? "next.config.mjs" : "next.config.js";
1778
+ const merged = nextConfigMergedTemplate(existingNextConfig);
1779
+ if (merged !== existingNextConfig) {
1780
+ writeFileSafe(path.join(cwd, configFile), merged, true);
1781
+ written.push(configFile + " (updated)");
1782
+ } else skipped.push(configFile + " (already configured)");
1783
+ } else write("next.config.ts", nextConfigTemplate());
1784
+ write("app/layout.tsx", rootLayoutTemplate(cfg, globalCssRelPath));
1785
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1786
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1787
+ if (existingGlobalCss) {
1788
+ const injected = injectCssImport(existingGlobalCss, cfg.theme);
1789
+ if (injected) {
1790
+ writeFileSafe(globalCssAbsPath, injected, true);
1791
+ written.push(globalCssRelPath + " (updated)");
1792
+ } else skipped.push(globalCssRelPath + " (already configured)");
1793
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme));
1794
+ write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
1795
+ write("postcss.config.mjs", postcssConfigTemplate());
1796
+ if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
1797
+ write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
1798
+ write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
1799
+ write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
1800
+ }
1801
+ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
1802
+ write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
1803
+ write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
1804
+ write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
1805
+ write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
1806
+ write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
1807
+ if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
1808
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1809
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1810
+ const cssTheme = {
1811
+ fumadocs: "fumadocs",
1812
+ darksharp: "darksharp",
1813
+ "pixel-border": "pixel-border",
1814
+ colorful: "colorful",
1815
+ darkbold: "darkbold",
1816
+ shiny: "shiny",
1817
+ greentree: "greentree",
1818
+ default: "fumadocs"
1819
+ }[cfg.theme] || "fumadocs";
1820
+ if (existingGlobalCss) {
1821
+ const injected = injectSvelteCssImport(existingGlobalCss, cssTheme);
1822
+ if (injected) {
1823
+ writeFileSafe(globalCssAbsPath, injected, true);
1824
+ written.push(globalCssRelPath + " (updated)");
1825
+ } else skipped.push(globalCssRelPath + " (already configured)");
1826
+ } else write(globalCssRelPath, svelteGlobalCssTemplate(cssTheme));
1827
+ write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
1828
+ write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1829
+ write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1830
+ }
1831
+ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1832
+ write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1833
+ write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1834
+ if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
1835
+ write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
1836
+ write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
1837
+ write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
1838
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1839
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1840
+ const cssTheme = {
1841
+ fumadocs: "fumadocs",
1842
+ darksharp: "darksharp",
1843
+ "pixel-border": "pixel-border",
1844
+ colorful: "colorful",
1845
+ darkbold: "darkbold",
1846
+ shiny: "shiny",
1847
+ greentree: "greentree",
1848
+ default: "fumadocs"
1849
+ }[cfg.theme] || "fumadocs";
1850
+ if (existingGlobalCss) {
1851
+ const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
1852
+ if (injected) {
1853
+ writeFileSafe(globalCssAbsPath, injected, true);
1854
+ written.push(globalCssRelPath + " (updated)");
1855
+ } else skipped.push(globalCssRelPath + " (already configured)");
1856
+ } else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
1857
+ write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
1858
+ write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
1859
+ write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1860
+ }
1861
+ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
1862
+ write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
1863
+ write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
1864
+ write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
1865
+ write("server/api/docs.post.ts", nuxtServerApiDocsPostTemplate());
1866
+ write("server/api/docs/load.get.ts", nuxtServerApiDocsLoadTemplate());
1867
+ write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
1868
+ path.join(cwd, "nuxt.config.ts");
1869
+ if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
1870
+ const cssTheme = {
1871
+ fumadocs: "fumadocs",
1872
+ darksharp: "darksharp",
1873
+ "pixel-border": "pixel-border",
1874
+ colorful: "colorful",
1875
+ darkbold: "darkbold",
1876
+ shiny: "shiny",
1877
+ greentree: "greentree",
1878
+ default: "fumadocs"
1879
+ }[cfg.theme] || "fumadocs";
1880
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1881
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1882
+ if (existingGlobalCss) {
1883
+ const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
1884
+ if (injected) {
1885
+ writeFileSafe(globalCssAbsPath, injected, true);
1886
+ written.push(globalCssRelPath + " (updated)");
1887
+ } else skipped.push(globalCssRelPath + " (already configured)");
1888
+ } else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
1889
+ write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
1890
+ write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
1891
+ write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
1892
+ }
1893
+
1894
+ //#endregion
1895
+ //#region src/cli/index.ts
1896
+ const command = process.argv.slice(2)[0];
1897
+ async function main() {
1898
+ if (!command || command === "init") await init();
1899
+ else if (command === "--help" || command === "-h") printHelp();
1900
+ else if (command === "--version" || command === "-v") printVersion();
1901
+ else {
1902
+ console.error(pc.red(`Unknown command: ${command}`));
1903
+ console.error();
1904
+ printHelp();
1905
+ process.exit(1);
1906
+ }
1907
+ }
1908
+ function printHelp() {
1909
+ console.log(`
1910
+ ${pc.bold("@farming-labs/docs")} — Documentation framework CLI
1911
+
1912
+ ${pc.dim("Usage:")}
1913
+ npx @farming-labs/docs ${pc.cyan("<command>")}
1914
+
1915
+ ${pc.dim("Commands:")}
1916
+ ${pc.cyan("init")} Scaffold docs in your project (default)
1917
+
1918
+ ${pc.dim("Supported frameworks:")}
1919
+ Next.js, SvelteKit, Astro, Nuxt
594
1920
 
595
1921
  ${pc.dim("Options:")}
596
1922
  ${pc.cyan("-h, --help")} Show this help message