@farming-labs/docs 0.0.15 → 0.0.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.
Files changed (2) hide show
  1. package/dist/cli/index.mjs +316 -41
  2. package/package.json +1 -1
@@ -214,6 +214,49 @@ const THEME_INFO = {
214
214
  function getThemeInfo(theme) {
215
215
  return THEME_INFO[theme] ?? THEME_INFO.fumadocs;
216
216
  }
217
+ function getThemeExportName(themeName) {
218
+ const base = themeName.replace(/\.ts$/i, "").trim();
219
+ if (!base) return "customTheme";
220
+ return base.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toLowerCase());
221
+ }
222
+ function getCustomThemeCssImportPath(globalCssRelPath, themeName) {
223
+ if (globalCssRelPath.startsWith("app/")) return `../themes/${themeName}.css`;
224
+ if (globalCssRelPath.startsWith("src/")) return `../../themes/${themeName}.css`;
225
+ return `../themes/${themeName}.css`;
226
+ }
227
+ /** Content for themes/{name}.ts - createTheme with the given name */
228
+ function customThemeTsTemplate(themeName) {
229
+ return `\
230
+ import { createTheme } from "@farming-labs/docs";
231
+
232
+ export const ${getThemeExportName(themeName)} = createTheme({
233
+ name: "${themeName.replace(/\.ts$/i, "")}",
234
+ ui: {
235
+ colors: {
236
+ primary: "#e11d48",
237
+ background: "#09090b",
238
+ foreground: "#fafafa",
239
+ muted: "#71717a",
240
+ border: "#27272a",
241
+ },
242
+ radius: "0.5rem",
243
+ },
244
+ });
245
+ `;
246
+ }
247
+ function customThemeCssTemplate(themeName) {
248
+ return `\
249
+ /* Custom theme: ${themeName} - edit variables and selectors as needed */
250
+ @import "@farming-labs/theme/presets/black";
251
+
252
+ .dark {
253
+ --color-fd-primary: #e11d48;
254
+ --color-fd-background: #09090b;
255
+ --color-fd-border: #27272a;
256
+ --radius: 0.5rem;
257
+ }
258
+ `;
259
+ }
217
260
  /** Config import for Next.js app/layout.tsx → root docs.config */
