@farming-labs/docs 0.0.2-beta.3 → 0.0.2-beta.30

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,16 +57,19 @@ function readFileSafe(filePath) {
53
57
  if (!fs.existsSync(filePath)) return null;
54
58
  return fs.readFileSync(filePath, "utf-8");
55
59
  }
56
- /** Common locations where global CSS files live in Next.js projects. */
60
+ /** Common locations where global CSS files live in Next.js / SvelteKit projects. */
57
61
  const GLOBAL_CSS_CANDIDATES = [
58
62
  "app/globals.css",
59
63
  "app/global.css",
60
64
  "src/app/globals.css",
61
65
  "src/app/global.css",
66
+ "src/app.css",
62
67
  "styles/globals.css",
63
68
  "styles/global.css",
64
69
  "src/styles/globals.css",
65
- "src/styles/global.css"
70
+ "src/styles/global.css",
71
+ "assets/css/main.css",
72
+ "assets/main.css"
66
73
  ];
67
74
  /**
68
75
  * Find existing global CSS files in the project.
@@ -125,14 +132,132 @@ function spawnAndWaitFor(command, args, cwd, waitFor, timeoutMs = 6e4) {
125
132
 
126
133
  //#endregion
127
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
+ }
128
252
  function docsConfigTemplate(cfg) {
253
+ const t = getThemeInfo(cfg.theme);
129
254
  return `\
130
255
  import { defineDocs } from "@farming-labs/docs";
131
- import { fumadocs } from "@farming-labs/fumadocs";
256
+ import { ${t.factory} } from "${t.nextImport}";
132
257
 
133
258
  export default defineDocs({
134
259
  entry: "${cfg.entry}",
135
- theme: fumadocs({
260
+ theme: ${t.factory}({
136
261
  ui: {
137
262
  colors: { primary: "#6366f1" },
138
263
  },
@@ -169,15 +294,15 @@ function nextConfigMergedTemplate(existingContent) {
169
294
  }
170
295
  return lines.join("\n");
171
296
  }
172
- function rootLayoutTemplate(globalCssRelPath = "app/globals.css") {
297
+ function rootLayoutTemplate(cfg, globalCssRelPath = "app/globals.css") {
173
298
  let cssImport;
174
299
  if (globalCssRelPath.startsWith("app/")) cssImport = "./" + globalCssRelPath.slice(4);
175
300
  else if (globalCssRelPath.startsWith("src/app/")) cssImport = "./" + globalCssRelPath.slice(8);
176
301
  else cssImport = "../" + globalCssRelPath;
177
302
  return `\
178
303
  import type { Metadata } from "next";
179
- import { RootProvider } from "@farming-labs/fumadocs";
180
- import docsConfig from "@/docs.config";
304
+ import { RootProvider } from "@farming-labs/theme";
305
+ import docsConfig from "${nextRootLayoutConfigImport(cfg.useAlias)}";
181
306
  import "${cssImport}";
182
307
 
183
308
  export const metadata: Metadata = {
@@ -206,26 +331,23 @@ export default function RootLayout({
206
331
  function globalCssTemplate(theme) {
207
332
  return `\
208
333
  @import "tailwindcss";
209
- @import "@farming-labs/${theme}/css";
334
+ @import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
210
335
  `;
211
336
  }
212
- /**
213
- * Inject the fumadocs CSS import into an existing global.css.
214
- * Returns the modified content, or null if already present.
215
- */
216
337
  function injectCssImport(existingContent, theme) {
217
- const importLine = `@import "@farming-labs/${theme}/css";`;
338
+ const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
218
339
  if (existingContent.includes(importLine)) return null;
340
+ if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
219
341
  const lines = existingContent.split("\n");
220
342
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
221
343
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
222
344
  else lines.unshift(importLine);
223
345
  return lines.join("\n");
224
346
  }
