@farming-labs/docs 0.0.2-beta.13 → 0.0.2-beta.15

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,7 @@ 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";
19
20
  return null;
20
21
  }
21
22
  function detectPackageManager(cwd) {
@@ -133,22 +134,28 @@ const THEME_INFO = {
133
134
  factory: "fumadocs",
134
135
  nextImport: "@farming-labs/theme",
135
136
  svelteImport: "@farming-labs/svelte-theme",
137
+ astroImport: "@farming-labs/astro-theme",
136
138
  nextCssImport: "default",
137
- svelteCssTheme: "fumadocs"
139
+ svelteCssTheme: "fumadocs",
140
+ astroCssTheme: "fumadocs"
138
141
  },
139
142
  darksharp: {
140
143
  factory: "darksharp",
141
144
  nextImport: "@farming-labs/theme/darksharp",
142
145
  svelteImport: "@farming-labs/svelte-theme/darksharp",
146
+ astroImport: "@farming-labs/astro-theme/darksharp",
143
147
  nextCssImport: "darksharp",
144
- svelteCssTheme: "darksharp"
148
+ svelteCssTheme: "darksharp",
149
+ astroCssTheme: "darksharp"
145
150
  },
146
151
  "pixel-border": {
147
152
  factory: "pixelBorder",
148
153
  nextImport: "@farming-labs/theme/pixel-border",
149
154
  svelteImport: "@farming-labs/svelte-theme/pixel-border",
155
+ astroImport: "@farming-labs/astro-theme/pixel-border",
150
156
  nextCssImport: "pixel-border",
151
- svelteCssTheme: "pixel-border"
157
+ svelteCssTheme: "pixel-border",
158
+ astroCssTheme: "pixel-border"
152
159
  }
153
160
  };
