@farming-labs/docs 0.0.2-beta.1 → 0.0.2-beta.10

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,12 @@ 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";
17
19
  return null;
18
20
  }
19
21
  function detectPackageManager(cwd) {
@@ -53,6 +55,25 @@ function readFileSafe(filePath) {
53
55
  if (!fs.existsSync(filePath)) return null;
54
56
  return fs.readFileSync(filePath, "utf-8");
55
57
  }
58
+ /** Common locations where global CSS files live in Next.js / SvelteKit projects. */
59
+ const GLOBAL_CSS_CANDIDATES = [
60
+ "app/globals.css",
61
+ "app/global.css",
62
+ "src/app/globals.css",
63
+ "src/app/global.css",
64
+ "src/app.css",
65
+ "styles/globals.css",
66
+ "styles/global.css",
67
+ "src/styles/globals.css",
68
+ "src/styles/global.css"
69
+ ];
70
+ /**
71
+ * Find existing global CSS files in the project.
72
+ * Returns relative paths that exist.
73
+ */
74
+ function detectGlobalCssFiles(cwd) {
75
+ return GLOBAL_CSS_CANDIDATES.filter((rel) => fs.existsSync(path.join(cwd, rel)));
76
+ }
56
77
  /**
57
78
  * Run a shell command synchronously, inheriting stdio.
58
79
  */
@@ -107,14 +128,65 @@ function spawnAndWaitFor(command, args, cwd, waitFor, timeoutMs = 6e4) {
107
128
 
108
129
  //#endregion
109
130
  //#region src/cli/templates.ts
131
+ const THEME_INFO = {
132
+ fumadocs: {
133
+ factory: "fumadocs",
134
+ nextImport: "@farming-labs/theme",
135
+ svelteImport: "@farming-labs/svelte-theme",
136
+ nextCssImport: "default",
137
+ svelteCssTheme: "fumadocs"
138
+ },
139
+ darksharp: {
140
+ factory: "darksharp",
141
+ nextImport: "@farming-labs/theme/darksharp",
142
+ svelteImport: "@farming-labs/svelte-theme/darksharp",
143
+ nextCssImport: "darksharp",
144
+ svelteCssTheme: "darksharp"
145
+ },
146
+ "pixel-border": {
147
+ factory: "pixelBorder",
148
+ nextImport: "@farming-labs/theme/pixel-border",
149
+ svelteImport: "@farming-labs/svelte-theme/pixel-border",
150
+ nextCssImport: "pixel-border",
151
+ svelteCssTheme: "pixel-border"
152
+ }
153
+ };
154
+ function getThemeInfo(theme) {
155
+ return THEME_INFO[theme] ?? THEME_INFO.fumadocs;
156
+ }
157
+ /** Config import for Next.js app/layout.tsx → root docs.config */
158
+ function nextRootLayoutConfigImport(useAlias) {
159
+ return useAlias ? "@/docs.config" : "../docs.config";
160
+ }
161
+ /** Config import for Next.js app/{entry}/layout.tsx → root docs.config */
162
+ function nextDocsLayoutConfigImport(useAlias) {
163
+ return useAlias ? "@/docs.config" : "../../docs.config";
164
+ }
165
+ /** Config import for SvelteKit src/lib/docs.server.ts → src/lib/docs.config */
166
+ function svelteServerConfigImport(useAlias) {
167
+ return useAlias ? "$lib/docs.config" : "./docs.config";
168
+ }
169
+ /** Config import for SvelteKit src/routes/{entry}/+layout.svelte → src/lib/docs.config */
170
+ function svelteLayoutConfigImport(useAlias) {
171
+ return useAlias ? "$lib/docs.config" : "../../lib/docs.config";
172
+ }
173
+ /** Config import for SvelteKit src/routes/{entry}/[...slug]/+page.svelte → src/lib/docs.config */
174
+ function sveltePageConfigImport(useAlias) {
175
+ return useAlias ? "$lib/docs.config" : "../../../lib/docs.config";
176
+ }
177
+ /** Server import for SvelteKit +layout.server.js → src/lib/docs.server */
178
+ function svelteLayoutServerImport(useAlias) {
179
+ return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
180
+ }
110
181
  function docsConfigTemplate(cfg) {
182
+ const t = getThemeInfo(cfg.theme);
111
183
  return `\
112
184
  import { defineDocs } from "@farming-labs/docs";
113
- import { fumadocs } from "@farming-labs/fumadocs";
185
+ import { ${t.factory} } from "${t.nextImport}";
114
186
 
115
187
  export default defineDocs({
116
188
  entry: "${cfg.entry}",
117
- theme: fumadocs({
189
+ theme: ${t.factory}({
118
190
  ui: {
119
191
  colors: { primary: "#6366f1" },
120
192
  },
@@ -151,12 +223,16 @@ function nextConfigMergedTemplate(existingContent) {
151
223
  }
152
224
  return lines.join("\n");
153
225
  }
154
- function rootLayoutTemplate() {
226
+ function rootLayoutTemplate(cfg, globalCssRelPath = "app/globals.css") {
227
+ let cssImport;
228
+ if (globalCssRelPath.startsWith("app/")) cssImport = "./" + globalCssRelPath.slice(4);
229
+ else if (globalCssRelPath.startsWith("src/app/")) cssImport = "./" + globalCssRelPath.slice(8);
230
+ else cssImport = "../" + globalCssRelPath;
155
231
  return `\
156
232
  import type { Metadata } from "next";
157
- import { RootProvider } from "@farming-labs/fumadocs";
158
- import docsConfig from "@/docs.config";
159
- import "./global.css";
233
+ import { RootProvider } from "@farming-labs/theme";
234
+ import docsConfig from "${nextRootLayoutConfigImport(cfg.useAlias)}";
235
+ import "${cssImport}";
160
236
 
161
237
  export const metadata: Metadata = {
162
238
  title: {
@@ -184,26 +260,23 @@ export default function RootLayout({
184
260
  function globalCssTemplate(theme) {
185
261
  return `\
186
262
  @import "tailwindcss";
187
- @import "@farming-labs/${theme}/css";
263
+ @import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
188
264
  `;
189
265
  }
190
- /**
191
- * Inject the fumadocs CSS import into an existing global.css.
192
- * Returns the modified content, or null if already present.
193
- */
194
266
  function injectCssImport(existingContent, theme) {
195
- const importLine = `@import "@farming-labs/${theme}/css";`;
267
+ const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
196
268
  if (existingContent.includes(importLine)) return null;
269
+ if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
197
270
  const lines = existingContent.split("\n");
198
271
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
199
272
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
200
273
  else lines.unshift(importLine);
201
274
  return lines.join("\n");
202
275
  }
203
- function docsLayoutTemplate() {
276
+ function docsLayoutTemplate(cfg) {
204
277
  return `\
205
- import docsConfig from "@/docs.config";
206
- import { createDocsLayout } from "@farming-labs/fumadocs";
278
+ import docsConfig from "${nextDocsLayoutConfigImport(cfg.useAlias)}";
279
+ import { createDocsLayout } from "@farming-labs/theme";
207
280
 
208
281
  export default createDocsLayout(docsConfig);
209
282
  `;
@@ -282,6 +355,7 @@ Start by reading the [Installation](/${cfg.entry}/installation) guide, then foll
282
355
  `;
283
356
  }
284
357
  function installationPageTemplate(cfg) {
358
+ const t = getThemeInfo(cfg.theme);
285
359
  return `\
286
360
  ---
287
361
  title: "Installation"
@@ -308,11 +382,11 @@ Your project includes a \`docs.config.ts\` at the root:
308
382
 
309
383
  \`\`\`ts
310
384
  import { defineDocs } from "@farming-labs/docs";
311
- import { fumadocs } from "@farming-labs/fumadocs";
385
+ import { ${t.factory} } from "${t.nextImport}";
312
386
 
313
387
  export default defineDocs({
314
388
  entry: "${cfg.entry}",
315
- theme: fumadocs({
389
+ theme: ${t.factory}({
316
390
  ui: { colors: { primary: "#6366f1" } },
317
391
  }),
318
392
  });
@@ -339,6 +413,7 @@ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your f
339
413
  `;
340
414
  }
341
415
  function quickstartPageTemplate(cfg) {
416
+ const t = getThemeInfo(cfg.theme);
342
417
  return `\
343
418
  ---
344
419
  title: "Quickstart"
@@ -401,7 +476,280 @@ console.log(greet("World"));
401
476
  Edit \`docs.config.ts\` to change colors, typography, and component defaults:
402
477
 
403
478
  \`\`\`ts
404
- theme: fumadocs({
479
+ theme: ${t.factory}({
480
+ ui: {
481
+ colors: { primary: "#22c55e" },
482
+ },
483
+ }),
484
+ \`\`\`
485
+
486
+ ## Deploying
487
+
488
+ Build your docs for production:
489
+
490
+ \`\`\`bash
491
+ pnpm build
492
+ \`\`\`
493
+
494
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
495
+ `;
496
+ }
497
+ function svelteDocsConfigTemplate(cfg) {
498
+ const t = getThemeInfo(cfg.theme);
499
+ return `\
500
+ import { defineDocs } from "@farming-labs/docs";
501
+ import { ${t.factory} } from "${t.svelteImport}";
502
+
503
+ export default defineDocs({
504
+ entry: "${cfg.entry}",
505
+ theme: ${t.factory}({
506
+ ui: {
507
+ colors: { primary: "#6366f1" },
508
+ },
509
+ }),
510
+
511
+ nav: {
512
+ title: "${cfg.projectName}",
513
+ url: "/${cfg.entry}",
514
+ },
515
+
516
+ breadcrumb: { enabled: true },
517
+
518
+ metadata: {
519
+ titleTemplate: "%s – ${cfg.projectName}",
520
+ description: "Documentation for ${cfg.projectName}",
521
+ },
522
+ });
523
+ `;
524
+ }
525
+ function svelteDocsServerTemplate(cfg) {
526
+ return `\
527
+ import { createDocsServer } from "@farming-labs/svelte/server";
528
+ import config from "${svelteServerConfigImport(cfg.useAlias)}";
529
+
530
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx,svx}", {
531
+ query: "?raw",
532
+ import: "default",
533
+ eager: true,
534
+ }) as Record<string, string>;
535
+
536
+ export const { load, GET, POST } = createDocsServer({
537
+ ...config,
538
+ _preloadedContent: contentFiles,
539
+ });
540
+ `;
541
+ }
542
+ function svelteDocsLayoutTemplate(cfg) {
543
+ return `\
544
+ <script>
545
+ import { DocsLayout } from "@farming-labs/svelte-theme";
546
+ import config from "${svelteLayoutConfigImport(cfg.useAlias)}";
547
+
548
+ let { data, children } = $props();
549
+ <\/script>
550
+
551
+ <DocsLayout tree={data.tree} {config}>
552
+ {@render children()}
553
+ </DocsLayout>
554
+ `;
555
+ }
556
+ function svelteDocsLayoutServerTemplate(cfg) {
557
+ return `\
558
+ export { load } from "${svelteLayoutServerImport(cfg.useAlias)}";
559
+ `;
560
+ }
561
+ function svelteDocsPageTemplate(cfg) {
562
+ return `\
563
+ <script>
564
+ import { DocsContent } from "@farming-labs/svelte-theme";
565
+ import config from "${sveltePageConfigImport(cfg.useAlias)}";
566
+
567
+ let { data } = $props();
568
+ <\/script>
569
+
570
+ <DocsContent {data} {config} />
571
+ `;
572
+ }
573
+ function svelteRootLayoutTemplate(globalCssRelPath) {
574
+ let cssImport;
575
+ if (globalCssRelPath.startsWith("src/")) cssImport = "./" + globalCssRelPath.slice(4);
576
+ else cssImport = "../" + globalCssRelPath;
577
+ return `\
578
+ <script>
579
+ import "${cssImport}";
580
+
581
+ let { children } = $props();
582
+ <\/script>
583
+
584
+ {@render children()}
585
+ `;
586
+ }
587
+ function svelteGlobalCssTemplate(theme) {
588
+ return `\
589
+ @import "@farming-labs/svelte-theme/${theme}/css";
590
+ `;
591
+ }
592
+ function svelteCssImportLine(theme) {
593
+ return `@import "@farming-labs/svelte-theme/${theme}/css";`;
594
+ }
595
+ function injectSvelteCssImport(existingContent, theme) {
596
+ const importLine = svelteCssImportLine(theme);
597
+ if (existingContent.includes(importLine)) return null;
598
+ const lines = existingContent.split("\n");
599
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
600
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
601
+ else lines.unshift(importLine);
602
+ return lines.join("\n");
603
+ }
604
+ function svelteWelcomePageTemplate(cfg) {
605
+ return `\
606
+ ---
607
+ title: "Documentation"
608
+ description: "Welcome to ${cfg.projectName} documentation"
609
+ ---
610
+
611
+ # Welcome to ${cfg.projectName}
612
+
613
+ Get started with our documentation. Browse the pages on the left to learn more.
614
+
615
+ ## Overview
616
+
617
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
618
+
619
+ ## Features
620
+
621
+ - **Markdown Support** — Write docs with standard Markdown
622
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
623
+ - **Dark Mode** — Built-in theme switching
624
+ - **Search** — Full-text search across all pages
625
+ - **Responsive** — Works on any screen size
626
+
627
+ ---
628
+
629
+ ## Next Steps
630
+
631
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
632
+ `;
633
+ }
634
+ function svelteInstallationPageTemplate(cfg) {
635
+ const t = getThemeInfo(cfg.theme);
636
+ return `\
637
+ ---
638
+ title: "Installation"
639
+ description: "How to install and set up ${cfg.projectName}"
640
+ ---
641
+
642
+ # Installation
643
+
644
+ Follow these steps to install and configure ${cfg.projectName}.
645
+
646
+ ## Prerequisites
647
+
648
+ - Node.js 18+
649
+ - A package manager (pnpm, npm, or yarn)
650
+
651
+ ## Install Dependencies
652
+
653
+ \`\`\`bash
654
+ pnpm add @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme
655
+ \`\`\`
656
+
657
+ ## Configuration
658
+
659
+ Your project includes a \`docs.config.ts\` in \`src/lib/\`:
660
+
661
+ \`\`\`ts title="src/lib/docs.config.ts"
662
+ import { defineDocs } from "@farming-labs/docs";
663
+ import { ${t.factory} } from "${t.svelteImport}";
664
+
665
+ export default defineDocs({
666
+ entry: "${cfg.entry}",
667
+ contentDir: "${cfg.entry}",
668
+ theme: ${t.factory}({
669
+ ui: { colors: { primary: "#6366f1" } },
670
+ }),
671
+ });
672
+ \`\`\`
673
+
674
+ ## Project Structure
675
+
676
+ \`\`\`
677
+ ${cfg.entry}/ # Markdown content
678
+ page.md # /${cfg.entry}
679
+ installation/
680
+ page.md # /${cfg.entry}/installation
681
+ quickstart/
682
+ page.md # /${cfg.entry}/quickstart
683
+ src/
684
+ lib/
685
+ docs.config.ts # Docs configuration
686
+ docs.server.ts # Server-side docs loader
687
+ routes/
688
+ ${cfg.entry}/
689
+ +layout.svelte # Docs layout
690
+ +layout.server.js # Layout data loader
691
+ [...slug]/
692
+ +page.svelte # Dynamic doc page
693
+ \`\`\`
694
+
695
+ ## What's Next?
696
+
697
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
698
+ `;
699
+ }
700
+ function svelteQuickstartPageTemplate(cfg) {
701
+ const t = getThemeInfo(cfg.theme);
702
+ return `\
703
+ ---
704
+ title: "Quickstart"
705
+ description: "Get up and running in minutes"
706
+ ---
707
+
708
+ # Quickstart
709
+
710
+ This guide walks you through creating your first documentation page.
711
+
712
+ ## Creating a Page
713
+
714
+ Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
715
+
716
+ \`\`\`bash
717
+ mkdir -p ${cfg.entry}/my-page
718
+ \`\`\`
719
+
720
+ Then create \`${cfg.entry}/my-page/page.md\`:
721
+
722
+ \`\`\`md
723
+ ---
724
+ title: "My Page"
725
+ description: "A custom documentation page"
726
+ ---
727
+
728
+ # My Page
729
+
730
+ Write your content here using **Markdown**.
731
+ \`\`\`
732
+
733
+ Your page is now available at \`/${cfg.entry}/my-page\`.
734
+
735
+ ## Code Blocks
736
+
737
+ Code blocks are automatically syntax-highlighted:
738
+
739
+ \`\`\`typescript
740
+ function greet(name: string): string {
741
+ return \\\`Hello, \\\${name}!\\\`;
742
+ }
743
+
744
+ console.log(greet("World"));
745
+ \`\`\`
746
+
747
+ ## Customizing the Theme
748
+
749
+ Edit \`src/lib/docs.config.ts\` to change colors, typography, and component defaults:
750
+
751
+ \`\`\`ts title="src/lib/docs.config.ts"
752
+ theme: ${t.factory}({
405
753
  ui: {
406
754
  colors: { primary: "#22c55e" },
407
755
  },
@@ -425,24 +773,63 @@ Deploy to Vercel, Netlify, or any Node.js hosting platform.
425
773
  async function init() {
426
774
  const cwd = process.cwd();
427
775
  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);
776
+ let framework = detectFramework(cwd);
777
+ if (framework) {
778
+ const frameworkName = framework === "nextjs" ? "Next.js" : "SvelteKit";
779
+ p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
780
+ } else {
781
+ p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
782
+ const picked = await p.select({
783
+ message: "Which framework are you using?",
784
+ options: [{
785
+ value: "nextjs",
786
+ label: "Next.js",
787
+ hint: "React framework with App Router"
788
+ }, {
789
+ value: "sveltekit",
790
+ label: "SvelteKit",
791
+ hint: "Svelte framework with file-based routing"
792
+ }]
793
+ });
794
+ if (p.isCancel(picked)) {
795
+ p.outro(pc.red("Init cancelled."));
796
+ process.exit(0);
797
+ }
798
+ framework = picked;
432
799
  }
433
- p.log.success(`Detected framework: ${pc.cyan("Next.js")}`);
434
800
  const theme = await p.select({
435
801
  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
- }]
802
+ options: [
803
+ {
804
+ value: "fumadocs",
805
+ label: "Fumadocs (Default)",
806
+ hint: "Clean, modern docs theme with sidebar, search, and dark mode"
807
+ },
808
+ {
809
+ value: "darksharp",
810
+ label: "Darksharp",
811
+ hint: "All-black, sharp edges, zero-radius look"
812
+ },
813
+ {
814
+ value: "pixel-border",
815
+ label: "Pixel Border",
816
+ hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
817
+ }
818
+ ]
441
819
  });
442
820
  if (p.isCancel(theme)) {
443
821
  p.outro(pc.red("Init cancelled."));
444
822
  process.exit(0);
445
823
  }
824
+ const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)`;
825
+ const useAlias = await p.confirm({
826
+ message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
827
+ initialValue: false
828
+ });
829
+ if (p.isCancel(useAlias)) {
830
+ p.outro(pc.red("Init cancelled."));
831
+ process.exit(0);
832
+ }
446
833
  const entry = await p.text({
447
834
  message: "Where should your docs live?",
448
835
  placeholder: "docs",
@@ -458,11 +845,49 @@ async function init() {
458
845
  process.exit(0);
459
846
  }
460
847
  const entryPath = entry;
461
- const pkgJson = JSON.parse(readFileSafe(path.join(cwd, "package.json")));
848
+ const detectedCssFiles = detectGlobalCssFiles(cwd);
849
+ let globalCssRelPath;
850
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : "app/globals.css";
851
+ if (detectedCssFiles.length === 1) {
852
+ globalCssRelPath = detectedCssFiles[0];
853
+ p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
854
+ } else if (detectedCssFiles.length > 1) {
855
+ const picked = await p.select({
856
+ message: "Multiple global CSS files found. Which one should we use?",
857
+ options: detectedCssFiles.map((f) => ({
858
+ value: f,
859
+ label: f
860
+ }))
861
+ });
862
+ if (p.isCancel(picked)) {
863
+ p.outro(pc.red("Init cancelled."));
864
+ process.exit(0);
865
+ }
866
+ globalCssRelPath = picked;
867
+ } else {
868
+ const cssPath = await p.text({
869
+ message: "Where is your global CSS file?",
870
+ placeholder: defaultCssPath,
871
+ defaultValue: defaultCssPath,
872
+ validate: (value) => {
873
+ if (!value) return "CSS file path is required";
874
+ if (!value.endsWith(".css")) return "Path must end with .css";
875
+ }
876
+ });
877
+ if (p.isCancel(cssPath)) {
878
+ p.outro(pc.red("Init cancelled."));
879
+ process.exit(0);
880
+ }
881
+ globalCssRelPath = cssPath;
882
+ }
883
+ const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
884
+ const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
462
885
  const cfg = {
463
886
  entry: entryPath,
464
887
  theme,
465
- projectName: pkgJson.name || "My Project"
888
+ projectName: pkgJson.name || "My Project",
889
+ framework,
890
+ useAlias
466
891
  };
467
892
  const s = p.spinner();
468
893
  s.start("Scaffolding docs files");
@@ -472,32 +897,8 @@ async function init() {
472
897
  if (writeFileSafe(path.join(cwd, rel), content, overwrite)) written.push(rel);
473
898
  else skipped.push(rel);
474
899
  }
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));
900
+ if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
901
+ else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
501
902
  s.stop("Files scaffolded");
502
903
  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
904
  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"));
@@ -506,20 +907,23 @@ async function init() {
506
907
  const s2 = p.spinner();
507
908
  s2.start("Installing dependencies");
508
909
  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);
910
+ if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
911
+ else {
912
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
913
+ const devDeps = [
914
+ "@tailwindcss/postcss",
915
+ "postcss",
916
+ "tailwindcss",
917
+ "@types/mdx",
918
+ "@types/node"
919
+ ];
920
+ const allDeps = {
921
+ ...pkgJson.dependencies,
922
+ ...pkgJson.devDependencies
923
+ };
924
+ const missingDevDeps = devDeps.filter((d) => !allDeps[d]);
925
+ if (missingDevDeps.length > 0) exec(`${devInstallCommand(pm)} ${missingDevDeps.join(" ")}`, cwd);
926
+ }
523
927
  } catch {
524
928
  s2.stop("Failed to install dependencies");
525
929
  p.log.error(`Dependency installation failed. Run the install command manually:
@@ -539,13 +943,23 @@ async function init() {
539
943
  process.exit(0);
540
944
  }
541
945
  p.log.step("Starting dev server...");
542
- try {
543
- const child = await spawnAndWaitFor("npx", [
946
+ const devCommand = framework === "sveltekit" ? {
947
+ cmd: "npx",
948
+ args: ["vite", "dev"],
949
+ waitFor: "ready"
950
+ } : {
951
+ cmd: "npx",
952
+ args: [
544
953
  "next",
545
954
  "dev",
546
955
  "--webpack"
547
- ], cwd, "Ready", 6e4);
548
- const url = `http://localhost:3000/${entryPath}`;
956
+ ],
957
+ waitFor: "Ready"
958
+ };
959
+ const defaultPort = framework === "sveltekit" ? "5173" : "3000";
960
+ try {
961
+ const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
962
+ const url = `http://localhost:${defaultPort}/${entryPath}`;
549
963
  console.log();
550
964
  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
965
  p.outro(pc.green("Happy documenting!"));
@@ -561,12 +975,67 @@ async function init() {
561
975
  });
562
976
  });
563
977
  } catch (err) {
978
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : "npx next dev --webpack";
564
979
  p.log.error(`Could not start dev server. Try running manually:
565
- ${pc.cyan("npx next dev --webpack")}`);
980
+ ${pc.cyan(manualCmd)}`);
566
981
  p.outro(pc.yellow("Setup complete. Start the server manually."));
567
982
  process.exit(1);
568
983
  }
569
984
  }
985
+ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
986
+ write("docs.config.ts", docsConfigTemplate(cfg));
987
+ const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
988
+ if (existingNextConfig) {
989
+ 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";
990
+ const merged = nextConfigMergedTemplate(existingNextConfig);
991
+ if (merged !== existingNextConfig) {
992
+ writeFileSafe(path.join(cwd, configFile), merged, true);
993
+ written.push(configFile + " (updated)");
994
+ } else skipped.push(configFile + " (already configured)");
995
+ } else write("next.config.ts", nextConfigTemplate());
996
+ write("app/layout.tsx", rootLayoutTemplate(cfg, globalCssRelPath));
997
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
998
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
999
+ if (existingGlobalCss) {
1000
+ const injected = injectCssImport(existingGlobalCss, cfg.theme);
1001
+ if (injected) {
1002
+ writeFileSafe(globalCssAbsPath, injected, true);
1003
+ written.push(globalCssRelPath + " (updated)");
1004
+ } else skipped.push(globalCssRelPath + " (already configured)");
1005
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme));
1006
+ write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
1007
+ write("postcss.config.mjs", postcssConfigTemplate());
1008
+ if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
1009
+ write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
1010
+ write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
1011
+ write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
1012
+ }
1013
+ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
1014
+ write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
1015
+ write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
1016
+ write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
1017
+ write(`src/routes/${cfg.entry}/+layout.server.js`, svelteDocsLayoutServerTemplate(cfg));
1018
+ write(`src/routes/${cfg.entry}/[...slug]/+page.svelte`, svelteDocsPageTemplate(cfg));
1019
+ if (!readFileSafe(path.join(cwd, "src/routes/+layout.svelte"))) write("src/routes/+layout.svelte", svelteRootLayoutTemplate(globalCssRelPath));
1020
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1021
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1022
+ const cssTheme = {
1023
+ fumadocs: "fumadocs",
1024
+ darksharp: "darksharp",
1025
+ "pixel-border": "pixel-border",
1026
+ default: "fumadocs"
1027
+ }[cfg.theme] || "fumadocs";
1028
+ if (existingGlobalCss) {
1029
+ const injected = injectSvelteCssImport(existingGlobalCss, cssTheme);
1030
+ if (injected) {
1031
+ writeFileSafe(globalCssAbsPath, injected, true);
1032
+ written.push(globalCssRelPath + " (updated)");
1033
+ } else skipped.push(globalCssRelPath + " (already configured)");
1034
+ } else write(globalCssRelPath, svelteGlobalCssTemplate(cssTheme));
1035
+ write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
1036
+ write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1037
+ write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1038
+ }
570
1039
 
571
1040
  //#endregion
572
1041
  //#region src/cli/index.ts
@@ -587,11 +1056,14 @@ function printHelp() {
587
1056
  ${pc.bold("@farming-labs/docs")} — Documentation framework CLI
588
1057
 
589
1058
  ${pc.dim("Usage:")}
590
- farming-docs ${pc.cyan("<command>")}
1059
+ npx @farming-labs/docs ${pc.cyan("<command>")}
591
1060
 
592
1061
  ${pc.dim("Commands:")}
593
1062
  ${pc.cyan("init")} Scaffold docs in your project (default)
594
1063
 
1064
+ ${pc.dim("Supported frameworks:")}
1065
+ Next.js, SvelteKit
1066
+
595
1067
  ${pc.dim("Options:")}
596
1068
  ${pc.cyan("-h, --help")} Show this help message
597
1069
  ${pc.cyan("-v, --version")} Show version
package/dist/index.d.mts CHANGED
@@ -405,11 +405,308 @@ interface PageActionsConfig {
405
405
  */
406
406
  position?: "above-title" | "below-title";
407
407
  }
408
+ /**
409
+ * GitHub repository configuration for "Edit on GitHub" links
410
+ * and source file references.
411
+ *
412
+ * @example
413
+ * ```ts
414
+ * // Simple repo (not a monorepo)
415
+ * github: {
416
+ * url: "https://github.com/Kinfe123/my-docs",
417
+ * }
418
+ *
419
+ * // Monorepo — docs site lives in "website/" subdirectory
420
+ * github: {
421
+ * url: "https://github.com/farming-labs/docs",
422
+ * directory: "website",
423
+ * branch: "main",
424
+ * }
425
+ * ```
426
+ *
427
+ * Or as a simple string (branch defaults to "main", no directory prefix):
428
+ * ```ts
429
+ * github: "https://github.com/Kinfe123/my-docs"
430
+ * ```
431
+ */
432
+ interface GithubConfig {
433
+ /** Repository URL (e.g. "https://github.com/farming-labs/docs") */
434
+ url: string;
435
+ /** Branch name. @default "main" */
436
+ branch?: string;
437
+ /**
438
+ * Subdirectory inside the repo where the docs site lives.
439
+ * Use this for monorepos where the docs app is not at the repo root.
440
+ *
441
+ * @example "website" → links point to `website/app/docs/…/page.mdx`
442
+ */
443
+ directory?: string;
444
+ }
445
+ /**
446
+ * Configuration for "Ask AI" — a RAG-powered chat that lets users
447
+ * ask questions about the documentation content.
448
+ *
449
+ * The AI handler searches relevant doc pages, builds context, and
450
+ * streams a response from an LLM (OpenAI-compatible API).
451
+ *
452
+ * The API key is **never** stored in the config. It is read from the
453
+ * `OPENAI_API_KEY` environment variable at runtime on the server.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * ai: {
458
+ * enabled: true,
459
+ * model: "gpt-4o-mini",
460
+ * systemPrompt: "You are a helpful assistant for our developer docs.",
461
+ * }
462
+ * ```
463
+ */
464
+ interface AIConfig {
465
+ /**
466
+ * Whether to enable "Ask AI" functionality.
467
+ * When enabled, the unified `/api/docs` route handler will accept
468
+ * POST requests for AI chat.
469
+ * @default false
470
+ */
471
+ enabled?: boolean;
472
+ /**
473
+ * How the AI chat UI is presented.
474
+ *
475
+ * - `"search"` — AI tab integrated into the Cmd+K search dialog (default)
476
+ * - `"floating"` — A floating chat widget (bubble button + slide-out panel)
477
+ *
478
+ * @default "search"
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * // Floating chat bubble in the bottom-right corner
483
+ * ai: {
484
+ * enabled: true,
485
+ * mode: "floating",
486
+ * position: "bottom-right",
487
+ * }
488
+ * ```
489
+ */
490
+ mode?: "search" | "floating";
491
+ /**
492
+ * Position of the floating chat button on screen.
493
+ * Only used when `mode` is `"floating"`.
494
+ *
495
+ * - `"bottom-right"` — bottom-right corner (default)
496
+ * - `"bottom-left"` — bottom-left corner
497
+ * - `"bottom-center"` — bottom center
498
+ *
499
+ * @default "bottom-right"
500
+ */
501
+ position?: "bottom-right" | "bottom-left" | "bottom-center";
502
+ /**
503
+ * Visual style of the floating chat when opened.
504
+ * Only used when `mode` is `"floating"`.
505
+ *
506
+ * - `"panel"` — A tall panel that slides up from the button position (default).
507
+ * Stays anchored near the floating button. No backdrop overlay.
508
+ *
509
+ * - `"modal"` — A centered modal dialog with a backdrop overlay,
510
+ * similar to the Cmd+K search dialog. Feels more focused and immersive.
511
+ *
512
+ * - `"popover"` — A compact popover near the button. Smaller than the
513
+ * panel, suitable for quick questions without taking much screen space.
514
+ *
515
+ * - `"full-modal"` — A full-screen immersive overlay (inspired by better-auth).
516
+ * Messages scroll in the center, input is pinned at the bottom.
517
+ * Suggested questions appear as horizontal pills. Best for
518
+ * documentation-heavy sites that want a premium AI experience.
519
+ *
520
+ * @default "panel"
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * ai: {
525
+ * enabled: true,
526
+ * mode: "floating",
527
+ * position: "bottom-right",
528
+ * floatingStyle: "full-modal",
529
+ * }
530
+ * ```
531
+ */
532
+ floatingStyle?: "panel" | "modal" | "popover" | "full-modal";
533
+ /**
534
+ * Custom trigger component for the floating chat button.
535
+ * Only used when `mode` is `"floating"`.
536
+ *
537
+ * Pass a React element to replace the default sparkles button.
538
+ * The element receives an `onClick` handler automatically.
539
+ *
540
+ * @example
541
+ * ```tsx
542
+ * ai: {
543
+ * enabled: true,
544
+ * mode: "floating",
545
+ * triggerComponent: <button className="my-chat-btn">💬 Help</button>,
546
+ * }
547
+ * ```
548
+ */
549
+ triggerComponent?: unknown;
550
+ /**
551
+ * The LLM model to use for chat completions.
552
+ * Must be compatible with the OpenAI Chat Completions API.
553
+ * @default "gpt-4o-mini"
554
+ */
555
+ model?: string;
556
+ /**
557
+ * Custom system prompt prepended to the AI conversation.
558
+ * The documentation context is automatically appended after this prompt.
559
+ *
560
+ * @default "You are a helpful documentation assistant. Answer questions
561
+ * based on the provided documentation context. Be concise and accurate.
562
+ * If the answer is not in the context, say so honestly."
563
+ */
564
+ systemPrompt?: string;
565
+ /**
566
+ * Base URL for an OpenAI-compatible API endpoint.
567
+ * Use this to point to a self-hosted model, Azure OpenAI, or any
568
+ * compatible provider (e.g. Groq, Together, OpenRouter).
569
+ * @default "https://api.openai.com/v1"
570
+ */
571
+ baseUrl?: string;
572
+ /**
573
+ * API key for the LLM provider.
574
+ * Pass it via an environment variable to keep it out of source control.
575
+ *
576
+ * @default process.env.OPENAI_API_KEY
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * // Default — reads OPENAI_API_KEY automatically
581
+ * ai: { enabled: true }
582
+ *
583
+ * // Custom provider key
584
+ * ai: {
585
+ * enabled: true,
586
+ * apiKey: process.env.GROQ_API_KEY,
587
+ * }
588
+ * ```
589
+ */
590
+ apiKey?: string;
591
+ /**
592
+ * Maximum number of search results to include as context for the AI.
593
+ * More results = more context but higher token usage.
594
+ * @default 5
595
+ */
596
+ maxResults?: number;
597
+ /**
598
+ * Pre-filled suggested questions shown in the AI chat when empty.
599
+ * When a user clicks one, it fills the input and submits automatically.
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * ai: {
604
+ * enabled: true,
605
+ * suggestedQuestions: [
606
+ * "How do I install this?",
607
+ * "What themes are available?",
608
+ * "How do I create a custom component?",
609
+ * ],
610
+ * }
611
+ * ```
612
+ */
613
+ suggestedQuestions?: string[];
614
+ /**
615
+ * Display name for the AI assistant in the chat UI.
616
+ * Shown as the message label, header title, and passed to the loading component.
617
+ *
618
+ * @default "AI"
619
+ *
620
+ * @example
621
+ * ```ts
622
+ * ai: {
623
+ * enabled: true,
624
+ * aiLabel: "DocsBot",
625
+ * }
626
+ * ```
627
+ */
628
+ aiLabel?: string;
629
+ /**
630
+ * The npm package name used in import examples.
631
+ * The AI will use this in code snippets instead of generic placeholders.
632
+ *
633
+ * @example
634
+ * ```ts
635
+ * ai: {
636
+ * enabled: true,
637
+ * packageName: "@farming-labs/docs",
638
+ * }
639
+ * ```
640
+ */
641
+ packageName?: string;
642
+ /**
643
+ * The public URL of the documentation site.
644
+ * The AI will use this for links instead of relative paths.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * ai: {
649
+ * enabled: true,
650
+ * docsUrl: "https://docs.farming-labs.dev",
651
+ * }
652
+ * ```
653
+ */
654
+ docsUrl?: string;
655
+ /**
656
+ * Custom loading indicator shown while the AI is generating a response.
657
+ * Replaces the default "AI is thinking..." indicator.
658
+ *
659
+ * Pass a function that receives `{ name }` (the `aiLabel` value) and
660
+ * returns a React element. This way you don't need to duplicate the label.
661
+ *
662
+ * @example
663
+ * ```tsx
664
+ * ai: {
665
+ * enabled: true,
666
+ * aiLabel: "Sage",
667
+ * loadingComponent: ({ name }) => (
668
+ * <div className="flex items-center gap-2 text-sm text-zinc-400">
669
+ * <span className="animate-pulse">🤔</span>
670
+ * <span>{name} is thinking...</span>
671
+ * </div>
672
+ * ),
673
+ * }
674
+ * ```
675
+ */
676
+ loadingComponent?: (props: {
677
+ name: string;
678
+ }) => unknown;
679
+ }
408
680
  interface DocsConfig {
409
681
  /** Entry folder for docs (e.g. "docs" → /docs) */
410
682
  entry: string;
683
+ /** Path to the content directory. Defaults to `entry` value. */
684
+ contentDir?: string;
411
685
  /** Theme configuration - single source of truth for UI */
412
686
  theme?: DocsTheme;
687
+ /**
688
+ * GitHub repository URL or config. Enables "Edit on GitHub" links
689
+ * on each docs page footer, pointing to the source `.mdx` file.
690
+ *
691
+ * @example
692
+ * ```ts
693
+ * // Simple — branch defaults to "main"
694
+ * github: "https://github.com/Kinfe123/my-docs"
695
+ *
696
+ * // Monorepo — docs site lives in "website/" subdirectory
697
+ * github: {
698
+ * url: "https://github.com/farming-labs/docs",
699
+ * directory: "website",
700
+ * }
701
+ *
702
+ * // Custom branch
703
+ * github: {
704
+ * url: "https://github.com/Kinfe123/my-docs",
705
+ * branch: "develop",
706
+ * }
707
+ * ```
708
+ */
709
+ github?: string | GithubConfig;
413
710
  /**
414
711
  * Sidebar navigation header.
415
712
  * Customise the title shown at the top of the sidebar.
@@ -519,6 +816,39 @@ interface DocsConfig {
519
816
  * ```
520
817
  */
521
818
  pageActions?: PageActionsConfig;
819
+ /**
820
+ * AI-powered "Ask AI" chat for documentation.
821
+ *
822
+ * When enabled, the unified API route handler (`/api/docs`) accepts
823
+ * POST requests for AI chat. The handler uses RAG (Retrieval-Augmented
824
+ * Generation) — it searches relevant docs, builds context, and streams
825
+ * a response from an LLM.
826
+ *
827
+ * The API key defaults to `process.env.OPENAI_API_KEY`. For other providers,
828
+ * pass the key via `apiKey: process.env.YOUR_KEY`.
829
+ *
830
+ * @example
831
+ * ```ts
832
+ * // Enable with defaults (gpt-4o-mini, OPENAI_API_KEY)
833
+ * ai: { enabled: true }
834
+ *
835
+ * // Custom model + system prompt
836
+ * ai: {
837
+ * enabled: true,
838
+ * model: "gpt-4o",
839
+ * systemPrompt: "You are an expert on our SDK. Be concise.",
840
+ * }
841
+ *
842
+ * // Use a different provider (e.g. Groq)
843
+ * ai: {
844
+ * enabled: true,
845
+ * baseUrl: "https://api.groq.com/openai/v1",
846
+ * apiKey: process.env.GROQ_API_KEY,
847
+ * model: "llama-3.1-70b-versatile",
848
+ * }
849
+ * ```
850
+ */
851
+ ai?: AIConfig;
522
852
  /** SEO metadata - separate from theme */
523
853
  metadata?: DocsMetadata;
524
854
  /** Open Graph image handling */
@@ -572,7 +902,7 @@ declare function createTheme(baseTheme: DocsTheme): (overrides?: Partial<DocsThe
572
902
  * @example
573
903
  * ```ts
574
904
  * import { extendTheme } from "@farming-labs/docs";
575
- * import { fumadocs } from "@farming-labs/fumadocs/default";
905
+ * import { fumadocs } from "@farming-labs/theme/default";
576
906
  *
577
907
  * // Start with fumadocs defaults, override some values
578
908
  * export const myTheme = extendTheme(fumadocs(), {
@@ -594,4 +924,4 @@ declare function resolveTitle(pageTitle: string, metadata?: DocsMetadata): strin
594
924
  */
595
925
  declare function resolveOGImage(page: PageFrontmatter, ogConfig?: OGConfig, baseUrl?: string): string | undefined;
596
926
  //#endregion
597
- export { type BreadcrumbConfig, type CopyMarkdownConfig, type DocsConfig, type DocsMetadata, type DocsNav, type DocsTheme, type FontStyle, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, type PageActionsConfig, type PageFrontmatter, type SidebarConfig, type ThemeToggleConfig, type TypographyConfig, type UIConfig, createTheme, deepMerge, defineDocs, extendTheme, resolveOGImage, resolveTitle };
927
+ export { type AIConfig, type BreadcrumbConfig, type CopyMarkdownConfig, type DocsConfig, type DocsMetadata, type DocsNav, type DocsTheme, type FontStyle, type GithubConfig, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, type PageActionsConfig, type PageFrontmatter, type SidebarConfig, type ThemeToggleConfig, type TypographyConfig, type UIConfig, createTheme, deepMerge, defineDocs, extendTheme, resolveOGImage, resolveTitle };
package/dist/index.mjs CHANGED
@@ -5,14 +5,17 @@
5
5
  function defineDocs(config) {
6
6
  return {
7
7
  entry: config.entry ?? "docs",
8
+ contentDir: config.contentDir,
8
9
  theme: config.theme,
9
10
  nav: config.nav,
11
+ github: config.github,
10
12
  themeToggle: config.themeToggle,
11
13
  breadcrumb: config.breadcrumb,
12
14
  sidebar: config.sidebar,
13
15
  components: config.components,
14
16
  icons: config.icons,
15
17
  pageActions: config.pageActions,
18
+ ai: config.ai,
16
19
  metadata: config.metadata,
17
20
  og: config.og
18
21
  };
@@ -80,7 +83,7 @@ function createTheme(baseTheme) {
80
83
  * @example
81
84
  * ```ts
82
85
  * import { extendTheme } from "@farming-labs/docs";
83
- * import { fumadocs } from "@farming-labs/fumadocs/default";
86
+ * import { fumadocs } from "@farming-labs/theme/default";
84
87
  *
85
88
  * // Start with fumadocs defaults, override some values
86
89
  * export const myTheme = extendTheme(fumadocs(), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.2-beta.1",
3
+ "version": "0.0.2-beta.10",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "bin": {
17
- "farming-docs": "./dist/cli/index.mjs"
17
+ "docs": "./dist/cli/index.mjs"
18
18
  },
19
19
  "files": [
20
20
  "dist"