@farming-labs/docs 0.0.2-beta.9 → 0.0.2

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,78 @@ 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"
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"
152
212
  }
153
213
  };
154
214
  function getThemeInfo(theme) {
@@ -178,6 +238,17 @@ function sveltePageConfigImport(useAlias) {
178
238
  function svelteLayoutServerImport(useAlias) {
179
239
  return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
180
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
+ }
181
252
  function docsConfigTemplate(cfg) {
182
253
  const t = getThemeInfo(cfg.theme);
183
254
  return `\
@@ -292,7 +363,8 @@ const config = {
292
363
  export default config;
293
364
  `;
294
365
  }
295
- function tsconfigTemplate() {
366
+ /** @param useAlias - When false, paths (e.g. @/*) are omitted so no alias is added. */
367
+ function tsconfigTemplate(useAlias = false) {
296
368
  return `\
297
369
  {
298
370
  "compilerOptions": {
@@ -309,8 +381,7 @@ function tsconfigTemplate() {
309
381
  "isolatedModules": true,
310
382
  "jsx": "react-jsx",
311
383
  "incremental": true,
312
- "plugins": [{ "name": "next" }],
313
- "paths": { "@/*": ["./*"] }
384
+ "plugins": [{ "name": "next" }]${useAlias ? ",\n \"paths\": { \"@/*\": [\"./*\"] }" : ""}
314
385
  },
315
386
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
316
387
  "exclude": ["node_modules"]
@@ -527,7 +598,17 @@ function svelteDocsServerTemplate(cfg) {
527
598
  import { createDocsServer } from "@farming-labs/svelte/server";
528
599
  import config from "${svelteServerConfigImport(cfg.useAlias)}";
529
600
 
530
- export const { load, GET, POST } = createDocsServer(config);
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
+ });
531
612
  `;
532
613
  }
533
614
  function svelteDocsLayoutTemplate(cfg) {
@@ -758,29 +839,741 @@ pnpm build
758
839
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
759
840
  `;
760
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
+ }
761
1480
 
762
1481
  //#endregion
763
1482
  //#region src/cli/init.ts
764
- async function init() {
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 = {}) {
765
1491
  const cwd = process.cwd();
766
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
+ }
767
1547
  let framework = detectFramework(cwd);
768
1548
  if (framework) {
769
- const frameworkName = framework === "nextjs" ? "Next.js" : "SvelteKit";
1549
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "astro" ? "Astro" : "Nuxt";
770
1550
  p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
771
1551
  } else {
772
1552
  p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
773
1553
  const picked = await p.select({
774
1554
  message: "Which framework are you using?",
775
- options: [{
776
- value: "nextjs",
777
- label: "Next.js",
778
- hint: "React framework with App Router"
779
- }, {
780
- value: "sveltekit",
781
- label: "SvelteKit",
782
- hint: "Svelte framework with file-based routing"
783
- }]
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
+ ]
784
1577
  });
785
1578
  if (p.isCancel(picked)) {
786
1579
  p.outro(pc.red("Init cancelled."));
@@ -805,6 +1598,26 @@ async function init() {
805
1598
  value: "pixel-border",
806
1599
  label: "Pixel Border",
807
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"
808
1621
  }
809
1622
  ]
810
1623
  });
@@ -812,7 +1625,7 @@ async function init() {
812
1625
  p.outro(pc.red("Init cancelled."));
813
1626
  process.exit(0);
814
1627
  }
815
- const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)`;
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)`;
816
1629
  const useAlias = await p.confirm({
817
1630
  message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
818
1631
  initialValue: false
@@ -821,6 +1634,37 @@ async function init() {
821
1634
  p.outro(pc.red("Init cancelled."));
822
1635
  process.exit(0);
823
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
+ }
824
1668
  const entry = await p.text({
825
1669
  message: "Where should your docs live?",
826
1670
  placeholder: "docs",
@@ -838,7 +1682,7 @@ async function init() {
838
1682
  const entryPath = entry;
839
1683
  const detectedCssFiles = detectGlobalCssFiles(cwd);
840
1684
  let globalCssRelPath;
841
- const defaultCssPath = framework === "sveltekit" ? "src/app.css" : "app/globals.css";
1685
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
842
1686
  if (detectedCssFiles.length === 1) {
843
1687
  globalCssRelPath = detectedCssFiles[0];
844
1688
  p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
@@ -878,7 +1722,8 @@ async function init() {
878
1722
  theme,
879
1723
  projectName: pkgJson.name || "My Project",
880
1724
  framework,
881
- useAlias
1725
+ useAlias,
1726
+ astroAdapter
882
1727
  };
883
1728
  const s = p.spinner();
884
1729
  s.start("Scaffolding docs files");
@@ -889,6 +1734,8 @@ async function init() {
889
1734
  else skipped.push(rel);
890
1735
  }
891
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);
892
1739
  else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
893
1740
  s.stop("Files scaffolded");
894
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"));
@@ -899,6 +1746,10 @@ async function init() {
899
1746
  s2.start("Installing dependencies");
900
1747
  try {
901
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);
902
1753
  else {
903
1754
  exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
904
1755
  const devDeps = [
@@ -938,6 +1789,14 @@ async function init() {
938
1789
  cmd: "npx",
939
1790
  args: ["vite", "dev"],
940
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"
941
1800
  } : {
942
1801
  cmd: "npx",
943
1802
  args: [
@@ -947,7 +1806,7 @@ async function init() {
947
1806
  ],
948
1807
  waitFor: "Ready"
949
1808
  };
950
- const defaultPort = framework === "sveltekit" ? "5173" : "3000";
1809
+ const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : framework === "nuxt" ? "3000" : "3000";
951
1810
  try {
952
1811
  const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
953
1812
  const url = `http://localhost:${defaultPort}/${entryPath}`;
@@ -966,7 +1825,7 @@ async function init() {
966
1825
  });
967
1826
  });