154
161
  function getThemeInfo(theme) {
@@ -178,6 +185,17 @@ function sveltePageConfigImport(useAlias) {
178
185
  function svelteLayoutServerImport(useAlias) {
179
186
  return useAlias ? "$lib/docs.server" : "../../lib/docs.server";
180
187
  }
188
+ function astroServerConfigImport(useAlias) {
189
+ return useAlias ? "@/lib/docs.config" : "./docs.config";
190
+ }
191
+ function astroPageConfigImport(useAlias, depth) {
192
+ if (useAlias) return "@/lib/docs.config";
193
+ return `${"../".repeat(depth)}lib/docs.config`;
194
+ }
195
+ function astroPageServerImport(useAlias, depth) {
196
+ if (useAlias) return "@/lib/docs.server";
197
+ return `${"../".repeat(depth)}lib/docs.server`;
198
+ }
181
199
  function docsConfigTemplate(cfg) {
182
200
  const t = getThemeInfo(cfg.theme);
183
201
  return `\
@@ -527,6 +545,7 @@ function svelteDocsServerTemplate(cfg) {
527
545
  import { createDocsServer } from "@farming-labs/svelte/server";
528
546
  import config from "${svelteServerConfigImport(cfg.useAlias)}";
529
547
 
548
+ // preload for production
530
549
  const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx,svx}", {
531
550
  query: "?raw",
532
551
  import: "default",
@@ -764,6 +783,327 @@ Build your docs for production:
764
783
  pnpm build
765
784
  \`\`\`
766
785
 
786
+ Deploy to Vercel, Netlify, or any Node.js hosting platform.
787
+ `;
788
+ }
789
+ function astroDocsConfigTemplate(cfg) {
790
+ const t = getThemeInfo(cfg.theme);
791
+ return `\
792
+ import { defineDocs } from "@farming-labs/docs";
793
+ import { ${t.factory} } from "${t.astroImport}";
794
+
795
+ export default defineDocs({
796
+ entry: "${cfg.entry}",
797
+ contentDir: "${cfg.entry}",
798
+ theme: ${t.factory}({
799
+ ui: {
800
+ colors: { primary: "#6366f1" },
801
+ },
802
+ }),
803
+
804
+ nav: {
805
+ title: "${cfg.projectName}",
806
+ url: "/${cfg.entry}",
807
+ },
808
+
809
+ breadcrumb: { enabled: true },
810
+
811
+ metadata: {
812
+ titleTemplate: "%s – ${cfg.projectName}",
813
+ description: "Documentation for ${cfg.projectName}",
814
+ },
815
+ });
816
+ `;
817
+ }
818
+ function astroDocsServerTemplate(cfg) {
819
+ return `\
820
+ import { createDocsServer } from "@farming-labs/astro/server";
821
+ import config from "${astroServerConfigImport(cfg.useAlias)}";
822
+
823
+ const contentFiles = import.meta.glob("/${cfg.entry ?? "docs"}/**/*.{md,mdx}", {
824
+ query: "?raw",
825
+ import: "default",
826
+ eager: true,
827
+ }) as Record<string, string>;
828
+
829
+ export const { load, GET, POST } = createDocsServer({
830
+ ...config,
831
+ _preloadedContent: contentFiles,
832
+ });
833
+ `;
834
+ }
835
+ const ASTRO_ADAPTER_INFO = {
836
+ vercel: {
837
+ pkg: "@astrojs/vercel",
838
+ import: "@astrojs/vercel"
839
+ },
840
+ netlify: {
841
+ pkg: "@astrojs/netlify",
842
+ import: "@astrojs/netlify"
843
+ },
844
+ node: {
845
+ pkg: "@astrojs/node",
846
+ import: "@astrojs/node"
847
+ },
848
+ cloudflare: {
849
+ pkg: "@astrojs/cloudflare",
850
+ import: "@astrojs/cloudflare"
851
+ }
852
+ };
853
+ function getAstroAdapterPkg(adapter) {
854
+ return ASTRO_ADAPTER_INFO[adapter]?.pkg ?? ASTRO_ADAPTER_INFO.vercel.pkg;
855
+ }
856
+ function astroConfigTemplate(adapter = "vercel") {
857
+ const info = ASTRO_ADAPTER_INFO[adapter] ?? ASTRO_ADAPTER_INFO.vercel;
858
+ const adapterCall = adapter === "node" ? `${adapter}({ mode: "standalone" })` : `${adapter}()`;
859
+ return `\
860
+ import { defineConfig } from "astro/config";
861
+ import ${adapter} from "${info.import}";
862
+
863
+ export default defineConfig({
864
+ output: "server",
865
+ adapter: ${adapterCall},
866
+ });
867
+ `;
868
+ }
869
+ function astroDocsPageTemplate(cfg) {
870
+ return `\
871
+ ---
872
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
873
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
874
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
875
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
876
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
877
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
878
+
879
+ const data = await load(Astro.url.pathname);
880
+ ---
881
+
882
+ <html lang="en">
883
+ <head>
884
+ <meta charset="utf-8" />
885
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
886
+ <title>{data.title} – Docs</title>
887
+ </head>
888
+ <body>
889
+ <DocsLayout tree={data.tree} config={config}>
890
+ <DocsContent data={data} config={config} />
891
+ </DocsLayout>
892
+ <SearchDialog config={config} />
893
+ </body>
894
+ </html>
895
+ `;
896
+ }
897
+ function astroDocsIndexTemplate(cfg) {
898
+ return `\
899
+ ---
900
+ import DocsLayout from "@farming-labs/astro-theme/src/components/DocsLayout.astro";
901
+ import DocsContent from "@farming-labs/astro-theme/src/components/DocsContent.astro";
902
+ import SearchDialog from "@farming-labs/astro-theme/src/components/SearchDialog.astro";
903
+ import config from "${astroPageConfigImport(cfg.useAlias, 2)}";
904
+ import { load } from "${astroPageServerImport(cfg.useAlias, 2)}";
905
+ import "${`@farming-labs/astro-theme/${getThemeInfo(cfg.theme).astroCssTheme}/css`}";
906
+
907
+ const data = await load(Astro.url.pathname);
908
+ ---
909
+
910
+ <html lang="en">
911
+ <head>
912
+ <meta charset="utf-8" />
913
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
914
+ <title>{data.title} – Docs</title>
915
+ </head>
916
+ <body>
917
+ <DocsLayout tree={data.tree} config={config}>
918
+ <DocsContent data={data} config={config} />
919
+ </DocsLayout>
920
+ <SearchDialog config={config} />
921
+ </body>
922
+ </html>
923
+ `;
924
+ }
925
+ function astroApiRouteTemplate(cfg) {
926
+ return `\
927
+ import type { APIRoute } from "astro";
928
+ import { GET as docsGET, POST as docsPOST } from "${astroPageServerImport(cfg.useAlias, 2)}";
929
+
930
+ export const GET: APIRoute = async ({ request }) => {
931
+ return docsGET({ request });
932
+ };
933
+
934
+ export const POST: APIRoute = async ({ request }) => {
935
+ return docsPOST({ request });
936
+ };
937
+ `;
938
+ }
939
+ function astroGlobalCssTemplate(theme) {
940
+ return `\
941
+ @import "@farming-labs/astro-theme/${theme}/css";
942
+ `;
943
+ }
944
+ function astroCssImportLine(theme) {
945
+ return `@import "@farming-labs/astro-theme/${theme}/css";`;
946
+ }
947
+ function injectAstroCssImport(existingContent, theme) {
948
+ const importLine = astroCssImportLine(theme);
949
+ if (existingContent.includes(importLine)) return null;
950
+ const lines = existingContent.split("\n");
951
+ const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
952
+ if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
953
+ else lines.unshift(importLine);
954
+ return lines.join("\n");
955
+ }
956
+ function astroWelcomePageTemplate(cfg) {
957
+ return `\
958
+ ---
959
+ title: "Documentation"
960
+ description: "Welcome to ${cfg.projectName} documentation"
961
+ ---
962
+
963
+ # Welcome to ${cfg.projectName}
964
+
965
+ Get started with our documentation. Browse the pages on the left to learn more.
966
+
967
+ ## Overview
968
+
969
+ This documentation was generated by \`@farming-labs/docs\`. Edit the markdown files in \`${cfg.entry}/\` to customize.
970
+
971
+ ## Features
972
+
973
+ - **Markdown Support** — Write docs with standard Markdown
974
+ - **Syntax Highlighting** — Code blocks with automatic highlighting
975
+ - **Dark Mode** — Built-in theme switching
976
+ - **Search** — Full-text search across all pages
977
+ - **Responsive** — Works on any screen size
978
+
979
+ ---
980
+
981
+ ## Next Steps
982
+
983
+ Start by reading the [Installation](/${cfg.entry}/installation) guide, then follow the [Quickstart](/${cfg.entry}/quickstart) to build something.
984
+ `;
985
+ }
986
+ function astroInstallationPageTemplate(cfg) {
987
+ const t = getThemeInfo(cfg.theme);
988
+ return `\
989
+ ---
990
+ title: "Installation"
991
+ description: "How to install and set up ${cfg.projectName}"
992
+ ---
993
+
994
+ # Installation
995
+
996
+ Follow these steps to install and configure ${cfg.projectName}.
997
+
998
+ ## Prerequisites
999
+
1000
+ - Node.js 18+
1001
+ - A package manager (pnpm, npm, or yarn)
1002
+
1003
+ ## Install Dependencies
1004
+
1005
+ \\\`\\\`\\\`bash
1006
+ pnpm add @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme
1007
+ \\\`\\\`\\\`
1008
+
1009
+ ## Configuration
1010
+
1011
+ Your project includes a \\\`docs.config.ts\\\` in \\\`src/lib/\\\`:
1012
+
1013
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1014
+ import { defineDocs } from "@farming-labs/docs";
1015
+ import { ${t.factory} } from "${t.astroImport}";
1016
+
1017
+ export default defineDocs({
1018
+ entry: "${cfg.entry}",
1019
+ contentDir: "${cfg.entry}",
1020
+ theme: ${t.factory}({
1021
+ ui: { colors: { primary: "#6366f1" } },
1022
+ }),
1023
+ });
1024
+ \\\`\\\`\\\`
1025
+
1026
+ ## Project Structure
1027
+
1028
+ \\\`\\\`\\\`
1029
+ ${cfg.entry}/ # Markdown content
1030
+ page.md # /${cfg.entry}
1031
+ installation/
1032
+ page.md # /${cfg.entry}/installation
1033
+ quickstart/
1034
+ page.md # /${cfg.entry}/quickstart
1035
+ src/
1036
+ lib/
1037
+ docs.config.ts # Docs configuration
1038
+ docs.server.ts # Server-side docs loader
1039
+ pages/
1040
+ ${cfg.entry}/
1041
+ index.astro # Docs index page
1042
+ [...slug].astro # Dynamic doc page
1043
+ api/
1044
+ ${cfg.entry}.ts # Search/AI API route
1045
+ \\\`\\\`\\\`
1046
+
1047
+ ## What's Next?
1048
+
1049
+ Head to the [Quickstart](/${cfg.entry}/quickstart) guide to start writing your first page.
1050
+ `;
1051
+ }
1052
+ function astroQuickstartPageTemplate(cfg) {
1053
+ const t = getThemeInfo(cfg.theme);
1054
+ return `\
1055
+ ---
1056
+ title: "Quickstart"
1057
+ description: "Get up and running in minutes"
1058
+ ---
1059
+
1060
+ # Quickstart
1061
+
1062
+ This guide walks you through creating your first documentation page.
1063
+
1064
+ ## Creating a Page
1065
+
1066
+ Create a new folder under \\\`${cfg.entry}/\\\` with a \\\`page.md\\\` file:
1067
+
1068
+ \\\`\\\`\\\`bash
1069
+ mkdir -p ${cfg.entry}/my-page
1070
+ \\\`\\\`\\\`
1071
+
1072
+ Then create \\\`${cfg.entry}/my-page/page.md\\\`:
1073
+
1074
+ \\\`\\\`\\\`md
1075
+ ---
1076
+ title: "My Page"
1077
+ description: "A custom documentation page"
1078
+ ---
1079
+
1080
+ # My Page
1081
+
1082
+ Write your content here using **Markdown**.
1083
+ \\\`\\\`\\\`
1084
+
1085
+ Your page is now available at \\\`/${cfg.entry}/my-page\\\`.
1086
+
1087
+ ## Customizing the Theme
1088
+
1089
+ Edit \\\`src/lib/docs.config.ts\\\` to change colors, typography, and component defaults:
1090
+
1091
+ \\\`\\\`\\\`ts title="src/lib/docs.config.ts"
1092
+ theme: ${t.factory}({
1093
+ ui: {
1094
+ colors: { primary: "#22c55e" },
1095
+ },
1096
+ }),
1097
+ \\\`\\\`\\\`
1098
+
1099
+ ## Deploying
1100
+
1101
+ Build your docs for production:
1102
+
1103
+ \\\`\\\`\\\`bash
1104
+ pnpm build
1105
+ \\\`\\\`\\\`
1106
+
767
1107
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
768
1108
  `;
769
1109
  }
@@ -775,21 +1115,29 @@ async function init() {
775
1115
  p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
776
1116
  let framework = detectFramework(cwd);
777
1117
  if (framework) {
778
- const frameworkName = framework === "nextjs" ? "Next.js" : "SvelteKit";
1118
+ const frameworkName = framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : "Astro";
779
1119
  p.log.success(`Detected framework: ${pc.cyan(frameworkName)}`);
780
1120
  } else {
781
1121
  p.log.warn("Could not auto-detect a framework from " + pc.cyan("package.json") + ".");
782
1122
  const picked = await p.select({
783
1123
  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
- }]
1124
+ options: [
1125
+ {
1126
+ value: "nextjs",
1127
+ label: "Next.js",
1128
+ hint: "React framework with App Router"
1129
+ },
1130
+ {
1131
+ value: "sveltekit",
1132
+ label: "SvelteKit",
1133
+ hint: "Svelte framework with file-based routing"
1134
+ },
1135
+ {
1136
+ value: "astro",
1137
+ label: "Astro",
1138
+ hint: "Content-focused framework with island architecture"
1139
+ }
1140
+ ]
793
1141
  });
794
1142
  if (p.isCancel(picked)) {
795
1143
  p.outro(pc.red("Init cancelled."));
@@ -821,7 +1169,7 @@ async function init() {
821
1169
  p.outro(pc.red("Init cancelled."));
822
1170
  process.exit(0);
823
1171
  }
824
- const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)`;
1172
+ const aliasHint = framework === "nextjs" ? `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)` : framework === "sveltekit" ? `Uses ${pc.cyan("$lib/")} prefix (SvelteKit built-in)` : `Uses ${pc.cyan("@/")} prefix (requires tsconfig paths)`;
825
1173
  const useAlias = await p.confirm({
826
1174
  message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
827
1175
  initialValue: false
@@ -830,6 +1178,37 @@ async function init() {
830
1178
  p.outro(pc.red("Init cancelled."));
831
1179
  process.exit(0);
832
1180
  }
1181
+ let astroAdapter;
1182
+ if (framework === "astro") {
1183
+ const adapter = await p.select({
1184
+ message: "Where will you deploy?",
1185
+ options: [
1186
+ {
1187
+ value: "vercel",
1188
+ label: "Vercel",
1189
+ hint: "Recommended for most projects"
1190
+ },
1191
+ {
1192
+ value: "netlify",
1193
+ label: "Netlify"
1194
+ },
1195
+ {
1196
+ value: "cloudflare",
1197
+ label: "Cloudflare Pages"
1198
+ },
1199
+ {
1200
+ value: "node",
1201
+ label: "Node.js / Docker",
1202
+ hint: "Self-hosted standalone server"
1203
+ }
1204
+ ]
1205
+ });
1206
+ if (p.isCancel(adapter)) {
1207
+ p.outro(pc.red("Init cancelled."));
1208
+ process.exit(0);
1209
+ }
1210
+ astroAdapter = adapter;
1211
+ }
833
1212
  const entry = await p.text({
834
1213
  message: "Where should your docs live?",
835
1214
  placeholder: "docs",
@@ -847,7 +1226,7 @@ async function init() {
847
1226
  const entryPath = entry;
848
1227
  const detectedCssFiles = detectGlobalCssFiles(cwd);
849
1228
  let globalCssRelPath;
850
- const defaultCssPath = framework === "sveltekit" ? "src/app.css" : "app/globals.css";
1229
+ const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : "app/globals.css";
851
1230
  if (detectedCssFiles.length === 1) {
852
1231
  globalCssRelPath = detectedCssFiles[0];
853
1232
  p.log.info(`Found global CSS at ${pc.cyan(globalCssRelPath)}`);
@@ -887,7 +1266,8 @@ async function init() {
887
1266
  theme,
888
1267
  projectName: pkgJson.name || "My Project",
889
1268
  framework,
890
- useAlias
1269
+ useAlias,
1270
+ astroAdapter
891
1271
  };
892
1272
  const s = p.spinner();
893
1273
  s.start("Scaffolding docs files");
@@ -898,6 +1278,7 @@ async function init() {
898
1278
  else skipped.push(rel);
899
1279
  }
900
1280
  if (framework === "sveltekit") scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written);
1281
+ else if (framework === "astro") scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written);
901
1282
  else scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written);
902
1283
  s.stop("Files scaffolded");
903
1284
  if (written.length > 0) p.log.success(`Created ${written.length} file${written.length > 1 ? "s" : ""}:\n` + written.map((f) => ` ${pc.green("+")} ${f}`).join("\n"));
@@ -908,7 +1289,10 @@ async function init() {
908
1289
  s2.start("Installing dependencies");
909
1290
  try {
910
1291
  if (framework === "sveltekit") exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/svelte @farming-labs/svelte-theme`, cwd);
