@farming-labs/docs 0.0.2-beta.14 → 0.0.2-beta.17

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.
@@ -16,6 +16,8 @@ function detectFramework(cwd) {
16
16
  };
17
17
  if (allDeps["next"]) return "nextjs";
18
18
  if (allDeps["@sveltejs/kit"]) return "sveltekit";
19
+ if (allDeps["astro"]) return "astro";
20
+ if (allDeps["nuxt"]) return "nuxt";
19
21
  return null;
20
22
  }
21
23
  function detectPackageManager(cwd) {
@@ -65,7 +67,9 @@ const GLOBAL_CSS_CANDIDATES = [
65
67
  "styles/globals.css",
66
68
  "styles/global.css",
67
69
  "src/styles/globals.css",
68
- "src/styles/global.css"
70
+ "src/styles/global.css",
71
+ "assets/css/main.css",
72
+ "assets/main.css"
69
73
  ];
70
74
  /**
71
75
  * Find existing global CSS files in the project.
@@ -133,22 +137,45 @@ const THEME_INFO = {
133
137
  factory: "fumadocs",
134
138
  nextImport: "@farming-labs/theme",
135
139
  svelteImport: "@farming-labs/svelte-theme",
140
+ astroImport: "@farming-labs/astro-theme",
141
+ nuxtImport: "@farming-labs/nuxt-theme",
136
142
  nextCssImport: "default",
137
- svelteCssTheme: "fumadocs"
143
+ svelteCssTheme: "fumadocs",
144
+ astroCssTheme: "fumadocs",
145
+ nuxtCssTheme: "fumadocs"
138
146
  },
139
147
  darksharp: {
140
148
  factory: "darksharp",
141
149
  nextImport: "@farming-labs/theme/darksharp",
142
150
  svelteImport: "@farming-labs/svelte-theme/darksharp",
151
+ astroImport: "@farming-labs/astro-theme/darksharp",
152
+ nuxtImport: "@farming-labs/nuxt-theme/darksharp",
143
153
  nextCssImport: "darksharp",
144
- svelteCssTheme: "darksharp"
154
+ svelteCssTheme: "darksharp",
155
+ astroCssTheme: "darksharp",
156
+ nuxtCssTheme: "darksharp"
145
157
  },
146
158
  "pixel-border": {
147
159
  factory: "pixelBorder",
148
160
  nextImport: "@farming-labs/theme/pixel-border",
149
161
  svelteImport: "@farming-labs/svelte-theme/pixel-border",
162
+ astroImport: "@farming-labs/astro-theme/pixel-border",
163
+ nuxtImport: "@farming-labs/nuxt-theme/pixel-border",
150
164
  nextCssImport: "pixel-border",
151
- svelteCssTheme: "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"
152
179
  }
153
180
  };
154
181
  function getThemeInfo(theme) {
@@ -178,6 +205,17 @@ function sveltePageConfigImport(useAlias) {
178
205
  function svelteLayoutServerImport(useAlias) {
179
206
  return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
180
207
  }
208
+ function astroServerConfigImport(useAlias) {
209
+ return useAlias ? "@/lib/docs.config" : "./docs.config";
210
+ }
211
+ function astroPageConfigImport(useAlias, depth) {
212
+ if (useAlias) return "@/lib/docs.config";
213
+ return `${"../".repeat(depth)}lib/docs.config`;
214
+ }
215
+ function astroPageServerImport(useAlias, depth) {
216
+ if (useAlias) return "@/lib/docs.server";
217
+ return `${"../".repeat(depth)}lib/docs.server`;
218
+ }
181
219
  function docsConfigTemplate(cfg) {
182
220
  const t = getThemeInfo(cfg.theme);
183
221
  return `\
@@ -768,6 +806,643 @@ pnpm build
768
806
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
769
807
  `;
770
808
  }
809
+ function astroDocsConfigTemplate(cfg) {
810
+ const t = getThemeInfo(cfg.theme);
811
+ return `\
812
+ import { defineDocs } from "@farming-labs/docs";
813
+ import { ${t.factory} } from "${t.astroImport}";
814
+
815
+ export default defineDocs({
816
+ entry: "${cfg.entry}",
817
+ contentDir: "${cfg.entry}",
818
+ theme: ${t.factory}({
819
+ ui: {
820
+ colors: { primary: "#6366f1" },
821
+ },
822
+ }),
823
+
824
+ nav: {
825
+ title: "${cfg.projectName}",
826
+ url: "/${cfg.entry}",
827
+ },
828
+
829
+ breadcrumb: { enabled: true },
830
+
831
+ metadata: {
832
+ titleTemplate: "%s – ${cfg.projectName}",
833
+ description: "Documentation for ${cfg.projectName}",
834
+ },
835
+ });
836
+ `;
837
+ }
838
+ function astroDocsServerTemplate(cfg) {
839
+ return `\
840
+ import { createDocsServer } from "@farming-labs/astro/server";
841
+ import config from "${astroServerConfigImport(cfg.useAlias)}";
842
+
843
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
844
+ query: "?raw",
845
+ import: "default",
846
+ eager: true,
847
+ }) as Record<string, string>;
848
+
849
+ export const { load, GET, POST } = createDocsServer({
850
+ ...config,
851
+ _preloadedContent: contentFiles,
852
+ });
853
+ `;
854
+ }
855
+ const ASTRO_ADAPTER_INFO = {
856
+ vercel: {
857
+ pkg: "@astrojs/vercel",
858
+ import: "@astrojs/vercel"
859
+ },
860
+ netlify: {
861
+ pkg: "@astrojs/netlify",
862
+ import: "@astrojs/netlify"
863
+ },
864
+ node: {
865
+ pkg: "@astrojs/node",
866
+ import: "@astrojs/node"
867
+ },
868
+ cloudflare: {
869
+ pkg: "@astrojs/cloudflare",
870
+ import: "@astrojs/cloudflare"
871
+ }
872
+ };
873
+ function getAstroAdapterPkg(adapter) {
874
+ return ASTRO_ADAPTER_INFO[adapter]?.pkg ?? ASTRO_ADAPTER_INFO.vercel.pkg;
875
+ }
876
+ function astroConfigTemplate(adapter = "vercel") {
877
+ const info = ASTRO_ADAPTER_INFO[adapter] ?? ASTRO_ADAPTER_INFO.vercel;
878
+ const adapterCall = adapter === "node" ? `${adapter}({ mode: "standalone" })` : `${adapter}()`;
879
+ return `\
880
+ import { defineConfig } from "astro/config";
881
+ import ${adapter} from "${info.import}";
882
+
883
+ export default defineConfig({
884
+ output: "server",
885
+ adapter: ${adapterCall},
886
+ });
887
+ `;
888
+ }
889
+ function astroDocsPageTemplate(cfg) {
890
+ return `\
891
+ ---
892
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
893
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
894
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
895
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
896
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
897
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
898
+
899
+ const data = await load(Astro.url.pathname);
900
+ ---
901
+
902
+ <html lang="en">
903
+ <head>
904
+ <meta charset="utf-8" />
905
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
906
+ <title>{data.title} – Docs</title>
907
+ </head>
908
+ <body>
909
+ <DocsLayout tree={data.tree} config={config}>
910
+ <DocsContent data={data} config={config} />
911
+ </DocsLayout>
912
+ <SearchDialog config={config} />
913
+ </body>
914
+ </html>
915
+ `;
916
+ }
917
+ function astroDocsIndexTemplate(cfg) {
918
+ return `\
919
+ ---
920
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
921
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
922
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
923
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
924
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
925
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
926
+
927
+ const data = await load(Astro.url.pathname);
928
+ ---
929
+
930
+ <html lang="en">
931
+ <head>
932
+ <meta charset="utf-8" />
933
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
934
+ <title>{data.title} – Docs</title>
935
+ </head>
936
+ <body>
937
+ <DocsLayout tree={data.tree} config={config}>
938
+ <DocsContent data={data} config={config} />
939
+ </DocsLayout>
940
+ <SearchDialog config={config} />
941
+ </body>
942
+ </html>
943
+ `;
944
+ }
945
+ function astroApiRouteTemplate(cfg) {
946
+ return `\
947
+ import type { APIRoute } from "astro";
948
+ import { GET as docsGET, POST as docsPOST } from "${astroPageServerImport(cfg.useAlias, 2)}";
949
+
950
+ export const GET: APIRoute = async ({ request }) => {
951
+ return docsGET({ request });
952
+ };
953
+
954
+ export const POST: APIRoute = async ({ request }) => {
955
+ return docsPOST({ request });
956
+ };
957
+ `;
958
+ }
959
+ function astroGlobalCssTemplate(theme) {
960
+ return `\
961
+ @import "@farming-labs/astro-theme/${theme}/css";
962
+ `;
963
+ }
964
+ function astroCssImportLine(theme) {
965
+ return `@import "@farming-labs/astro-theme/${theme}/css";`;
966
+ }
967
+ function injectAstroCssImport(existingContent, theme) {
968
+ const importLine = astroCssImportLine(theme);
969
+ if (existingContent.includes(importLine)) return null;
970
+ const lines = existingContent.split("\n");
971
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
972
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
973
+ else lines.unshift(importLine);
974
+ return lines.join("\n");
975
+ }
976
+ function astroWelcomePageTemplate(cfg) {
977
+ return `\
978
+ ---
979
+ title: "Documentation"
980
+ description: "Welcome to ${cfg.projectName} documentation"
981
+ ---
982
+
983
+ # Welcome to ${cfg.projectName}
984
+
985
+ Get started with our documentation. Browse the pages on the left to learn more.
986
+
987
+ ## Overview
988
+
989
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
990
+
991
+ ## Features
992
+
993
+ - **Markdown Support** — Write docs with standard Markdown
994
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
995
+ - **Dark Mode** — Built-in theme switching
996
+ - **Search** — Full-text search across all pages
997
+ - **Responsive** — Works on any screen size
998
+
999
+ ---
1000
+
1001
+ ## Next Steps
1002
+
1003
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
1004
+ `;
1005
+ }
1006
+ function astroInstallationPageTemplate(cfg) {
1007
+ const t = getThemeInfo(cfg.theme);
1008
+ return `\
1009
+ ---
1010
+ title: "Installation"
1011
+ description: "How to install and set up ${cfg.projectName}"
1012
+ ---
1013
+
1014
+ # Installation
1015
+
1016
+ Follow these steps to install and configure ${cfg.projectName}.
1017
+
1018
+ ## Prerequisites
1019
+
1020
+ - Node.js 18+
1021
+ - A package manager (pnpm, npm, or yarn)
1022
+
1023
+ ## Install Dependencies
1024
+
1025
+ \\\`\\\`\\\`bash
1026
+ pnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
1027
+ \\\`\\\`\\\`
1028
+
1029
+ ## Configuration
1030
+
1031
+ Your project includes a \\\`docs.config.ts\\\` in \\\`src/lib/\\\`:
1032
+
1033
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1034
+ import { defineDocs } from "@farming-labs/docs";
1035
+ import { ${t.factory} } from "${t.astroImport}";
1036
+
1037
+ export default defineDocs({
1038
+ entry: "${cfg.entry}",
1039
+ contentDir: "${cfg.entry}",
1040
+ theme: ${t.factory}({
1041
+ ui: { colors: { primary: "#6366f1" } },
1042
+ }),
1043
+ });
1044
+ \\\`\\\`\\\`
1045
+
1046
+ ## Project Structure
1047
+
1048
+ \\\`\\\`\\\`
1049
+ ${cfg.entry}/ # Markdown content
1050
+ page.md # /${cfg.entry}
1051
+ installation/
1052
+ page.md # /${cfg.entry}/installation
1053
+ quickstart/
1054
+ page.md # /${cfg.entry}/quickstart
1055
+ src/
1056
+ lib/
1057
+ docs.config.ts # Docs configuration
1058
+ docs.server.ts # Server-side docs loader
1059
+ pages/
1060
+ ${cfg.entry}/
1061
+ index.astro # Docs index page
1062
+ [...slug].astro # Dynamic doc page
1063
+ api/
1064
+ ${cfg.entry}.ts # Search/AI API route
1065
+ \\\`\\\`\\\`
1066
+
1067
+ ## What's Next?
1068
+
1069
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1070
+ `;
1071
+ }
1072
+ function astroQuickstartPageTemplate(cfg) {
1073
+ const t = getThemeInfo(cfg.theme);
1074
+ return `\
1075
+ ---
1076
+ title: "Quickstart"
1077
+ description: "Get up and running in minutes"
1078
+ ---
1079
+
1080
+ # Quickstart
1081
+
1082
+ This guide walks you through creating your first documentation page.
1083
+
1084
+ ## Creating a Page
1085
+
1086
+ Create a new folder under \\\`${cfg.entry}/\\\` with a \\\`page.md\\\` file:
1087
+
1088
+ \\\`\\\`\\\`bash
1089
+ mkdir -p ${cfg.entry}/my-page
1090
+ \\\`\\\`\\\`
1091
+
1092
+ Then create \\\`${cfg.entry}/my-page/page.md\\\`:
1093
+
1094
+ \\\`\\\`\\\`md
1095
+ ---
1096
+ title: "My Page"
1097
+ description: "A custom documentation page"
1098
+ ---
1099
+
1100
+ # My Page
1101
+
1102
+ Write your content here using **Markdown**.
1103
+ \\\`\\\`\\\`
1104
+
1105
+ Your page is now available at \\\`/${cfg.entry}/my-page\\\`.
1106
+
1107
+ ## Customizing the Theme
1108
+
1109
+ Edit \\\`src/lib/docs.config.ts\\\` to change colors, typography, and component defaults:
1110
+
1111
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1112
+ theme: ${t.factory}({
1113
+ ui: {
1114
+ colors: { primary: "#22c55e" },
1115
+ },
1116
+ }),
1117
+ \\\`\\\`\\\`
1118
+
1119
+ ## Deploying
1120
+
1121
+ Build your docs for production:
1122
+
1123
+ \\\`\\\`\\\`bash
1124
+ pnpm build
1125
+ \\\`\\\`\\\`
1126
+
1127
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1128
+ `;
1129
+ }
1130
+ function nuxtDocsConfigTemplate(cfg) {
1131
+ const t = getThemeInfo(cfg.theme);
1132
+ return `\
1133
+ import { defineDocs } from "@farming-labs/docs";
1134
+ import { ${t.factory} } from "${t.nuxtImport}";
1135
+
1136
+ export default defineDocs({
1137
+ entry: "${cfg.entry}",
1138
+ contentDir: "${cfg.entry}",
1139
+ theme: ${t.factory}({
1140
+ ui: {
1141
+ colors: { primary: "#6366f1" },
1142
+ },
1143
+ }),
1144
+
1145
+ nav: {
1146
+ title: "${cfg.projectName}",
1147
+ url: "/${cfg.entry}",
1148
+ },
1149
+
1150
+ breadcrumb: { enabled: true },
1151
+
1152
+ metadata: {
1153
+ titleTemplate: "%s – ${cfg.projectName}",
1154
+ description: "Documentation for ${cfg.projectName}",
1155
+ },
1156
+ });
1157
+ `;
1158
+ }
1159
+ function nuxtDocsServerTemplate(cfg) {
1160
+ return `\
1161
+ import { createDocsServer } from "@farming-labs/nuxt/server";
1162
+ import config from "../../docs.config";
1163
+
1164
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
1165
+ query: "?raw",
1166
+ import: "default",
1167
+ eager: true,
1168
+ }) as Record<string, string>;
1169
+
1170
+ export const docsServer = createDocsServer({
1171
+ ...config,
1172
+ _preloadedContent: contentFiles,
1173
+ });
1174
+ `;
1175
+ }
1176
+ function nuxtServerApiDocsGetTemplate() {
1177
+ return `\
1178
+ import { getRequestURL } from "h3";
1179
+ import { docsServer } from "../utils/docs-server";
1180
+
1181
+ export default defineEventHandler((event) => {
1182
+ const url = getRequestURL(event);
1183
+ const request = new Request(url.href, {
1184
+ method: event.method,
1185
+ headers: event.headers,
1186
+ });
1187
+ return docsServer.GET({ request });
1188
+ });
1189
+ `;
1190
+ }
1191
+ function nuxtServerApiDocsPostTemplate() {
1192
+ return `\
1193
+ import { getRequestURL, readRawBody } from "h3";
1194
+ import { docsServer } from "../utils/docs-server";
1195
+
1196
+ export default defineEventHandler(async (event) => {
1197
+ const url = getRequestURL(event);
1198
+ const body = await readRawBody(event);
1199
+ const request = new Request(url.href, {
1200
+ method: "POST",
1201
+ headers: event.headers,
1202
+ body: body ?? undefined,
1203
+ });
1204
+ return docsServer.POST({ request });
1205
+ });
1206
+ `;
1207
+ }
1208
+ function nuxtServerApiDocsLoadTemplate() {
1209
+ return `\
1210
+ import { getQuery } from "h3";
1211
+ import { docsServer } from "../../utils/docs-server";
1212
+
1213
+ export default defineEventHandler(async (event) => {
1214
+ const query = getQuery(event);
1215
+ const pathname = (query.pathname as string) ?? "/docs";
1216
+ return docsServer.load(pathname);
1217
+ });
1218
+ `;
1219
+ }
1220
+ function nuxtDocsPageTemplate(cfg) {
1221
+ return `\
1222
+ <script setup lang="ts">
1223
+ import { DocsLayout, DocsContent } from "@farming-labs/nuxt-theme";
1224
+ import config from "${cfg.useAlias ? "~/docs.config" : "../../docs.config"}";
1225
+
1226
+ const route = useRoute();
1227
+ const pathname = computed(() => route.path);
1228
+
1229
+ const { data, error } = await useAsyncData(\`docs-\${pathname.value}\`, () =>
1230
+ $fetch("/api/docs/load", {
1231
+ query: { pathname: pathname.value },
1232
+ })
1233
+ );
1234
+
1235
+ if (error.value) {
1236
+ throw createError({
1237
+ statusCode: 404,
1238
+ statusMessage: "Page not found",
1239
+ });
1240
+ }
1241
+ <\/script>
1242
+
1243
+ <template>
1244
+ <div v-if="data" class="fd-docs-wrapper">
1245
+ <DocsLayout :tree="data.tree" :config="config">
1246
+ <DocsContent :data="data" :config="config" />
1247
+ </DocsLayout>
1248
+ </div>
1249
+ </template>
1250
+ `;
1251
+ }
1252
+ function nuxtConfigTemplate(cfg) {
1253
+ return `\
1254
+ export default defineNuxtConfig({
1255
+ compatibilityDate: "2024-11-01",
1256
+
1257
+ css: ["@farming-labs/nuxt-theme/${getThemeInfo(cfg.theme).nuxtCssTheme}/css"],
1258
+
1259
+ vite: {
1260
+ optimizeDeps: {
1261
+ include: ["@farming-labs/docs", "@farming-labs/nuxt", "@farming-labs/nuxt-theme"],
1262
+ },
1263
+ },
1264
+
1265
+ nitro: {
1266
+ moduleSideEffects: ["@farming-labs/nuxt/server"],
1267
+ },
1268
+ });
1269
+ `;
1270
+ }
1271
+ function nuxtWelcomePageTemplate(cfg) {
1272
+ return `\
1273
+ ---
1274
+ order: 1
1275
+ title: Documentation
1276
+ description: Welcome to ${cfg.projectName} documentation
1277
+ icon: book
1278
+ ---
1279
+
1280
+ # Welcome to ${cfg.projectName}
1281
+
1282
+ Get started with our documentation. Browse the pages on the left to learn more.
1283
+
1284
+ ## Overview
1285
+
1286
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
1287
+
1288
+ ## Features
1289
+
1290
+ - **Markdown Support** — Write docs with standard Markdown
1291
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
1292
+ - **Dark Mode** — Built-in theme switching
1293
+ - **Search** — Full-text search across all pages (⌘K)
1294
+ - **Responsive** — Works on any screen size
1295
+
1296
+ ---
1297
+
1298
+ ## Next Steps
1299
+
1300
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
1301
+ `;
1302
+ }
1303
+ function nuxtInstallationPageTemplate(cfg) {
1304
+ const t = getThemeInfo(cfg.theme);
1305
+ return `\
1306
+ ---
1307
+ order: 3
1308
+ title: Installation
1309
+ description: How to install and set up ${cfg.projectName}
1310
+ icon: terminal
1311
+ ---
1312
+
1313
+ # Installation
1314
+
1315
+ Follow these steps to install and configure ${cfg.projectName}.
1316
+
1317
+ ## Prerequisites
1318
+
1319
+ - Node.js 18+
1320
+ - A package manager (pnpm, npm, or yarn)
1321
+
1322
+ ## Install Dependencies
1323
+
1324
+ \`\`\`bash
1325
+ pnpm add @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme
1326
+ \`\`\`
1327
+
1328
+ ## Configuration
1329
+
1330
+ Your project includes a \`docs.config.ts\` at the root:
1331
+
1332
+ \`\`\`ts title="docs.config.ts"
1333
+ import { defineDocs } from "@farming-labs/docs";
1334
+ import { ${t.factory} } from "${t.nuxtImport}";
1335
+
1336
+ export default defineDocs({
1337
+ entry: "${cfg.entry}",
1338
+ contentDir: "${cfg.entry}",
1339
+ theme: ${t.factory}({
1340
+ ui: { colors: { primary: "#6366f1" } },
1341
+ }),
1342
+ });
1343
+ \`\`\`
1344
+
1345
+ ## Project Structure
1346
+
1347
+ \`\`\`
1348
+ ${cfg.entry}/ # Markdown content
1349
+ page.md
1350
+ installation/page.md
1351
+ quickstart/page.md
1352
+ server/
1353
+ utils/docs-server.ts # createDocsServer + preloaded content
1354
+ api/docs/
1355
+ load.get.ts # Page data API
1356
+ docs.get.ts # Search API
1357
+ docs.post.ts # AI chat API
1358
+ pages/
1359
+ ${cfg.entry}/[[...slug]].vue # Docs catch-all page
1360
+ docs.config.ts
1361
+ nuxt.config.ts
1362
+ \`\`\`
1363
+
1364
+ ## What's Next?
1365
+
1366
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1367
+ `;
1368
+ }
1369
+ function nuxtQuickstartPageTemplate(cfg) {
1370
+ const t = getThemeInfo(cfg.theme);
1371
+ return `\
1372
+ ---
1373
+ order: 2
1374
+ title: Quickstart
1375
+ description: Get up and running in minutes
1376
+ icon: rocket
1377
+ ---
1378
+
1379
+ # Quickstart
1380
+
1381
+ This guide walks you through creating your first documentation page.
1382
+
1383
+ ## Creating a Page
1384
+
1385
+ Create a new folder under \`${cfg.entry}/\` with a \`page.md\` file:
1386
+
1387
+ \`\`\`bash
1388
+ mkdir -p ${cfg.entry}/my-page
1389
+ \`\`\`
1390
+
1391
+ Then create \`${cfg.entry}/my-page/page.md\`:
1392
+
1393
+ \`\`\`md
1394
+ ---
1395
+ title: "My Page"
1396
+ description: "A custom documentation page"
1397
+ ---
1398
+
1399
+ # My Page
1400
+
1401
+ Write your content here using **Markdown**.
1402
+ \`\`\`
1403
+
1404
+ Your page is now available at \`/${cfg.entry}/my-page\`.
1405
+
1406
+ ## Customizing the Theme
1407
+
1408
+ Edit \`docs.config.ts\` to change colors and typography:
1409
+
1410
+ \`\`\`ts
1411
+ theme: ${t.factory}({
1412
+ ui: {
1413
+ colors: { primary: "#22c55e" },
1414
+ },
1415
+ }),
1416
+ \`\`\`
1417
+
1418
+ ## Deploying
1419
+
1420
+ Build your docs for production:
1421
+
1422
+ \`\`\`bash
1423
+ pnpm build
1424
+ \`\`\`
1425
+
1426
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1427
+ `;
1428
+ }
1429
+ function nuxtGlobalCssTemplate(theme) {
1430
+ return `\
1431
+ @import "@farming-labs/nuxt-theme/${theme}/css";
1432
+ `;
1433
+ }
1434
+ function nuxtCssImportLine(theme) {
1435
+ return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
1436
+ }
1437
+ function injectNuxtCssImport(existingContent, theme) {
1438
+ const importLine = nuxtCssImportLine(theme);
1439
+ if (existingContent.includes(importLine)) return null;
1440
+ const lines = existingContent.split("\n");
1441
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1442
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
1443
+ else lines.unshift(importLine);
1444
+ return lines.join("\n");
1445
+ }
771
1446
 
772
1447
  //#endregion
773
1448
  //#region src/cli/init.ts
@@ -776,21 +1451,34 @@ async function init() {
776
1451
  p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
777
1452
  let framework = detectFramework(cwd);
778
1453
  if (framework) {
779
- const frameworkName = framework === "nextjs" ? "Next.js" : "SvelteKit";
1454
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
780
1455
  p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
781
1456
  } else {
782
1457
  p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
783
1458
  const picked = await p.select({
784
1459
  message: "Which framework are you using?",
785
- options: [{
786
- value: "nextjs",
787
- label: "Next.js",
788
- hint: "React framework with App Router"
789
- }, {
790
- value: "sveltekit",
791
- label: "SvelteKit",
792
- hint: "Svelte framework with file-based routing"
793
- }]
1460
+ options: [
1461
+ {
1462
+ value: "nextjs",
1463
+ label: "Next.js",
1464
+ hint: "React framework with App Router"
1465
+ },
1466
+ {
1467
+ value: "sveltekit",
1468
+ label: "SvelteKit",
1469
+ hint: "Svelte framework with file-based routing"
1470
+ },
1471
+ {
1472
+ value: "astro",
1473
+ label: "Astro",
1474
+ hint: "Content-focused framework with island architecture"
1475
+ },
1476
+ {
1477
+ value: "nuxt",
1478
+ label: "Nuxt",
1479
+ hint: "Vue 3 framework with file-based routing and Nitro server"
1480
+ }
1481
+ ]
794
1482
  });
795
1483
  if (p.isCancel(picked)) {
796
1484
  p.outro(pc.red("Init cancelled."));
@@ -815,6 +1503,11 @@ async function init() {
815
1503
  value: "pixel-border",
816
1504
  label: "Pixel Border",
817
1505
  hint: "Rounded borders, pixel-perfect spacing, refined sidebar"
1506
+ },
1507
+ {
1508
+ value: "colorful",
1509
+ label: "Colorful",
1510
+ hint: "Fumadocs-style neutral theme with description support"
818
1511
  }
819
1512
  ]
820
1513
  });
@@ -822,7 +1515,7 @@ async function init() {
822
1515
  p.outro(pc.red("Init cancelled."));
823
1516
  process.exit(0);
824
1517
  }
825
- const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)`;
1518
+ 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)`;
826
1519
  const useAlias = await p.confirm({
827
1520
  message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
828
1521
  initialValue: false
@@ -831,6 +1524,37 @@ async function init() {
831
1524
  p.outro(pc.red("Init cancelled."));
832
1525
  process.exit(0);
833
1526
  }
1527
+ let astroAdapter;
1528
+ if (framework === "astro") {
1529
+ const adapter = await p.select({
1530
+ message: "Where will you deploy?",
1531
+ options: [
1532
+ {
1533
+ value: "vercel",
1534
+ label: "Vercel",
1535
+ hint: "Recommended for most projects"
1536
+ },
1537
+ {
1538
+ value: "netlify",
1539
+ label: "Netlify"
1540
+ },
1541
+ {
1542
+ value: "cloudflare",
1543
+ label: "Cloudflare Pages"
1544
+ },
1545
+ {
1546
+ value: "node",
1547
+ label: "Node.js / Docker",
1548
+ hint: "Self-hosted standalone server"
1549
+ }
1550
+ ]
1551
+ });
1552
+ if (p.isCancel(adapter)) {
1553
+ p.outro(pc.red("Init cancelled."));
1554
+ process.exit(0);
1555
+ }
1556
+ astroAdapter = adapter;
1557
+ }
834
1558
  const entry = await p.text({
835
1559
  message: "Where should your docs live?",
836
1560
  placeholder: "docs",
@@ -848,7 +1572,7 @@ async function init() {
848
1572
  const entryPath = entry;
849
1573
  const detectedCssFiles = detectGlobalCssFiles(cwd);
850
1574
  let globalCssRelPath;
851
- const defaultCssPath = framework === "sveltekit" ? "src/app.css" : "app/globals.css";
1575
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
852
1576
  if (detectedCssFiles.length === 1) {
853
1577
  globalCssRelPath = detectedCssFiles[0];
854
1578
  p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
@@ -888,7 +1612,8 @@ async function init() {
888
1612
  theme,
889
1613
  projectName: pkgJson.name || "My Project",
890
1614
  framework,
891
- useAlias
1615
+ useAlias,
1616
+ astroAdapter
892
1617
  };
893
1618
  const s = p.spinner();
894
1619
  s.start("Scaffolding docs files");
@@ -899,6 +1624,8 @@ async function init() {
899
1624
  else skipped.push(rel);
900
1625
  }
901
1626
  if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
1627
+ else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
1628
+ else if (framework === "nuxt") scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written);
902
1629
  else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
903
1630
  s.stop("Files scaffolded");
904
1631
  if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
@@ -909,6 +1636,10 @@ async function init() {
909
1636
  s2.start("Installing dependencies");
910
1637
  try {
911
1638
  if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
1639
+ else if (framework === "astro") {
1640
+ const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
1641
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
1642
+ } else if (framework === "nuxt") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/nuxt @farming-labs/nuxt-theme`, cwd);
912
1643
  else {
913
1644
  exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
914
1645
  const devDeps = [
@@ -948,6 +1679,14 @@ async function init() {
948
1679
  cmd: "npx",
949
1680
  args: ["vite", "dev"],
950
1681
  waitFor: "ready"
1682
+ } : framework === "astro" ? {
1683
+ cmd: "npx",
1684
+ args: ["astro", "dev"],
1685
+ waitFor: "ready"
1686
+ } : framework === "nuxt" ? {
1687
+ cmd: "npx",
1688
+ args: ["nuxt", "dev"],
1689
+ waitFor: "Local"
951
1690
  } : {
952
1691
  cmd: "npx",
953
1692
  args: [
@@ -957,7 +1696,7 @@ async function init() {
957
1696
  ],
958
1697
  waitFor: "Ready"
959
1698
  };
960
- const defaultPort = framework === "sveltekit" ? "5173" : "3000";
1699
+ const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
961
1700
  try {
962
1701
  const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
963
1702
  const url = `http://localhost:${defaultPort}/${entryPath}`;
@@ -976,7 +1715,7 @@ async function init() {
976
1715
  });
977
1716
  });
978
1717
  } catch (err) {
979
- const manualCmd = framework === "sveltekit" ? "npx vite dev" : "npx next dev --webpack";
1718
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
980
1719
  p.log.error(`Could not start dev server. Try running manually:
981
1720
  ${pc.cyan(manualCmd)}`);
982
1721
  p.outro(pc.yellow("Setup complete. Start the server manually."));
@@ -1024,6 +1763,7 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1024
1763
  fumadocs: "fumadocs",
1025
1764
  darksharp: "darksharp",
1026
1765
  "pixel-border": "pixel-border",
1766
+ colorful: "colorful",
1027
1767
  default: "fumadocs"
1028
1768
  }[cfg.theme] || "fumadocs";
1029
1769
  if (existingGlobalCss) {
@@ -1037,6 +1777,61 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1037
1777
  write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1038
1778
  write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1039
1779
  }
1780
+ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1781
+ write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1782
+ write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1783
+ if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
1784
+ write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
1785
+ write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
1786
+ write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
1787
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1788
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1789
+ const cssTheme = {
1790
+ fumadocs: "fumadocs",
1791
+ darksharp: "darksharp",
1792
+ "pixel-border": "pixel-border",
1793
+ colorful: "colorful",
1794
+ default: "fumadocs"
1795
+ }[cfg.theme] || "fumadocs";
1796
+ if (existingGlobalCss) {
1797
+ const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
1798
+ if (injected) {
1799
+ writeFileSafe(globalCssAbsPath, injected, true);
1800
+ written.push(globalCssRelPath + " (updated)");
1801
+ } else skipped.push(globalCssRelPath + " (already configured)");
1802
+ } else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
1803
+ write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
1804
+ write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
1805
+ write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1806
+ }
1807
+ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
1808
+ write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
1809
+ write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
1810
+ write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
1811
+ write("server/api/docs.post.ts", nuxtServerApiDocsPostTemplate());
1812
+ write("server/api/docs/load.get.ts", nuxtServerApiDocsLoadTemplate());
1813
+ write(`pages/${cfg.entry}/[[...slug]].vue`, nuxtDocsPageTemplate(cfg));
1814
+ path.join(cwd, "nuxt.config.ts");
1815
+ if (!fileExists(path.join(cwd, "nuxt.config.ts")) && !fileExists(path.join(cwd, "nuxt.config.js"))) write("nuxt.config.ts", nuxtConfigTemplate(cfg));
1816
+ const cssTheme = {
1817
+ fumadocs: "fumadocs",
1818
+ darksharp: "darksharp",
1819
+ "pixel-border": "pixel-border",
1820
+ default: "fumadocs"
1821
+ }[cfg.theme] || "fumadocs";
1822
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1823
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1824
+ if (existingGlobalCss) {
1825
+ const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
1826
+ if (injected) {
1827
+ writeFileSafe(globalCssAbsPath, injected, true);
1828
+ written.push(globalCssRelPath + " (updated)");
1829
+ } else skipped.push(globalCssRelPath + " (already configured)");
1830
+ } else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
1831
+ write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
1832
+ write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
1833
+ write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
1834
+ }
1040
1835
 
1041
1836
  //#endregion
1042
1837
  //#region src/cli/index.ts
@@ -1063,7 +1858,7 @@ ${pc.dim("Commands:")}
1063
1858
  ${pc.cyan("init")} Scaffold docs in your project (default)
1064
1859
 
1065
1860
  ${pc.dim("Supported frameworks:")}
1066
- Next.js, SvelteKit
1861
+ Next.js, SvelteKit, Astro, Nuxt
1067
1862
 
1068
1863
  ${pc.dim("Options:")}
1069
1864
  ${pc.cyan("-h, --help")} Show this help message
package/dist/index.d.mts CHANGED
@@ -137,6 +137,13 @@ interface UIConfig {
137
137
  toc?: {
138
138
  enabled?: boolean;
139
139
  depth?: number;
140
+ /**
141
+ * Visual style of the TOC indicator.
142
+ * - `"default"` — highlight active link text color only
143
+ * - `"directional"` — animated thumb bar that tracks active headings (fumadocs style)
144
+ * @default "default"
145
+ */
146
+ style?: "default" | "directional";
140
147
  };