968
1827
  } catch (err) {
969
- const manualCmd = framework === "sveltekit" ? "npx vite dev" : "npx next dev --webpack";
1828
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : framework === "nuxt" ? "npx nuxt dev" : "npx next dev --webpack";
970
1829
  p.log.error(`Could not start dev server. Try running manually:
971
1830
  ${pc.cyan(manualCmd)}`);
972
1831
  p.outro(pc.yellow("Setup complete. Start the server manually."));
@@ -996,7 +1855,7 @@ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
996
1855
  } else write(globalCssRelPath, globalCssTemplate(cfg.theme));
997
1856
  write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
998
1857
  write("postcss.config.mjs", postcssConfigTemplate());
999
- if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate());
1858
+ if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
1000
1859
  write(`app/${cfg.entry}/page.mdx`, welcomePageTemplate(cfg));
1001
1860
  write(`app/${cfg.entry}/installation/page.mdx`, installationPageTemplate(cfg));
1002
1861
  write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
@@ -1014,6 +1873,10 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1014
1873
  fumadocs: "fumadocs",
1015
1874
  darksharp: "darksharp",
1016
1875
  "pixel-border": "pixel-border",
1876
+ colorful: "colorful",
1877
+ darkbold: "darkbold",
1878
+ shiny: "shiny",
1879
+ greentree: "greentree",
1017
1880
  default: "fumadocs"
1018
1881
  }[cfg.theme] || "fumadocs";
1019
1882
  if (existingGlobalCss) {
@@ -1027,12 +1890,97 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1027
1890
  write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1028
1891
  write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1029
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
+ }
1030
1955
 
1031
1956
  //#endregion
1032
1957
  //#region src/cli/index.ts
1033
- const command = process.argv.slice(2)[0];
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
+ }
1034
1975
  async function main() {
1035
- if (!command || command === "init") await init();
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);
1036
1984
  else if (command === "--help" || command === "-h") printHelp();
1037
1985
  else if (command === "--version" || command === "-v") printVersion();
1038
1986
  else {
@@ -1053,11 +2001,15 @@ ${pc.dim("Commands:")}
1053
2001
  ${pc.cyan("init")} Scaffold docs in your project (default)
1054
2002
 
1055
2003
  ${pc.dim("Supported frameworks:")}
1056
- Next.js, SvelteKit
1057
-
1058
- ${pc.dim("Options:")}
1059
- ${pc.cyan("-h, --help")} Show this help message
1060
- ${pc.cyan("-v, --version")} Show version
2004
+ Next.js, SvelteKit, Astro, Nuxt
2005
+
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
1061
2013
  `);
1062
2014
  }
1063
2015
  function printVersion() {