911
- else {
1292
+ else if (framework === "astro") {
1293
+ const adapterPkg = getAstroAdapterPkg(cfg.astroAdapter ?? "vercel");
1294
+ exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/astro @farming-labs/astro-theme ${adapterPkg}`, cwd);
1295
+ } else {
912
1296
  exec(`${installCommand(pm)} @farming-labs/docs @farming-labs/next @farming-labs/theme`, cwd);
913
1297
  const devDeps = [
914
1298
  "@tailwindcss/postcss",
@@ -947,6 +1331,10 @@ async function init() {
947
1331
  cmd: "npx",
948
1332
  args: ["vite", "dev"],
949
1333
  waitFor: "ready"
1334
+ } : framework === "astro" ? {
1335
+ cmd: "npx",
1336
+ args: ["astro", "dev"],
1337
+ waitFor: "ready"
950
1338
  } : {
951
1339
  cmd: "npx",
952
1340
  args: [
@@ -956,7 +1344,7 @@ async function init() {
956
1344
  ],
957
1345
  waitFor: "Ready"
958
1346
  };
959
- const defaultPort = framework === "sveltekit" ? "5173" : "3000";
1347
+ const defaultPort = framework === "sveltekit" ? "5173" : framework === "astro" ? "4321" : "3000";
960
1348
  try {
961
1349
  const child = await spawnAndWaitFor(devCommand.cmd, devCommand.args, cwd, devCommand.waitFor, 6e4);
962
1350
  const url = `http://localhost:${defaultPort}/${entryPath}`;
@@ -975,7 +1363,7 @@ async function init() {
975
1363
  });
976
1364
  });