225
- function docsLayoutTemplate() {
347
+ function docsLayoutTemplate(cfg) {
226
348
  return `\
227
- import docsConfig from "@/docs.config";
228
- import { createDocsLayout } from "@farming-labs/fumadocs";
349
+ import docsConfig from "${nextDocsLayoutConfigImport(cfg.useAlias)}";
350
+ import { createDocsLayout } from "@farming-labs/theme";
229
351
 
230
352
  export default createDocsLayout(docsConfig);
231
353
  `;
@@ -241,7 +363,8 @@ const config = {
241
363
  export default config;
242
364
  `;
243
365
  }
244
- function tsconfigTemplate() {
366
+ /** @param useAlias - When false, paths (e.g. @/*) are omitted so no alias is added. */
367
+ function tsconfigTemplate(useAlias = false) {
245
368
  return `\
246
369
  {
247
370
  "compilerOptions": {
@@ -258,8 +381,7 @@ function tsconfigTemplate() {
258
381
  "isolatedModules": true,
259
382
  "jsx": "react-jsx",
260
383
  "incremental": true,
261
- "plugins": [{ "name": "next" }],
262
- "paths": { "@/*": ["./*"] }
384
+ "plugins": [{ "name": "next" }]${useAlias ? ",\n \"paths\": { \"@/*\": [\"./*\"] }" : ""}
263
385
  },
264
386
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
265
387
  "exclude": ["node_modules"]
@@ -304,6 +426,7 @@ Start by reading the [Installation](/${cfg.entry}/installation) guide, then foll
304
426
  `;
305
427
  }
306
428
  function installationPageTemplate(cfg) {
429
+ const t = getThemeInfo(cfg.theme);
307
430
  return `\
308
431
  ---
309
432
  title: "Installation"
@@ -330,11 +453,11 @@ Your project includes a \`docs.config.ts\` at the root:
330
453
 
331
454
  \`\`\`ts
332
455
  import { defineDocs } from "@farming-labs/docs";
333
- import { fumadocs } from "@farming-labs/fumadocs";
456
+ import { ${t.factory} } from "${t.nextImport}";
334
457
 
335
458
  export default defineDocs({
336
459
  entry: "${cfg.entry}",
337
- theme: fumadocs({
460
+ theme: ${t.factory}({
338
461
  ui: { colors: { primary: "#6366f1" } },
339
462
  }),
340
463
  });
@@ -361,6 +484,7 @@ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your f
361
484
  `;
362
485
  }
363
486
  function quickstartPageTemplate(cfg) {
487
+ const t = getThemeInfo(cfg.theme);
364
488
  return `\
365
489
  ---
366
490
  title: "Quickstart"
@@ -423,7 +547,7 @@ console.log(greet("World"));
423
547
  Edit \`docs.config.ts\` to change colors, typography, and component defaults:
424
548
 
425
549
  \`\`\`ts
426
- theme: fumadocs({
550
+ theme: ${t.factory}({
427
551
  ui: {
428
552
  colors: { primary: "#22c55e" },
429
553
  },
@@ -441,216 +565,1451 @@ pnpm build
441
565
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
442
566
  `;
443
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}";
444
573
 
445
- //#endregion
446
- //#region src/cli/init.ts
447
- async function init() {
448
- const cwd = process.cwd();
449
- p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
450
- if (!detectFramework(cwd)) {
451
- 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");
452
- p.outro(pc.red("Init cancelled."));
453
- process.exit(1);
454
- }
455
- p.log.success(`Detected framework: ${pc.cyan("Next.js")}`);
456
- const theme = await p.select({
457
- message: "Which theme would you like to use?",
458
- options: [{
459
- value: "fumadocs",
460
- label: "Fumadocs",
461
- hint: "Clean, modern docs theme with sidebar, search, and dark mode"
462
- }]
463
- });
464
- if (p.isCancel(theme)) {
465
- p.outro(pc.red("Init cancelled."));
466
- process.exit(0);
467
- }
468
- const entry = await p.text({
469
- message: "Where should your docs live?",
470
- placeholder: "docs",
471
- defaultValue: "docs",
472
- validate: (value) => {
473
- if (!value) return "Entry path is required";
474
- if (value.startsWith("/")) return "Use a relative path (no leading /)";
475
- if (value.includes(" ")) return "Path cannot contain spaces";
476
- }
477
- });
478
- if (p.isCancel(entry)) {
479
- p.outro(pc.red("Init cancelled."));
480
- process.exit(0);
481
- }
482
- const entryPath = entry;
483
- const detectedCssFiles = detectGlobalCssFiles(cwd);
484
- let globalCssRelPath;
485
- if (detectedCssFiles.length === 1) {
486
- globalCssRelPath = detectedCssFiles[0];
487
- p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
488
- } else if (detectedCssFiles.length > 1) {
489
- const picked = await p.select({
490
- message: "Multiple global CSS files found. Which one should we use?",
491
- options: detectedCssFiles.map((f) => ({
492
- value: f,
493
- label: f
494
- }))
495
- });
496
- if (p.isCancel(picked)) {
497
- p.outro(pc.red("Init cancelled."));
498
- process.exit(0);
499
- }
500
- globalCssRelPath = picked;
501
- } else {
502
- const cssPath = await p.text({
503
- message: "Where is your global CSS file?",
504
- placeholder: "app/globals.css",
505
- defaultValue: "app/globals.css",
506
- validate: (value) => {
507
- if (!value) return "CSS file path is required";
508
- if (!value.endsWith(".css")) return "Path must end with .css";
509
- }
510
- });
511
- if (p.isCancel(cssPath)) {
512
- p.outro(pc.red("Init cancelled."));
513
- process.exit(0);
514
- }
515
- globalCssRelPath = cssPath;
516
- }
517
- const pkgJson = JSON.parse(readFileSafe(path.join(cwd, "package.json")));
518
- const cfg = {
519
- entry: entryPath,
520
- theme,
521
- projectName: pkgJson.name || "My Project"
522
- };
523
- const s = p.spinner();
524
- s.start("Scaffolding docs files");
525
- const written = [];
526
- const skipped = [];
527
- function write(rel, content, overwrite = false) {
528
- if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
529
- else skipped.push(rel);
530
- }
531
- write("docs.config.ts", docsConfigTemplate(cfg));
532
- const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
533
- if (existingNextConfig) {
534
- 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";
535
- const merged = nextConfigMergedTemplate(existingNextConfig);
536
- if (merged !== existingNextConfig) {
537
- writeFileSafe(path.join(cwd, configFile), merged, true);
538
- written.push(configFile + " (updated)");
539
- } else skipped.push(configFile + " (already configured)");
540
- } else write("next.config.ts", nextConfigTemplate());
541
- write("app/layout.tsx", rootLayoutTemplate(globalCssRelPath));
542
- const globalCssAbsPath = path.join(cwd, globalCssRelPath);
543
- const existingGlobalCss = readFileSafe(globalCssAbsPath);
544
- if (existingGlobalCss) {
545
- const injected = injectCssImport(existingGlobalCss, theme);
546
- if (injected) {
547
- writeFileSafe(globalCssAbsPath, injected, true);
548
- written.push(globalCssRelPath + " (updated)");
549
- } else skipped.push(globalCssRelPath + " (already configured)");
550
- } else write(globalCssRelPath, globalCssTemplate(theme));
551
- write(`app/${entryPath}/layout.tsx`, docsLayoutTemplate());
552
- write("postcss.config.mjs", postcssConfigTemplate());
553
- if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
554
- write(`app/${entryPath}/page.mdx`, welcomePageTemplate(cfg));
555
- write(`app/${entryPath}/installation/page.mdx`, installationPageTemplate(cfg));
556
- write(`app/${entryPath}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
557
- s.stop("Files scaffolded");
558
- if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
559
- 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"));
560
- const pm = detectPackageManager(cwd);
561
- p.log.info(`Using ${pc.cyan(pm)} as package manager`);
562
- const s2 = p.spinner();
563
- s2.start("Installing dependencies");
564
- try {
565
- exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/fumadocs`, cwd);
566
- const devDeps = [
567
- "@tailwindcss/postcss",
568
- "postcss",
569
- "tailwindcss",
570
- "@types/mdx",
571
- "@types/node"
572
- ];
573
- const allDeps = {
574
- ...pkgJson.dependencies,
575
- ...pkgJson.devDependencies
576
- };
577
- const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
578
- if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
579
- } catch {
580
- s2.stop("Failed to install dependencies");
581
- p.log.error(`Dependency installation failed. Run the install command manually:
582
- ${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
583
- p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
584
- process.exit(1);
585
- }
586
- s2.stop("Dependencies installed");
587
- const startDev = await p.confirm({
588
- message: "Start the dev server now?",
589
- initialValue: true
590
- });
591
- if (p.isCancel(startDev) || !startDev) {
592
- p.log.info(`You can start the dev server later with:
593
- ${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
594
- p.outro(pc.green("Done! Happy documenting."));
595
- process.exit(0);
596
- }
597
- p.log.step("Starting dev server...");
598
- try {
599
- const child = await spawnAndWaitFor("npx", [
600
- "next",
601
- "dev",
602
- "--webpack"
603
- ], cwd, "Ready", 6e4);
604
- const url = `http://localhost:3000/${entryPath}`;
605
- console.log();
606
- 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.`);
607
- p.outro(pc.green("Happy documenting!"));
608
- await new Promise((resolve) => {
609
- child.on("close", () => resolve());
610
- process.on("SIGINT", () => {
611
- child.kill("SIGINT");
612
- resolve();
613
- });
614
- process.on("SIGTERM", () => {
615
- child.kill("SIGTERM");
616
- resolve();
617
- });
618
- });
619
- } catch (err) {
620
- p.log.error(`Could not start dev server. Try running manually:
621
- ${pc.cyan("npx next dev --webpack")}`);
622
- p.outro(pc.yellow("Setup complete. Start the server manually."));
623
- process.exit(1);
624
- }
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
+ `;
625
613
  }
614
+ function svelteDocsLayoutTemplate(cfg) {
615
+ return `\
616
+ <script>
617
+ import { DocsLayout } from "@farming-labs/svelte-theme";
618
+ import config from "${svelteLayoutConfigImport(cfg.useAlias)}";
626
619
 
627
- //#endregion
628
- //#region src/cli/index.ts
629
- const command = process.argv.slice(2)[0];
630
- async function main() {
631
- if (!command || command === "init") await init();
632
- else if (command === "--help" || command === "-h") printHelp();
633
- else if (command === "--version" || command === "-v") printVersion();
634
- else {
635
- console.error(pc.red(`Unknown command: ${command}`));
636
- console.error();
637
- printHelp();
638
- process.exit(1);
639
- }
620
+ let { data, children } = $props();
621
+ <\/script>
622
+
623
+ <DocsLayout tree={data.tree} {config}>
624
+ {@render children()}
625
+ </DocsLayout>
626
+ `;
640
627
  }
641
- function printHelp() {
642
- console.log(`
643
- ${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)}";
644
638
 
645
- ${pc.dim("Usage:")}
646
- npx @farming-labs/docs ${pc.cyan("<command>")}
639
+ let { data } = $props();
640
+ <\/script>
647
641
 
648
- ${pc.dim("Commands:")}
649
- ${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
+ const contentDirName = cfg.entry ?? "docs";
1194
+ return `\
1195
+ import { createDocsServer } from "@farming-labs/nuxt/server";
1196
+ import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
1197
+
1198
+ const contentFiles = import.meta.glob("/${contentDirName}/**/*.{md,mdx}", {
1199
+ query: "?raw",
1200
+ import: "default",
1201
+ eager: true,
1202
+ }) as Record<string, string>;
1203
+
1204
+ export const docsServer = createDocsServer({
1205
+ ...config,
1206
+ _preloadedContent: contentFiles,
1207
+ });
1208
+ `;
1209
+ }
1210
+ function nuxtServerApiDocsGetTemplate() {
1211
+ return `\
1212
+ import { getRequestURL } from "h3";
1213
+ import { docsServer } from "../utils/docs-server";
1214
+
1215
+ export default defineEventHandler((event) => {
1216
+ const url = getRequestURL(event);
1217
+ const request = new Request(url.href, {
1218
+ method: event.method,
1219
+ headers: event.headers,
1220
+ });
1221
+ return docsServer.GET({ request });
1222
+ });
1223
+ `;
1224
+ }
1225
+ function nuxtServerApiDocsPostTemplate() {
1226
+ return `\
1227
+ import { getRequestURL, readRawBody } from "h3";
1228
+ import { docsServer } from "../utils/docs-server";
1229
+
1230
+ export default defineEventHandler(async (event) => {
1231
+ const url = getRequestURL(event);
1232
+ const body = await readRawBody(event);
1233
+ const request = new Request(url.href, {
1234
+ method: "POST",
1235
+ headers: event.headers,
1236
+ body: body ?? undefined,
1237
+ });
1238
+ return docsServer.POST({ request });
1239
+ });
1240
+ `;
1241
+ }
1242
+ function nuxtServerApiDocsLoadTemplate() {
1243
+ return `\
1244
+ import { getQuery } from "h3";
1245
+ import { docsServer } from "../../utils/docs-server";
1246
+
1247
+ export default defineEventHandler(async (event) => {
1248
+ const query = getQuery(event);
1249
+ const pathname = (query.pathname as string) ?? "/docs";
1250
+ return docsServer.load(pathname);
1251
+ });
1252
+ `;
1253
+ }
1254
+ function nuxtDocsPageTemplate(cfg) {
1255
+ return `\
1256
+ <script setup lang="ts">
1257
+ import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
1258
+ import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
1259
+
1260
+ const route = useRoute();
1261
+ const pathname = computed(() => route.path);
1262
+
1263
+ const { data, error } = await useAsyncData(\`docs-\${pathname.value}\`, () =>
1264
+ $fetch("/api/docs/load", {
1265
+ query: { pathname: pathname.value },
1266
+ })
1267
+ );
1268
+
1269
+ if (error.value) {
1270
+ throw createError({
1271
+ statusCode: 404,
1272
+ statusMessage: "Page not found",
1273
+ });
1274
+ }
1275
+ <\/script>
1276
+
1277
+ <template>
1278
+ <div v-if="data" class="fd-docs-wrapper">
1279
+ <DocsLayout :tree="data.tree" :config="config">
1280
+ <DocsContent :data="data" :config="config" />
1281
+ </DocsLayout>
1282
+ </div>
1283
+ </template>
1284
+ `;
1285
+ }
1286
+ function nuxtConfigTemplate(cfg) {
1287
+ return `\
1288
+ export default defineNuxtConfig({
1289
+ compatibilityDate: "2024-11-01",
1290
+
1291
+ css: ["@farming-labs/nuxt-theme/${getThemeInfo(cfg.theme).nuxtCssTheme}/css"],
1292
+
1293
+ vite: {
1294
+ optimizeDeps: {
1295
+ include: ["@farming-labs/docs", "@farming-labs/nuxt", "@farming-labs/nuxt-theme"],
1296
+ },
1297
+ },
1298
+
1299
+ nitro: {
1300
+ moduleSideEffects: ["@farming-labs/nuxt/server"],
1301
+ },
1302
+ });
1303
+ `;
1304
+ }
1305
+ function nuxtWelcomePageTemplate(cfg) {
1306
+ return `\
1307
+ ---
1308
+ order: 1
1309
+ title: Documentation
1310
+ description: Welcome to ${cfg.projectName} documentation
1311
+ icon: book
1312
+ ---
1313
+
1314
+ # Welcome to ${cfg.projectName}
1315
+
1316
+ Get started with our documentation. Browse the pages on the left to learn more.
1317
+
1318
+ ## Overview
1319
+
1320
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
1321
+
1322
+ ## Features
1323
+
1324
+ - **Markdown Support** — Write docs with standard Markdown
1325
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
1326
+ - **Dark Mode** — Built-in theme switching
1327
+ - **Search** — Full-text search across all pages (⌘K)
1328
+ - **Responsive** — Works on any screen size
1329
+
1330
+ ---
1331
+
1332
+ ## Next Steps
1333
+
1334
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
1335
+ `;
1336
+ }
1337
+ function nuxtInstallationPageTemplate(cfg) {
1338
+ const t = getThemeInfo(cfg.theme);
1339
+ return `\
1340
+ ---
1341
+ order: 3
1342
+ title: Installation
1343
+ description: How to install and set up ${cfg.projectName}
1344
+ icon: terminal
1345
+ ---
1346
+
1347
+ # Installation
1348
+
1349
+ Follow these steps to install and configure ${cfg.projectName}.
1350
+
1351
+ ## Prerequisites
1352
+
1353
+ - Node.js 18+
1354
+ - A package manager (pnpm, npm, or yarn)
1355
+
1356
+ ## Install Dependencies
1357
+
1358
+ \`\`\`bash
1359
+ pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
1360
+ \`\`\`
1361
+
1362
+ ## Configuration
1363
+
1364
+ Your project includes a \`docs.config.ts\` at the root:
1365
+
1366
+ \`\`\`ts title="docs.config.ts"
1367
+ import { defineDocs } from "@farming-labs/docs";
1368
+ import { ${t.factory} } from "${t.nuxtImport}";
1369
+
1370
+ export default defineDocs({
1371
+ entry: "${cfg.entry}",
1372
+ contentDir: "${cfg.entry}",
1373
+ theme: ${t.factory}({
1374
+ ui: { colors: { primary: "#6366f1" } },
1375
+ }),
1376
+ });
1377
+ \`\`\`
1378
+
1379
+ ## Project Structure
1380
+
1381
+ \`\`\`
1382
+ ${cfg.entry}/ # Markdown content
1383
+ page.md
1384
+ installation/page.md
1385
+ quickstart/page.md
1386
+ server/
1387
+ utils/docs-server.ts # createDocsServer + preloaded content
1388
+ api/docs/
1389
+ load.get.ts # Page data API
1390
+ docs.get.ts # Search API
1391
+ docs.post.ts # AI chat API
1392
+ pages/
1393
+ ${cfg.entry}/[[...slug]].vue # Docs catch-all page
1394
+ docs.config.ts
1395
+ nuxt.config.ts
1396
+ \`\`\`
1397
+
1398
+ ## What's Next?
1399
+
1400
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1401
+ `;
1402
+ }
1403
+ function nuxtQuickstartPageTemplate(cfg) {
1404
+ const t = getThemeInfo(cfg.theme);
1405
+ return `\
1406
+ ---
1407
+ order: 2
1408
+ title: Quickstart
1409
+ description: Get up and running in minutes
1410
+ icon: rocket
1411
+ ---
1412
+
1413
+ # Quickstart
1414
+
1415
+ This guide walks you through creating your first documentation page.
1416
+
1417
+ ## Creating a Page
1418
+
1419
+ Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
1420
+
1421
+ \`\`\`bash
1422
+ mkdir -p ${cfg.entry}/my-page
1423
+ \`\`\`
1424
+
1425
+ Then create \`${cfg.entry}/my-page/page.md\`:
1426
+
1427
+ \`\`\`md
1428
+ ---
1429
+ title: "My Page"
1430
+ description: "A custom documentation page"
1431
+ ---
1432
+
1433
+ # My Page
1434
+
1435
+ Write your content here using **Markdown**.
1436
+ \`\`\`
1437
+
1438
+ Your page is now available at \`/${cfg.entry}/my-page\`.
1439
+
1440
+ ## Customizing the Theme
1441
+
1442
+ Edit \`docs.config.ts\` to change colors and typography:
1443
+
1444
+ \`\`\`ts
1445
+ theme: ${t.factory}({
1446
+ ui: {
1447
+ colors: { primary: "#22c55e" },
1448
+ },
1449
+ }),
1450
+ \`\`\`
1451
+
1452
+ ## Deploying
1453
+
1454
+ Build your docs for production:
1455
+
1456
+ \`\`\`bash
1457
+ pnpm build
1458
+ \`\`\`
1459
+
1460
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1461
+ `;
1462
+ }
1463
+ function nuxtGlobalCssTemplate(theme) {
1464
+ return `\
1465
+ @import "@farming-labs/nuxt-theme/${theme}/css";
1466
+ `;
1467
+ }
1468
+ function nuxtCssImportLine(theme) {
1469
+ return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
1470
+ }
1471
+ function injectNuxtCssImport(existingContent, theme) {
1472
+ const importLine = nuxtCssImportLine(theme);
1473
+ if (existingContent.includes(importLine)) return null;
1474
+ const lines = existingContent.split("\n");
1475
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1476
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
1477
+ else lines.unshift(importLine);
1478
+ return lines.join("\n");
1479
+ }
1480
+
1481
+ //#endregion
1482
+ //#region src/cli/init.ts
1483
+ const EXAMPLES_REPO = "farming-labs/docs";
1484
+ const VALID_TEMPLATES = [
1485
+ "next",
1486
+ "nuxt",
1487
+ "sveltekit",
1488
+ "astro"
1489
+ ];
1490
+ async function init(options = {}) {
1491
+ const cwd = process.cwd();
1492
+ p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
1493
+ if (options.template) {
1494
+ const template = options.template.toLowerCase();
1495
+ if (!VALID_TEMPLATES.includes(template)) {
1496
+ p.log.error(`Invalid ${pc.cyan("--template")}. Use one of: ${VALID_TEMPLATES.map((t) => pc.cyan(t)).join(", ")}`);
1497
+ process.exit(1);
1498
+ }
1499
+ let projectName = options.name?.trim();
1500
+ if (!projectName) {
1501
+ const nameAnswer = await p.text({
1502
+ message: "Project name? (we'll create this folder and bootstrap the app here)",
1503
+ placeholder: "my-docs",
1504
+ defaultValue: "my-docs",
1505
+ validate: (value) => {
1506
+ const v = (value ?? "").trim();
1507
+ if (!v) return "Project name is required";
1508
+ if (v.includes("/") || v.includes("\\")) return "Project name cannot contain path separators";
1509
+ if (v.includes(" ")) return "Project name cannot contain spaces";
1510
+ }
1511
+ });
1512
+ if (p.isCancel(nameAnswer)) {
1513
+ p.outro(pc.red("Init cancelled."));
1514
+ process.exit(0);
1515
+ }
1516
+ projectName = nameAnswer.trim();
1517
+ }
1518
+ const templateLabel = template === "next" ? "Next.js" : template === "nuxt" ? "Nuxt" : template === "sveltekit" ? "SvelteKit" : "Astro";
1519
+ const targetDir = path.join(cwd, projectName);
1520
+ const fs = await import("node:fs");
1521
+ if (fs.existsSync(targetDir)) {
1522
+ p.log.error(`Directory ${pc.cyan(projectName)} already exists. Choose a different ${pc.cyan("--name")} or remove it.`);
1523
+ process.exit(1);
1524
+ }
1525
+ fs.mkdirSync(targetDir, { recursive: true });
1526
+ p.log.step(`Bootstrapping project with ${pc.cyan(`'${projectName}'`)} (${templateLabel})...`);
1527
+ try {
1528
+ exec(`npx degit ${EXAMPLES_REPO}/examples/${template} . --force`, targetDir);
1529
+ } catch (err) {
1530
+ p.log.error("Failed to bootstrap. Check your connection and that the repo exists.");
1531
+ process.exit(1);
1532
+ }
1533
+ p.log.success(`Bootstrapped ${pc.cyan(`'${projectName}'`)}. Installing dependencies...`);
1534
+ const pm = detectPackageManager(targetDir);
1535
+ try {
1536
+ if (pm === "pnpm") exec("pnpm install", targetDir);
1537
+ else if (pm === "yarn") exec("yarn install", targetDir);
1538
+ else if (pm === "bun") exec("bun install", targetDir);
1539
+ else exec("npm install", targetDir);
1540
+ } catch {
1541
+ p.log.warn("Dependency install failed. Run your package manager install command manually.");
1542
+ }
1543
+ const devCmd = pm === "yarn" ? "yarn dev" : pm === "bun" ? "bun dev" : `${pm} run dev`;
1544
+ p.outro(pc.green(`Done! Run ${pc.cyan(`cd ${projectName} && ${devCmd}`)} to start the dev server.`));
1545
+ process.exit(0);
1546
+ }
1547
+ let framework = detectFramework(cwd);
1548
+ if (framework) {
1549
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
1550
+ p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
1551
+ } else {
1552
+ p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
1553
+ const picked = await p.select({
1554
+ message: "Which framework are you using?",
1555
+ options: [
1556
+ {
1557
+ value: "nextjs",
1558
+ label: "Next.js",
1559
+ hint: "React framework with App Router"
1560
+ },
1561
+ {
1562
+ value: "sveltekit",
1563
+ label: "SvelteKit",
1564
+ hint: "Svelte framework with file-based routing"
1565
+ },
1566
+ {
1567
+ value: "astro",
1568
+ label: "Astro",
1569
+ hint: "Content-focused framework with island architecture"
1570
+ },
1571
+ {
1572
+ value: "nuxt",
1573
+ label: "Nuxt",
1574
+ hint: "Vue 3 framework with file-based routing and Nitro server"
1575
+ }
1576
+ ]
1577
+ });
1578
+ if (p.isCancel(picked)) {
1579
+ p.outro(pc.red("Init cancelled."));
1580
+ process.exit(0);
1581
+ }
1582
+ framework = picked;
1583
+ }
1584
+ const theme = await p.select({
1585
+ message: "Which theme would you like to use?",
1586
+ options: [
1587
+ {
1588
+ value: "fumadocs",
1589
+ label: "Fumadocs (Default)",
1590
+ hint: "Clean, modern docs theme with sidebar, search, and dark mode"
1591
+ },
1592
+ {
1593
+ value: "darksharp",
1594
+ label: "Darksharp",
1595
+ hint: "All-black, sharp edges, zero-radius look"
1596
+ },
1597
+ {
1598
+ value: "pixel-border",
1599
+ label: "Pixel Border",
1600
+ hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
1601
+ },
1602
+ {
1603
+ value: "colorful",
1604
+ label: "Colorful",
1605
+ hint: "Fumadocs-style neutral theme with description support"
1606
+ },
1607
+ {
1608
+ value: "darkbold",
1609
+ label: "DarkBold",
1610
+ hint: "Pure monochrome, Geist typography, clean minimalism"
1611
+ },
1612
+ {
1613
+ value: "shiny",
1614
+ label: "Shiny",
1615
+ hint: "Glossy, modern look with subtle shimmer effects"
1616
+ },
1617
+ {
1618
+ value: "greentree",
1619
+ label: "GreenTree",
1620
+ hint: "Emerald green accent, Inter font, Mintlify-inspired"
1621
+ }
1622
+ ]
1623
+ });
1624
+ if (p.isCancel(theme)) {
1625
+ p.outro(pc.red("Init cancelled."));
1626
+ process.exit(0);
1627
+ }
1628
+ 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)`;
1629
+ const useAlias = await p.confirm({
1630
+ message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
1631
+ initialValue: false
1632
+ });
1633
+ if (p.isCancel(useAlias)) {
1634
+ p.outro(pc.red("Init cancelled."));
1635
+ process.exit(0);
1636
+ }
1637
+ let astroAdapter;
1638
+ if (framework === "astro") {
1639
+ const adapter = await p.select({
1640
+ message: "Where will you deploy?",
1641
+ options: [
1642
+ {
1643
+ value: "vercel",
1644
+ label: "Vercel",
1645
+ hint: "Recommended for most projects"
1646
+ },
1647
+ {
1648
+ value: "netlify",
1649
+ label: "Netlify"
1650
+ },
1651
+ {
1652
+ value: "cloudflare",
1653
+ label: "Cloudflare Pages"
1654
+ },
1655
+ {
1656
+ value: "node",
1657
+ label: "Node.js / Docker",
1658
+ hint: "Self-hosted standalone server"
1659
+ }
1660
+ ]
1661
+ });
1662
+ if (p.isCancel(adapter)) {
1663
+ p.outro(pc.red("Init cancelled."));
1664
+ process.exit(0);
1665
+ }
1666
+ astroAdapter = adapter;
1667
+ }
1668
+ const entry = await p.text({
1669
+ message: "Where should your docs live?",
1670
+ placeholder: "docs",
1671
+ defaultValue: "docs",
1672
+ validate: (value) => {
1673
+ if (!value) return "Entry path is required";
1674
+ if (value.startsWith("/")) return "Use a relative path (no leading /)";
1675
+ if (value.includes(" ")) return "Path cannot contain spaces";
1676
+ }
1677
+ });
1678
+ if (p.isCancel(entry)) {
1679
+ p.outro(pc.red("Init cancelled."));
1680
+ process.exit(0);
1681
+ }
1682
+ const entryPath = entry;
1683
+ const detectedCssFiles = detectGlobalCssFiles(cwd);
1684
+ let globalCssRelPath;
1685
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
1686
+ if (detectedCssFiles.length === 1) {
1687
+ globalCssRelPath = detectedCssFiles[0];
1688
+ p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
1689
+ } else if (detectedCssFiles.length > 1) {
1690
+ const picked = await p.select({
1691
+ message: "Multiple global CSS files found. Which one should we use?",
1692
+ options: detectedCssFiles.map((f) => ({
1693
+ value: f,
1694
+ label: f
1695
+ }))
1696
+ });
1697
+ if (p.isCancel(picked)) {
1698
+ p.outro(pc.red("Init cancelled."));
1699
+ process.exit(0);
1700
+ }
1701
+ globalCssRelPath = picked;
1702
+ } else {
1703
+ const cssPath = await p.text({
1704
+ message: "Where is your global CSS file?",
1705
+ placeholder: defaultCssPath,
1706
+ defaultValue: defaultCssPath,
1707
+ validate: (value) => {
1708
+ if (!value) return "CSS file path is required";
1709
+ if (!value.endsWith(".css")) return "Path must end with .css";
1710
+ }
1711
+ });
1712
+ if (p.isCancel(cssPath)) {
1713
+ p.outro(pc.red("Init cancelled."));
1714
+ process.exit(0);
1715
+ }
1716
+ globalCssRelPath = cssPath;
1717
+ }
1718
+ const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
1719
+ const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
1720
+ const cfg = {
1721
+ entry: entryPath,
1722
+ theme,
1723
+ projectName: pkgJson.name || "My Project",
1724
+ framework,
1725
+ useAlias,
1726
+ astroAdapter
1727
+ };
1728
+ const s = p.spinner();
1729
+ s.start("Scaffolding docs files");
1730
+ const written = [];
1731
+ const skipped = [];
1732
+ function write(rel, content, overwrite = false) {
1733
+ if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
1734
+ else skipped.push(rel);
1735
+ }
1736
+ if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
1737
+ else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
1738
+ else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
1739
+ else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
1740
+ s.stop("Files scaffolded");
1741
+ if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
1742
+ 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"));
1743
+ const pm = detectPackageManager(cwd);
1744
+ p.log.info(`Using ${pc.cyan(pm)} as package manager`);
1745
+ const s2 = p.spinner();
1746
+ s2.start("Installing dependencies");
1747
+ try {
1748
+ if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
1749
+ else if (framework === "astro") {
1750
+ const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
1751
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
1752
+ } else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
1753
+ else {
1754
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
1755
+ const devDeps = [
1756
+ "@tailwindcss/postcss",
1757
+ "postcss",
1758
+ "tailwindcss",
1759
+ "@types/mdx",
1760
+ "@types/node"
1761
+ ];
1762
+ const allDeps = {
1763
+ ...pkgJson.dependencies,
1764
+ ...pkgJson.devDependencies
1765
+ };
1766
+ const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
1767
+ if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
1768
+ }
1769
+ } catch {
1770
+ s2.stop("Failed to install dependencies");
1771
+ p.log.error(`Dependency installation failed. Run the install command manually:
1772
+ ${pc.cyan(`${installCommand(pm)} @farming-labs/docs`)}`);
1773
+ p.outro(pc.yellow("Setup partially complete. Install deps and run dev server manually."));
1774
+ process.exit(1);
1775
+ }
1776
+ s2.stop("Dependencies installed");
1777
+ const startDev = await p.confirm({
1778
+ message: "Start the dev server now?",
1779
+ initialValue: true
1780
+ });
1781
+ if (p.isCancel(startDev) || !startDev) {
1782
+ p.log.info(`You can start the dev server later with:
1783
+ ${pc.cyan(`${pm === "yarn" ? "yarn" : pm + " run"} dev`)}`);
1784
+ p.outro(pc.green("Done! Happy documenting."));
1785
+ process.exit(0);
1786
+ }
1787
+ p.log.step("Starting dev server...");
1788
+ const devCommand = framework === "sveltekit" ? {
1789
+ cmd: "npx",
1790
+ args: ["vite", "dev"],
1791
+ waitFor: "ready"
1792
+ } : framework === "astro" ? {
1793
+ cmd: "npx",
1794
+ args: ["astro", "dev"],
1795
+ waitFor: "ready"
1796
+ } : framework === "nuxt" ? {
1797
+ cmd: "npx",
1798
+ args: ["nuxt", "dev"],
1799
+ waitFor: "Local"
1800
+ } : {
1801
+ cmd: "npx",
1802
+ args: [
1803
+ "next",
1804
+ "dev",
1805
+ "--webpack"
1806
+ ],
1807
+ waitFor: "Ready"
1808
+ };
1809
+ const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
1810
+ try {
1811
+ const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
1812
+ const url = `http://localhost:${defaultPort}/${entryPath}`;
1813
+ console.log();
1814
+ 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.`);
1815
+ p.outro(pc.green("Happy documenting!"));
1816
+ await new Promise((resolve) => {
1817
+ child.on("close", () => resolve());
1818
+ process.on("SIGINT", () => {
1819
+ child.kill("SIGINT");
1820
+ resolve();
1821
+ });
1822
+ process.on("SIGTERM", () => {
1823
+ child.kill("SIGTERM");
1824
+ resolve();
1825
+ });
1826
+ });
1827
+ } catch (err) {
1828
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
1829
+ p.log.error(`Could not start dev server. Try running manually:
1830
+ ${pc.cyan(manualCmd)}`);
1831
+ p.outro(pc.yellow("Setup complete. Start the server manually."));
1832
+ process.exit(1);
1833
+ }
1834
+ }
1835
+ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
1836
+ write("docs.config.ts", docsConfigTemplate(cfg));
1837
+ const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
1838
+ if (existingNextConfig) {
1839
+ 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";
1840
+ const merged = nextConfigMergedTemplate(existingNextConfig);
1841
+ if (merged !== existingNextConfig) {
1842
+ writeFileSafe(path.join(cwd, configFile), merged, true);
1843
+ written.push(configFile + " (updated)");
1844
+ } else skipped.push(configFile + " (already configured)");
1845
+ } else write("next.config.ts", nextConfigTemplate());
1846
+ write("app/layout.tsx", rootLayoutTemplate(cfg, globalCssRelPath));
1847
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1848
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1849
+ if (existingGlobalCss) {
1850
+ const injected = injectCssImport(existingGlobalCss, cfg.theme);
1851
+ if (injected) {
1852
+ writeFileSafe(globalCssAbsPath, injected, true);
1853
+ written.push(globalCssRelPath + " (updated)");
1854
+ } else skipped.push(globalCssRelPath + " (already configured)");
1855
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme));
1856
+ write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
1857
+ write("postcss.config.mjs", postcssConfigTemplate());
1858
+ if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
1859
+ write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
1860
+ write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
1861
+ write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
1862
+ }
1863
+ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
1864
+ write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
1865
+ write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
1866
+ write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
1867
+ write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
1868
+ write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
1869
+ if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
1870
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1871
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1872
+ const cssTheme = {
1873
+ fumadocs: "fumadocs",
1874
+ darksharp: "darksharp",
1875
+ "pixel-border": "pixel-border",
1876
+ colorful: "colorful",
1877
+ darkbold: "darkbold",
1878
+ shiny: "shiny",
1879
+ greentree: "greentree",
1880
+ default: "fumadocs"
1881
+ }[cfg.theme] || "fumadocs";
1882
+ if (existingGlobalCss) {
1883
+ const injected = injectSvelteCssImport(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, svelteGlobalCssTemplate(cssTheme));
1889
+ write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
1890
+ write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1891
+ write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1892
+ }
1893
+ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1894
+ write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1895
+ write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1896
+ if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
1897
+ write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
1898
+ write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
1899
+ write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
1900
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1901
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1902
+ const cssTheme = {
1903
+ fumadocs: "fumadocs",
1904
+ darksharp: "darksharp",
1905
+ "pixel-border": "pixel-border",
1906
+ colorful: "colorful",
1907
+ darkbold: "darkbold",
1908
+ shiny: "shiny",
1909
+ greentree: "greentree",
1910
+ default: "fumadocs"
1911
+ }[cfg.theme] || "fumadocs";
1912
+ if (existingGlobalCss) {
1913
+ const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
1914
+ if (injected) {
1915
+ writeFileSafe(globalCssAbsPath, injected, true);
1916
+ written.push(globalCssRelPath + " (updated)");
1917
+ } else skipped.push(globalCssRelPath + " (already configured)");
1918
+ } else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
1919
+ write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
1920
+ write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
1921
+ write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1922
+ }
1923
+ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
1924
+ write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
1925
+ write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
1926
+ write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
1927
+ write("server/api/docs.post.ts", nuxtServerApiDocsPostTemplate());
1928
+ write("server/api/docs/load.get.ts", nuxtServerApiDocsLoadTemplate());
1929
+ write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
1930
+ path.join(cwd, "nuxt.config.ts");
1931
+ if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
1932
+ const cssTheme = {
1933
+ fumadocs: "fumadocs",
1934
+ darksharp: "darksharp",
1935
+ "pixel-border": "pixel-border",
1936
+ colorful: "colorful",
1937
+ darkbold: "darkbold",
1938
+ shiny: "shiny",
1939
+ greentree: "greentree",
1940
+ default: "fumadocs"
1941
+ }[cfg.theme] || "fumadocs";
1942
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1943
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1944
+ if (existingGlobalCss) {
1945
+ const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
1946
+ if (injected) {
1947
+ writeFileSafe(globalCssAbsPath, injected, true);
1948
+ written.push(globalCssRelPath + " (updated)");
1949
+ } else skipped.push(globalCssRelPath + " (already configured)");
1950
+ } else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
1951
+ write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
1952
+ write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
1953
+ write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
1954
+ }
1955
+
1956
+ //#endregion
1957
+ //#region src/cli/index.ts
1958
+ const args = process.argv.slice(2);
1959
+ const command = args[0];
1960
+ /** Parse flags like --template next, --name my-docs, --theme darksharp, --entry docs */
1961
+ function parseFlags(argv) {
1962
+ const flags = {};
1963
+ for (let i = 0; i < argv.length; i++) {
1964
+ const arg = argv[i];
1965
+ if (arg.startsWith("--") && arg.includes("=")) {
1966
+ const [key, value] = arg.slice(2).split("=");
1967
+ flags[key] = value;
1968
+ } else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1].startsWith("--")) {
1969
+ flags[arg.slice(2)] = argv[i + 1];
1970
+ i++;
1971
+ }
1972
+ }
1973
+ return flags;
1974
+ }
1975
+ async function main() {
1976
+ const flags = parseFlags(args);
1977
+ const initOptions = {
1978
+ template: flags.template,
1979
+ name: flags.name,
1980
+ theme: flags.theme,
1981
+ entry: flags.entry
1982
+ };
1983
+ if (!command || command === "init") await init(initOptions);
1984
+ else if (command === "--help" || command === "-h") printHelp();
1985
+ else if (command === "--version" || command === "-v") printVersion();
1986
+ else {
1987
+ console.error(pc.red(`Unknown command: ${command}`));
1988
+ console.error();
1989
+ printHelp();
1990
+ process.exit(1);
1991
+ }
1992
+ }
1993
+ function printHelp() {
1994
+ console.log(`
1995
+ ${pc.bold("@farming-labs/docs")} — Documentation framework CLI
1996
+
1997
+ ${pc.dim("Usage:")}
1998
+ npx @farming-labs/docs ${pc.cyan("<command>")}
1999
+
2000
+ ${pc.dim("Commands:")}
2001
+ ${pc.cyan("init")} Scaffold docs in your project (default)
2002
+
2003
+ ${pc.dim("Supported frameworks:")}
2004
+ Next.js, SvelteKit, Astro, Nuxt
650
2005
 
651
- ${pc.dim("Options:")}
652
- ${pc.cyan("-h, --help")} Show this help message
653
- ${pc.cyan("-v, --version")} Show version
2006
+ ${pc.dim("Options for init:")}
2007
+ ${pc.cyan("--template <name>")} Bootstrap a project (${pc.dim("next")}, ${pc.dim("nuxt")}, ${pc.dim("sveltekit")}, ${pc.dim("astro")}); use with ${pc.cyan("--name")}
2008
+ ${pc.cyan("--name <project>")} Project folder name when using ${pc.cyan("--template")}; prompt if omitted (e.g. ${pc.dim("my-docs")})
2009
+ ${pc.cyan("--theme <name>")} Skip theme prompt (e.g. ${pc.dim("darksharp")}, ${pc.dim("greentree")})
2010
+ ${pc.cyan("--entry <path>")} Skip entry path prompt (e.g. ${pc.dim("docs")})
2011
+ ${pc.cyan("-h, --help")} Show this help message
2012
+ ${pc.cyan("-v, --version")} Show version
654
2013
  `);
655
2014
  }
656
2015
  function printVersion() {