141
148
  header?: {
142
149
  height?: number;
@@ -533,19 +540,44 @@ interface AIConfig {
533
540
  */
534
541
  floatingStyle?: "panel" | "modal" | "popover" | "full-modal";
535
542
  /**
536
- * Custom trigger component for the floating chat button.
543
+ * Custom trigger component for the floating chat button (Next.js only).
537
544
  * Only used when `mode` is `"floating"`.
538
545
  *
539
- * Pass a React element to replace the default sparkles button.
540
- * The element receives an `onClick` handler automatically.
546
+ * The click handler is attached automatically by the wrapper.
547
+ *
548
+ * - **Next.js**: Pass a JSX element via this config option.
549
+ * - **SvelteKit**: Use the `aiTrigger` snippet on `<DocsLayout>`.
550
+ * - **Nuxt / Vue**: Use the `ai-trigger` slot on `<DocsLayout>`.
551
+ * - **Astro**: Use `<MyTrigger slot="ai-trigger" />` on `<DocsLayout>`.
541
552
  *
542
553
  * @example
543
554
  * ```tsx
544
- * ai: {
545
- * enabled: true,
546
- * mode: "floating",
547
- * triggerComponent: <button className="my-chat-btn">💬 Help</button>,
548
- * }
555
+ * // Next.js — pass JSX directly in config
556
+ * triggerComponent: <button className="my-chat-btn">Ask AI</button>,
557
+ * ```
558
+ *
559
+ * ```svelte
560
+ * <!-- SvelteKit — use snippet in layout -->
561
+ * <DocsLayout {tree} {config}>
562
+ * {#snippet aiTrigger()}<AskAITrigger />{/snippet}
563
+ * {@render children()}
564
+ * </DocsLayout>
565
+ * ```
566
+ *
567
+ * ```vue
568
+ * <!-- Nuxt / Vue — use named slot in layout -->
569
+ * <DocsLayout :tree="tree" :config="config">
570
+ * <template #ai-trigger><AskAITrigger /></template>
571
+ * <DocsContent />
572
+ * </DocsLayout>
573
+ * ```
574
+ *
575
+ * ```astro
576
+ * <!-- Astro — use named slot in layout -->
577
+ * <DocsLayout tree={tree} config={config}>
578
+ * <AskAITrigger slot="ai-trigger" />
579
+ * <DocsContent />
580
+ * </DocsLayout>
549
581
  * ```
550
582
  */
551
583
  triggerComponent?: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.2-beta.14",
3
+ "version": "0.0.2-beta.17",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",