977
1365
  } catch (err) {
978
- const manualCmd = framework === "sveltekit" ? "npx vite dev" : "npx next dev --webpack";
1366
+ const manualCmd = framework === "sveltekit" ? "npx vite dev" : framework === "astro" ? "npx astro dev" : "npx next dev --webpack";
979
1367
  p.log.error(`Could not start dev server. Try running manually:
980
1368
  ${pc.cyan(manualCmd)}`);
981
1369
  p.outro(pc.yellow("Setup complete. Start the server manually."));
@@ -1036,6 +1424,32 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1036
1424
  write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1037
1425
  write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1038
1426
  }
1427
+ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1428
+ write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1429
+ write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1430
+ if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
1431
+ write(`src/pages/${cfg.entry}/index.astro`, astroDocsIndexTemplate(cfg));
1432
+ write(`src/pages/${cfg.entry}/[...slug].astro`, astroDocsPageTemplate(cfg));
1433
+ write(`src/pages/api/${cfg.entry}.ts`, astroApiRouteTemplate(cfg));
1434
+ const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1435
+ const existingGlobalCss = readFileSafe(globalCssAbsPath);
1436
+ const cssTheme = {
1437
+ fumadocs: "fumadocs",
1438
+ darksharp: "darksharp",
1439
+ "pixel-border": "pixel-border",
1440
+ default: "fumadocs"
1441
+ }[cfg.theme] || "fumadocs";
1442
+ if (existingGlobalCss) {
1443
+ const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
1444
+ if (injected) {
1445
+ writeFileSafe(globalCssAbsPath, injected, true);
1446
+ written.push(globalCssRelPath + " (updated)");
1447
+ } else skipped.push(globalCssRelPath + " (already configured)");
1448
+ } else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
1449
+ write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
1450
+ write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
1451
+ write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1452
+ }
1039
1453
 