218
261
  function nextRootLayoutConfigImport(useAlias) {
219
262
  return useAlias ? "@/docs.config" : "../docs.config";
@@ -250,6 +293,27 @@ function astroPageServerImport(useAlias, depth) {
250
293
  return `${"../".repeat(depth)}lib/docs.server`;
251
294
  }
252
295
  function docsConfigTemplate(cfg) {
296
+ if (cfg.theme === "custom" && cfg.customThemeName) {
297
+ const exportName = getThemeExportName(cfg.customThemeName);
298
+ return `\
299
+ import { defineDocs } from "@farming-labs/docs";
300
+ import { ${exportName} } from "${cfg.useAlias ? "@/themes/" + cfg.customThemeName.replace(/\.ts$/i, "") : "./themes/" + cfg.customThemeName.replace(/\.ts$/i, "")}";
301
+
302
+ export default defineDocs({
303
+ entry: "${cfg.entry}",
304
+ theme: ${exportName}({
305
+ ui: {
306
+ colors: { primary: "#6366f1" },
307
+ },
308
+ }),
309
+
310
+ metadata: {
311
+ titleTemplate: "%s – ${cfg.projectName}",
312
+ description: "Documentation for ${cfg.projectName}",
313
+ },
314
+ });
315
+ `;
316
+ }
253
317
  const t = getThemeInfo(cfg.theme);
254
318
  return `\
255
319
  import { defineDocs } from "@farming-labs/docs";
@@ -354,16 +418,21 @@ function injectRootProviderIntoLayout(content) {
354
418
  }
355
419
  return out === content ? null : out;
356
420
  }
357
- function globalCssTemplate(theme) {
421
+ function globalCssTemplate(theme, customThemeName, globalCssRelPath) {
422
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `\
423
+ @import "tailwindcss";
424
+ @import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";
425
+ `;
358
426
  return `\
359
427
  @import "tailwindcss";
360
428
  @import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";
361
429
  `;
362
430
  }
363
- function injectCssImport(existingContent, theme) {
364
- const importLine = `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
431
+ function injectCssImport(existingContent, theme, customThemeName, globalCssRelPath) {
432
+ const importLine = theme === "custom" && customThemeName && globalCssRelPath ? `@import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";` : `@import "@farming-labs/theme/${getThemeInfo(theme).nextCssImport}/css";`;
365
433
  if (existingContent.includes(importLine)) return null;
366
- if (existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
434
+ if (theme !== "custom" && existingContent.includes("@farming-labs/theme/") && existingContent.includes("/css")) return null;
435
+ if (theme === "custom" && existingContent.includes("themes/") && existingContent.includes(".css")) return null;
367
436
  const lines = existingContent.split("\n");
368
437
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
369
438
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
@@ -602,6 +671,34 @@ Deploy to Vercel, Netlify, or any Node.js hosting platform.
602
671
  `;
603
672
  }
604
673
  function svelteDocsConfigTemplate(cfg) {
674
+ if (cfg.theme === "custom" && cfg.customThemeName) {
675
+ const exportName = getThemeExportName(cfg.customThemeName);
676
+ return `\
677
+ import { defineDocs } from "@farming-labs/docs";
678
+ import { ${exportName} } from "${"../../themes/" + cfg.customThemeName.replace(/\.ts$/i, "")}";
679
+
680
+ export default defineDocs({
681
+ entry: "${cfg.entry}",
682
+ theme: ${exportName}({
683
+ ui: {
684
+ colors: { primary: "#6366f1" },
685
+ },
686
+ }),
687
+
688
+ nav: {
689
+ title: "${cfg.projectName}",
690
+ url: "/${cfg.entry}",
691
+ },
692
+
693
+ breadcrumb: { enabled: true },
694
+
695
+ metadata: {
696
+ titleTemplate: "%s – ${cfg.projectName}",
697
+ description: "Documentation for ${cfg.projectName}",
698
+ },
699
+ });
700
+ `;
701
+ }
605
702
  const t = getThemeInfo(cfg.theme);
606
703
  return `\
607
704
  import { defineDocs } from "@farming-labs/docs";
@@ -692,17 +789,23 @@ function svelteRootLayoutTemplate(globalCssRelPath) {
692
789
  {@render children()}
693
790
  `;
694
791
  }
695
- function svelteGlobalCssTemplate(theme) {
792
+ function svelteGlobalCssTemplate(theme, customThemeName, globalCssRelPath) {
793
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `\
794
+ @import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";
795
+ `;
696
796
  return `\
697
797
  @import "@farming-labs/svelte-theme/${theme}/css";
698
798
  `;
699
799
  }
700
- function svelteCssImportLine(theme) {
800
+ function svelteCssImportLine(theme, customThemeName, globalCssRelPath) {
801
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `@import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";`;
701
802
  return `@import "@farming-labs/svelte-theme/${theme}/css";`;
702
803
  }
703
- function injectSvelteCssImport(existingContent, theme) {
704
- const importLine = svelteCssImportLine(theme);
804
+ function injectSvelteCssImport(existingContent, theme, customThemeName, globalCssRelPath) {
805
+ const importLine = svelteCssImportLine(theme, customThemeName, globalCssRelPath);
705
806
  if (existingContent.includes(importLine)) return null;
807
+ if (theme !== "custom" && existingContent.includes("@farming-labs/svelte-theme/") && existingContent.includes("/css")) return null;
808
+ if (theme === "custom" && existingContent.includes("themes/") && existingContent.includes(".css")) return null;
706
809
  const lines = existingContent.split("\n");
707
810
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
708
811
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
@@ -876,6 +979,35 @@ Deploy to Vercel, Netlify, or any Node.js hosting platform.
876
979
  `;
877
980
  }
878
981
  function astroDocsConfigTemplate(cfg) {
982
+ if (cfg.theme === "custom" && cfg.customThemeName) {
983
+ const exportName = getThemeExportName(cfg.customThemeName);
984
+ return `\
985
+ import { defineDocs } from "@farming-labs/docs";
986
+ import { ${exportName} } from "${"../../themes/" + cfg.customThemeName.replace(/\.ts$/i, "")}";
987
+
988
+ export default defineDocs({
989
+ entry: "${cfg.entry}",
990
+ contentDir: "${cfg.entry}",
991
+ theme: ${exportName}({
992
+ ui: {
993
+ colors: { primary: "#6366f1" },
994
+ },
995
+ }),
996
+
997
+ nav: {
998
+ title: "${cfg.projectName}",
999
+ url: "/${cfg.entry}",
1000
+ },
1001
+
1002
+ breadcrumb: { enabled: true },
1003
+
1004
+ metadata: {
1005
+ titleTemplate: "%s – ${cfg.projectName}",
1006
+ description: "Documentation for ${cfg.projectName}",
1007
+ },
1008
+ });
1009
+ `;
1010
+ }
879
1011
  const t = getThemeInfo(cfg.theme);
880
1012
  return `\
881
1013
  import { defineDocs } from "@farming-labs/docs";
@@ -1025,17 +1157,23 @@ export const POST: APIRoute = async ({ request }) => {
1025
1157
  };
1026
1158
  `;
1027
1159
  }
1028
- function astroGlobalCssTemplate(theme) {
1160
+ function astroGlobalCssTemplate(theme, customThemeName, globalCssRelPath) {
1161
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `\
1162
+ @import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";
1163
+ `;
1029
1164
  return `\
1030
1165
  @import "@farming-labs/astro-theme/${theme}/css";
1031
1166
  `;
1032
1167
  }
1033
- function astroCssImportLine(theme) {
1168
+ function astroCssImportLine(theme, customThemeName, globalCssRelPath) {
1169
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `@import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";`;
1034
1170
  return `@import "@farming-labs/astro-theme/${theme}/css";`;
1035
1171
  }
1036
- function injectAstroCssImport(existingContent, theme) {
1037
- const importLine = astroCssImportLine(theme);
1172
+ function injectAstroCssImport(existingContent, theme, customThemeName, globalCssRelPath) {
1173
+ const importLine = astroCssImportLine(theme, customThemeName, globalCssRelPath);
1038
1174
  if (existingContent.includes(importLine)) return null;
1175
+ if (theme !== "custom" && existingContent.includes("@farming-labs/astro-theme/") && existingContent.includes("/css")) return null;
1176
+ if (theme === "custom" && existingContent.includes("themes/") && existingContent.includes(".css")) return null;
1039
1177
  const lines = existingContent.split("\n");
1040
1178
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1041
1179
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
@@ -1197,6 +1335,35 @@ Deploy to Vercel, Netlify, or any Node.js hosting platform.
1197
1335
  `;
1198
1336
  }
1199
1337
  function nuxtDocsConfigTemplate(cfg) {
1338
+ if (cfg.theme === "custom" && cfg.customThemeName) {
1339
+ const exportName = getThemeExportName(cfg.customThemeName);
1340
+ return `\
1341
+ import { defineDocs } from "@farming-labs/docs";
1342
+ import { ${exportName} } from "${cfg.useAlias ? "~/themes/" + cfg.customThemeName.replace(/\.ts$/i, "") : "./themes/" + cfg.customThemeName.replace(/\.ts$/i, "")}";
1343
+
1344
+ export default defineDocs({
1345
+ entry: "${cfg.entry}",
1346
+ contentDir: "${cfg.entry}",
1347
+ theme: ${exportName}({
1348
+ ui: {
1349
+ colors: { primary: "#6366f1" },
1350
+ },
1351
+ }),
1352
+
1353
+ nav: {
1354
+ title: "${cfg.projectName}",
1355
+ url: "/${cfg.entry}",
1356
+ },
1357
+
1358
+ breadcrumb: { enabled: true },
1359
+
1360
+ metadata: {
1361
+ titleTemplate: "%s – ${cfg.projectName}",
1362
+ description: "Documentation for ${cfg.projectName}",
1363
+ },
1364
+ });
1365
+ `;
1366
+ }
1200
1367
  const t = getThemeInfo(cfg.theme);
1201
1368
  return `\
1202
1369
  import { defineDocs } from "@farming-labs/docs";
@@ -1496,17 +1663,23 @@ pnpm build
1496
1663
  Deploy to Vercel, Netlify, or any Node.js hosting platform.
1497
1664
  `;
1498
1665
  }
1499
- function nuxtGlobalCssTemplate(theme) {
1666
+ function nuxtGlobalCssTemplate(theme, customThemeName, globalCssRelPath) {
1667
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `\
1668
+ @import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";
1669
+ `;
1500
1670
  return `\
1501
1671
  @import "@farming-labs/nuxt-theme/${theme}/css";
1502
1672
  `;
1503
1673
  }
1504
- function nuxtCssImportLine(theme) {
1674
+ function nuxtCssImportLine(theme, customThemeName, globalCssRelPath) {
1675
+ if (theme === "custom" && customThemeName && globalCssRelPath) return `@import "${getCustomThemeCssImportPath(globalCssRelPath, customThemeName.replace(/\.css$/i, ""))}";`;
1505
1676
  return `@import "@farming-labs/nuxt-theme/${theme}/css";`;
1506
1677
  }
1507
- function injectNuxtCssImport(existingContent, theme) {
1508
- const importLine = nuxtCssImportLine(theme);
1678
+ function injectNuxtCssImport(existingContent, theme, customThemeName, globalCssRelPath) {
1679
+ const importLine = nuxtCssImportLine(theme, customThemeName, globalCssRelPath);
1509
1680
  if (existingContent.includes(importLine)) return null;
1681
+ if (theme !== "custom" && existingContent.includes("@farming-labs/nuxt-theme/") && existingContent.includes("/css")) return null;
1682
+ if (theme === "custom" && existingContent.includes("themes/") && existingContent.includes(".css")) return null;
1510
1683
  const lines = existingContent.split("\n");
1511
1684
  const lastImportIdx = lines.reduce((acc, l, i) => l.trimStart().startsWith("@import") ? i : acc, -1);
1512
1685
  if (lastImportIdx >= 0) lines.splice(lastImportIdx + 1, 0, importLine);
@@ -1526,11 +1699,65 @@ const VALID_TEMPLATES = [
1526
1699
  async function init(options = {}) {
1527
1700
  const cwd = process.cwd();
1528
1701
  p.intro(pc.bgCyan(pc.black(" @farming-labs/docs ")));
1529
- if (options.template) {
1530
- const template = options.template.toLowerCase();
1531
- if (!VALID_TEMPLATES.includes(template)) {
1532
- p.log.error(`Invalid ${pc.cyan("--template")}. Use one of: ${VALID_TEMPLATES.map((t) => pc.cyan(t)).join(", ")}`);
1533
- process.exit(1);
1702
+ let projectType = "existing";
1703
+ if (!options.template) {
1704
+ const projectTypeAnswer = await p.select({
1705
+ message: "Are you adding docs to an existing project or starting fresh?",
1706
+ options: [{
1707
+ value: "existing",
1708
+ label: "Existing project",
1709
+ hint: "Add docs to the current app in this directory"
1710
+ }, {
1711
+ value: "fresh",
1712
+ label: "Fresh project",
1713
+ hint: "Bootstrap a new app from a template (Next, Nuxt, SvelteKit, Astro)"
1714
+ }]
1715
+ });
1716
+ if (p.isCancel(projectTypeAnswer)) {
1717
+ p.outro(pc.red("Init cancelled."));
1718
+ process.exit(0);
1719
+ }
1720
+ projectType = projectTypeAnswer;
1721
+ }
1722
+ if (projectType === "fresh" || options.template) {
1723
+ let template;
1724
+ if (options.template) {
1725
+ template = options.template.toLowerCase();
1726
+ if (!VALID_TEMPLATES.includes(template)) {
1727
+ p.log.error(`Invalid ${pc.cyan("--template")}. Use one of: ${VALID_TEMPLATES.map((t) => pc.cyan(t)).join(", ")}`);
1728
+ process.exit(1);
1729
+ }
1730
+ } else {
1731
+ const templateAnswer = await p.select({
1732
+ message: "Which framework would you like to use?",
1733
+ options: [
1734
+ {
1735
+ value: "next",
1736
+ label: "Next.js",
1737
+ hint: "React with App Router"
1738
+ },
1739
+ {
1740
+ value: "nuxt",
1741
+ label: "Nuxt",
1742
+ hint: "Vue 3 with file-based routing"
1743
+ },
1744
+ {
1745
+ value: "sveltekit",
1746
+ label: "SvelteKit",
1747
+ hint: "Svelte with file-based routing"
1748
+ },
1749
+ {
1750
+ value: "astro",
1751
+ label: "Astro",
1752
+ hint: "Content-focused with islands"
1753
+ }
1754
+ ]
1755
+ });
1756
+ if (p.isCancel(templateAnswer)) {
1757
+ p.outro(pc.red("Init cancelled."));
1758
+ process.exit(0);
1759
+ }
1760
+ template = templateAnswer;
1534
1761
  }
1535
1762
  let projectName = options.name?.trim();
1536
1763
  if (!projectName) {
@@ -1649,6 +1876,11 @@ async function init(options = {}) {
1649
1876
  value: "greentree",
1650
1877
  label: "GreenTree",
1651
1878
  hint: "Emerald green accent, Inter font, Mintlify-inspired"
1879
+ },
1880
+ {
1881
+ value: "custom",
1882
+ label: "Create your own theme",
1883
+ hint: "Scaffold a new theme file + CSS in themes/ (name asked next)"
1652
1884
  }
1653
1885
  ]
1654
1886
  });
@@ -1656,6 +1888,26 @@ async function init(options = {}) {
1656
1888
  p.outro(pc.red("Init cancelled."));
1657
1889
  process.exit(0);
1658
1890
  }
1891
+ let customThemeName;
1892
+ if (theme === "custom") {
1893
+ const nameAnswer = await p.text({
1894
+ message: "Theme name? (we'll create themes/<name>.ts and themes/<name>.css)",
1895
+ placeholder: "my-theme",
1896
+ defaultValue: "my-theme",
1897
+ validate: (value) => {
1898
+ const v = (value ?? "").trim().replace(/\.(ts|css)$/i, "");
1899
+ if (!v) return "Theme name is required";
1900
+ if (v.includes("/") || v.includes("\\")) return "Theme name cannot contain path separators";
1901
+ if (v.includes(" ")) return "Theme name cannot contain spaces";
1902
+ if (!/^[a-z0-9_-]+$/i.test(v)) return "Use only letters, numbers, hyphens, and underscores";
1903
+ }
1904
+ });
1905
+ if (p.isCancel(nameAnswer)) {
1906
+ p.outro(pc.red("Init cancelled."));
1907
+ process.exit(0);
1908
+ }
1909
+ customThemeName = nameAnswer.trim().replace(/\.(ts|css)$/i, "");
1910
+ }
1659
1911
  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)`;
1660
1912
  const useAlias = await p.confirm({
1661
1913
  message: `Use path aliases for imports? ${pc.dim(aliasHint)}`,
@@ -1696,21 +1948,22 @@ async function init(options = {}) {
1696
1948
  }
1697
1949
  astroAdapter = adapter;
1698
1950
  }
1951
+ const defaultEntry = "docs";
1699
1952
  const entry = await p.text({
1700
1953
  message: "Where should your docs live?",
1701
- placeholder: "docs",
1702
- defaultValue: "docs",
1954
+ placeholder: defaultEntry,
1955
+ defaultValue: defaultEntry,
1703
1956
  validate: (value) => {
1704
- if (!value) return "Entry path is required";
1705
- if (value.startsWith("/")) return "Use a relative path (no leading /)";
1706
- if (value.includes(" ")) return "Path cannot contain spaces";
1957
+ const v = (value ?? "").trim();
1958
+ if (v.startsWith("/")) return "Use a relative path (no leading /)";
1959
+ if (v.includes(" ")) return "Path cannot contain spaces";
1707
1960
  }
1708
1961
  });
1709
1962
  if (p.isCancel(entry)) {
1710
1963
  p.outro(pc.red("Init cancelled."));
1711
1964
  process.exit(0);
1712
1965
  }
1713
- const entryPath = entry;
1966
+ const entryPath = entry.trim() || defaultEntry;
1714
1967
  const detectedCssFiles = detectGlobalCssFiles(cwd);
1715
1968
  let globalCssRelPath;
1716
1969
  const defaultCssPath = framework === "sveltekit" ? "src/app.css" : framework === "astro" ? "src/styles/global.css" : framework === "nuxt" ? "assets/css/main.css" : "app/globals.css";
@@ -1731,27 +1984,29 @@ async function init(options = {}) {
1731
1984
  }
1732
1985
  globalCssRelPath = picked;
1733
1986
  } else {
1734
- const cssPath = await p.text({
1987
+ const cssPathAnswer = await p.text({
1735
1988
  message: "Where is your global CSS file?",
1736
1989
  placeholder: defaultCssPath,
1737
1990
  defaultValue: defaultCssPath,
1738
1991
  validate: (value) => {
1739
- if (!value) return "CSS file path is required";
1740
- if (!value.endsWith(".css")) return "Path must end with .css";
1992
+ const v = (value ?? "").trim();
1993
+ if (v && !v.endsWith(".css")) return "Path must end with .css";
1741
1994
  }
1742
1995
  });
1743
- if (p.isCancel(cssPath)) {
1996
+ if (p.isCancel(cssPathAnswer)) {
1744
1997
  p.outro(pc.red("Init cancelled."));
1745
1998
  process.exit(0);
1746
1999
  }
1747
- globalCssRelPath = cssPath;
2000
+ globalCssRelPath = cssPathAnswer.trim() || defaultCssPath;
1748
2001
  }
1749
2002
  const pkgJsonContent = readFileSafe(path.join(cwd, "package.json"));
1750
2003
  const pkgJson = pkgJsonContent ? JSON.parse(pkgJsonContent) : { name: "my-project" };
2004
+ const projectName = pkgJson.name || "My Project";
1751
2005
  const cfg = {
1752
2006
  entry: entryPath,
1753
2007
  theme,
1754
- projectName: pkgJson.name || "My Project",
2008
+ customThemeName,
2009
+ projectName,
1755
2010
  framework,
1756
2011
  useAlias,
1757
2012
  astroAdapter
@@ -1864,6 +2119,11 @@ async function init(options = {}) {
1864
2119
  }
1865
2120
  }
1866
2121
  function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
2122
+ if (cfg.theme === "custom" && cfg.customThemeName) {
2123
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
2124
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
2125
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
2126
+ }
1867
2127
  write("docs.config.ts", docsConfigTemplate(cfg));
1868
2128
  const existingNextConfig = readFileSafe(path.join(cwd, "next.config.ts")) ?? readFileSafe(path.join(cwd, "next.config.mjs")) ?? readFileSafe(path.join(cwd, "next.config.js"));
1869
2129
  if (existingNextConfig) {
@@ -1887,12 +2147,12 @@ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
1887
2147
  const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1888
2148
  const existingGlobalCss = readFileSafe(globalCssAbsPath);
1889
2149
  if (existingGlobalCss) {
1890
- const injected = injectCssImport(existingGlobalCss, cfg.theme);
2150
+ const injected = injectCssImport(existingGlobalCss, cfg.theme, cfg.customThemeName, globalCssRelPath);
1891
2151
  if (injected) {
1892
2152
  writeFileSafe(globalCssAbsPath, injected, true);
1893
2153
  written.push(globalCssRelPath + " (updated)");
1894
2154
  } else skipped.push(globalCssRelPath + " (already configured)");
1895
- } else write(globalCssRelPath, globalCssTemplate(cfg.theme));
2155
+ } else write(globalCssRelPath, globalCssTemplate(cfg.theme, cfg.customThemeName, globalCssRelPath));
1896
2156
  write(`app/${cfg.entry}/layout.tsx`, docsLayoutTemplate(cfg));
1897
2157
  write("postcss.config.mjs", postcssConfigTemplate());
1898
2158
  if (!fileExists(path.join(cwd, "tsconfig.json"))) write("tsconfig.json", tsconfigTemplate(cfg.useAlias));
@@ -1901,6 +2161,11 @@ function scaffoldNextJs(cwd, cfg, globalCssRelPath, write, skipped, written) {
1901
2161
  write(`app/${cfg.entry}/quickstart/page.mdx`, quickstartPageTemplate(cfg));
1902
2162
  }
1903
2163
  function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written) {
2164
+ if (cfg.theme === "custom" && cfg.customThemeName) {
2165
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
2166
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
2167
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
2168
+ }
1904
2169
  write("src/lib/docs.config.ts", svelteDocsConfigTemplate(cfg));
1905
2170
  write("src/lib/docs.server.ts", svelteDocsServerTemplate(cfg));
1906
2171
  write(`src/routes/${cfg.entry}/+layout.svelte`, svelteDocsLayoutTemplate(cfg));
@@ -1920,17 +2185,22 @@ function scaffoldSvelteKit(cwd, cfg, globalCssRelPath, write, skipped, written)
1920
2185
  default: "fumadocs"
1921
2186
  }[cfg.theme] || "fumadocs";
1922
2187
  if (existingGlobalCss) {
1923
- const injected = injectSvelteCssImport(existingGlobalCss, cssTheme);
2188
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectSvelteCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectSvelteCssImport(existingGlobalCss, cssTheme);
1924
2189
  if (injected) {
1925
2190
  writeFileSafe(globalCssAbsPath, injected, true);
1926
2191
  written.push(globalCssRelPath + " (updated)");
1927
2192
  } else skipped.push(globalCssRelPath + " (already configured)");
1928
- } else write(globalCssRelPath, svelteGlobalCssTemplate(cssTheme));
2193
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? svelteGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : svelteGlobalCssTemplate(cssTheme));
1929
2194
  write(`${cfg.entry}/page.md`, svelteWelcomePageTemplate(cfg));
1930
2195
  write(`${cfg.entry}/installation/page.md`, svelteInstallationPageTemplate(cfg));
1931
2196
  write(`${cfg.entry}/quickstart/page.md`, svelteQuickstartPageTemplate(cfg));
1932
2197
  }
1933
2198
  function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
2199
+ if (cfg.theme === "custom" && cfg.customThemeName) {
2200
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
2201
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
2202
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
2203
+ }
1934
2204
  write("src/lib/docs.config.ts", astroDocsConfigTemplate(cfg));
1935
2205
  write("src/lib/docs.server.ts", astroDocsServerTemplate(cfg));
1936
2206
  if (!fileExists(path.join(cwd, "astro.config.mjs")) && !fileExists(path.join(cwd, "astro.config.ts"))) write("astro.config.mjs", astroConfigTemplate(cfg.astroAdapter ?? "vercel"));
@@ -1950,17 +2220,22 @@ function scaffoldAstro(cwd, cfg, globalCssRelPath, write, skipped, written) {
1950
2220
  default: "fumadocs"
1951
2221
  }[cfg.theme] || "fumadocs";
1952
2222
  if (existingGlobalCss) {
1953
- const injected = injectAstroCssImport(existingGlobalCss, cssTheme);
2223
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectAstroCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectAstroCssImport(existingGlobalCss, cssTheme);
1954
2224
  if (injected) {
1955
2225
  writeFileSafe(globalCssAbsPath, injected, true);
1956
2226
  written.push(globalCssRelPath + " (updated)");
1957
2227
  } else skipped.push(globalCssRelPath + " (already configured)");
1958
- } else write(globalCssRelPath, astroGlobalCssTemplate(cssTheme));
2228
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? astroGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : astroGlobalCssTemplate(cssTheme));
1959
2229
  write(`${cfg.entry}/page.md`, astroWelcomePageTemplate(cfg));
1960
2230
  write(`${cfg.entry}/installation/page.md`, astroInstallationPageTemplate(cfg));
1961
2231
  write(`${cfg.entry}/quickstart/page.md`, astroQuickstartPageTemplate(cfg));
1962
2232
  }
1963
2233
  function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
2234
+ if (cfg.theme === "custom" && cfg.customThemeName) {
2235
+ const baseName = cfg.customThemeName.replace(/\.(ts|css)$/i, "");
2236
+ write(`themes/${baseName}.ts`, customThemeTsTemplate(baseName));
2237
+ write(`themes/${baseName}.css`, customThemeCssTemplate(baseName));
2238
+ }
1964
2239
  write("docs.config.ts", nuxtDocsConfigTemplate(cfg));
1965
2240
  write("server/utils/docs-server.ts", nuxtDocsServerTemplate(cfg));
1966
2241
  write("server/api/docs.get.ts", nuxtServerApiDocsGetTemplate());
@@ -1982,12 +2257,12 @@ function scaffoldNuxt(cwd, cfg, globalCssRelPath, write, skipped, written) {
1982
2257
  const globalCssAbsPath = path.join(cwd, globalCssRelPath);
1983
2258
  const existingGlobalCss = readFileSafe(globalCssAbsPath);
1984
2259
  if (existingGlobalCss) {
1985
- const injected = injectNuxtCssImport(existingGlobalCss, cssTheme);
2260
+ const injected = cfg.theme === "custom" && cfg.customThemeName ? injectNuxtCssImport(existingGlobalCss, "custom", cfg.customThemeName, globalCssRelPath) : injectNuxtCssImport(existingGlobalCss, cssTheme);
1986
2261
  if (injected) {
1987
2262
  writeFileSafe(globalCssAbsPath, injected, true);
1988
2263
  written.push(globalCssRelPath + " (updated)");
1989
2264
  } else skipped.push(globalCssRelPath + " (already configured)");
1990
- } else write(globalCssRelPath, nuxtGlobalCssTemplate(cssTheme));
2265
+ } else write(globalCssRelPath, cfg.theme === "custom" && cfg.customThemeName ? nuxtGlobalCssTemplate("custom", cfg.customThemeName, globalCssRelPath) : nuxtGlobalCssTemplate(cssTheme));
1991
2266
  write(`${cfg.entry}/page.md`, nuxtWelcomePageTemplate(cfg));
1992
2267
  write(`${cfg.entry}/installation/page.md`, nuxtInstallationPageTemplate(cfg));
1993
2268
  write(`${cfg.entry}/quickstart/page.md`, nuxtQuickstartPageTemplate(cfg));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",