1040
1454
  //#endregion
1041
1455
  //#region src/cli/index.ts
@@ -1062,7 +1476,7 @@ ${pc.dim("Commands:")}
1062
1476
  ${pc.cyan("init")} Scaffold docs in your project (default)
1063
1477
 
1064
1478
  ${pc.dim("Supported frameworks:")}
1065
- Next.js, SvelteKit
1479
+ Next.js, SvelteKit, Astro
1066
1480
 
1067
1481
  ${pc.dim("Options:")}
1068
1482
  ${pc.cyan("-h, --help")} Show this help message
package/dist/index.d.mts CHANGED
@@ -223,6 +223,8 @@ interface PageFrontmatter {
223
223
  icon?: string;
224
224
  /** Path to custom OG image for this page */
225
225
  ogImage?: string;
226
+ /** Sort order in the sidebar. Lower numbers appear first. Pages without `order` are sorted alphabetically after ordered pages. */
227
+ order?: number;
226
228
  }
227
229
  interface DocsNav {
228
230
  /**
@@ -677,6 +679,30 @@ interface AIConfig {
677
679
  name: string;
678
680
  }) => unknown;
679
681
  }
682
+ /**
683
+ * A single item in the slug-based sidebar ordering.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * ordering: [
688
+ * { slug: "installation" },
689
+ * { slug: "cli" },
690
+ * { slug: "themes", children: [
691
+ * { slug: "default" },
692
+ * { slug: "darksharp" },
693
+ * { slug: "pixel-border" },
694
+ * { slug: "creating-themes" },
695
+ * ]},
696
+ * { slug: "reference" },
697
+ * ]
698
+ * ```
699
+ */
700
+ interface OrderingItem {
701
+ /** Folder name (not the full path, just the directory name at this level) */
702
+ slug: string;
703
+ /** Ordering for child pages within this folder */
704
+ children?: OrderingItem[];
705
+ }
680
706
  interface DocsConfig {
681
707
  /** Entry folder for docs (e.g. "docs" → /docs) */
682
708
  entry: string;
@@ -849,6 +875,40 @@ interface DocsConfig {
849
875
  * ```
850
876
  */
851
877
  ai?: AIConfig;
878
+ /**
879
+ * Sidebar ordering strategy.
880
+ *
881
+ * - `"alphabetical"` — sort pages alphabetically by folder name (default)
882
+ * - `"numeric"` — sort by frontmatter `order` field (lower first, unset pages last)
883
+ * - `OrderingItem[]` — explicit slug-based ordering with nested children
884
+ *
885
+ * @default "alphabetical"
886
+ *
887
+ * @example
888
+ * ```ts
889
+ * // Alphabetical (default)
890
+ * ordering: "alphabetical",
891
+ *
892
+ * // Use frontmatter `order: 1`, `order: 2`, etc.
893
+ * ordering: "numeric",
894
+ *
895
+ * // Explicit slug-based ordering
896
+ * ordering: [
897
+ * { slug: "installation" },
898
+ * { slug: "cli" },
899
+ * { slug: "configuration" },
900
+ * { slug: "themes", children: [
901
+ * { slug: "default" },
902
+ * { slug: "darksharp" },
903
+ * { slug: "pixel-border" },
904
+ * { slug: "creating-themes" },
905
+ * ]},
906
+ * { slug: "customization" },
907
+ * { slug: "reference" },
908
+ * ]
909
+ * ```
910
+ */
911
+ ordering?: "alphabetical" | "numeric" | OrderingItem[];
852
912
  /** SEO metadata - separate from theme */
853
913
  metadata?: DocsMetadata;
854
914
  /** Open Graph image handling */
@@ -924,4 +984,4 @@ declare function resolveTitle(pageTitle: string, metadata?: DocsMetadata): strin
924
984
  */
925
985
  declare function resolveOGImage(page: PageFrontmatter, ogConfig?: OGConfig, baseUrl?: string): string | undefined;
926
986
  //#endregion
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 };
987
+ 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 OrderingItem, type PageActionsConfig, type PageFrontmatter, type SidebarConfig, type ThemeToggleConfig, type TypographyConfig, type UIConfig, createTheme, deepMerge, defineDocs, extendTheme, resolveOGImage, resolveTitle };
package/dist/index.mjs CHANGED
@@ -16,6 +16,7 @@ function defineDocs(config) {
16
16
  icons: config.icons,
17
17
  pageActions: config.pageActions,
18
18
  ai: config.ai,
19
+ ordering: config.ordering,
19
20
  metadata: config.metadata,
20
21
  og: config.og
21
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.2-beta.13",
3
+ "version": "0.0.2-beta.15",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",