@aravindc26/velu 0.9.0 → 0.10.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aravindc26/velu",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "A modern documentation site generator powered by Markdown and JSON configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,9 +12,9 @@
12
12
  },
13
13
  "theme": {
14
14
  "type": "string",
15
- "description": "Named theme preset that controls the overall look and feel.",
16
- "enum": ["violet", "maple", "palm", "willow", "linden", "almond", "aspen"],
17
- "default": "violet"
15
+ "description": "Named theme preset that controls the overall look and feel. Supports Fumadocs presets and legacy Velu aliases.",
16
+ "enum": ["neutral", "black", "vitepress", "dusk", "catppuccin", "ocean", "emerald", "ruby", "purple", "solar", "aspen", "violet", "maple", "palm", "willow", "linden", "almond"],
17
+ "default": "neutral"
18
18
  },
19
19
  "colors": {
20
20
  "type": "object",
package/src/build.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync, copyFileSync, cpSync, existsSync, rmSync } from "node:fs";
2
2
  import { join, dirname } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { generateThemeCss, type VeluColors, type VeluStyling } from "./themes.js";
4
+ import { generateThemeCss, resolveThemeName, type VeluColors, type VeluStyling } from "./themes.js";
5
5
 
6
6
  // ── Engine directory (shipped with the CLI package) ──────────────────────────
7
7
  const __filename = fileURLToPath(import.meta.url);
@@ -219,12 +219,12 @@ function build(docsDir: string, outDir: string) {
219
219
  styling: config.styling,
220
220
  });
221
221
  writeFileSync(join(outDir, "app", "velu-theme.css"), themeCss, "utf-8");
222
- console.log(`🎨 Generated theme: ${config.theme || "mint"}`);
222
+ console.log(`🎨 Generated theme: ${resolveThemeName(config.theme)}`);
223
223
 
224
224
  // ── 6. Generate index.mdx (dynamic — references first page) ──────────────
225
225
  writeFileSync(
226
226
  join(outDir, "content", "docs", "index.mdx"),
227
- `---\ntitle: "Overview"\ndescription: Documentation powered by Velu + Fumadocs\n---\n\nimport { Card, Cards } from "fumadocs-ui/components/card"\nimport { Callout } from "fumadocs-ui/components/callout"\n\n<Callout type="info">\n This site is powered by Velu + Fumadocs.\n</Callout>\n\n## Start here\n\n<Cards>\n <Card\n title="Read the docs"\n href="/${firstPage}/"\n description="Begin with the first page in your configured navigation."\n />\n</Cards>\n`,
227
+ `---\ntitle: "Overview"\ndescription: Documentation powered by Velu\n---\n\nimport { Card, Cards } from "fumadocs-ui/components/card"\nimport { Callout } from "fumadocs-ui/components/callout"\n\n<Callout type="info">\n Welcome to your documentation site.\n</Callout>\n\n## Start here\n\n<Cards>\n <Card\n title="Read the docs"\n href="/${firstPage}/"\n description="Begin with the first page in your configured navigation."\n />\n</Cards>\n`,
228
228
  "utf-8"
229
229
  );
230
230
 
package/src/cli.ts CHANGED
@@ -49,7 +49,7 @@ function init(targetDir: string) {
49
49
  // velu.json
50
50
  const config = {
51
51
  $schema: "https://raw.githubusercontent.com/aravindc26/velu/main/schema/velu.schema.json",
52
- theme: "violet" as const,
52
+ theme: "neutral" as const,
53
53
  navigation: {
54
54
  tabs: [
55
55
  {
@@ -128,7 +128,7 @@ function writeMetaFiles(metaFiles) {
128
128
  function writeIndexPage(firstPage) {
129
129
  writeFileSync(
130
130
  join(contentDir, 'index.mdx'),
131
- `---\ntitle: "Overview"\ndescription: Documentation powered by Velu + Fumadocs\n---\n\nimport { Card, Cards } from "fumadocs-ui/components/card"\nimport { Callout } from "fumadocs-ui/components/callout"\n\n<Callout type="info">\n This site is powered by Velu + Fumadocs.\n</Callout>\n\n## Start here\n\n<Cards>\n <Card\n title="Read the docs"\n href="/${firstPage}/"\n description="Begin with the first page in your configured navigation."\n />\n</Cards>\n`,
131
+ `---\ntitle: "Overview"\ndescription: Documentation powered by Velu\n---\n\nimport { Card, Cards } from "fumadocs-ui/components/card"\nimport { Callout } from "fumadocs-ui/components/callout"\n\n<Callout type="info">\n Welcome to your documentation site.\n</Callout>\n\n## Start here\n\n<Cards>\n <Card\n title="Read the docs"\n href="/${firstPage}/"\n description="Begin with the first page in your configured navigation."\n />\n</Cards>\n`,
132
132
  'utf-8'
133
133
  );
134
134
  }
@@ -244,7 +244,7 @@ const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 4321;
244
244
 
245
245
  if (command === 'dev') {
246
246
  console.log('');
247
- console.log(' \x1b[36mvelu\x1b[0m fumadocs dev');
247
+ console.log(' \x1b[36mvelu\x1b[0m dev server');
248
248
  console.log('');
249
249
  console.log(' watching for file changes...');
250
250
  startWatcher();
@@ -43,6 +43,9 @@ export default async function Page({ params }: PageProps) {
43
43
  />
44
44
  </DocsBody>
45
45
  </div>
46
+ <footer className="velu-footer">
47
+ Powered by <a href="https://getvelu.com" target="_blank" rel="noopener noreferrer">Velu</a>
48
+ </footer>
46
49
  </DocsPage>
47
50
  );
48
51
  }
@@ -1,5 +1,4 @@
1
1
  @import 'tailwindcss';
2
- @import 'fumadocs-ui/css/neutral.css';
3
2
  @import 'fumadocs-ui/css/preset.css';
4
3
  @import './velu-theme.css';
5
4
 
@@ -19,3 +18,22 @@ body {
19
18
  --fd-toc-width: 268px;
20
19
  }
21
20
  }
21
+
22
+ .velu-footer {
23
+ margin-top: 3rem;
24
+ padding-top: 1.5rem;
25
+ border-top: 1px solid var(--color-fd-border);
26
+ text-align: right;
27
+ font-size: 1.2rem;
28
+ color: var(--color-fd-muted-foreground);
29
+ }
30
+
31
+ .velu-footer a {
32
+ color: var(--color-fd-primary);
33
+ font-weight: 600;
34
+ text-decoration: none;
35
+ }
36
+
37
+ .velu-footer a:hover {
38
+ text-decoration: underline;
39
+ }
@@ -1,3 +1,4 @@
1
+ import { resolve } from 'node:path';
1
2
  import { createMDX } from 'fumadocs-mdx/next';
2
3
 
3
4
  const withMDX = createMDX({
@@ -10,6 +11,9 @@ const config = {
10
11
  output: 'export',
11
12
  distDir: 'dist',
12
13
  devIndicators: false,
14
+ turbopack: {
15
+ root: resolve('..'),
16
+ },
13
17
  images: {
14
18
  unoptimized: true,
15
19
  },
package/src/themes.ts CHANGED
@@ -1,27 +1,5 @@
1
1
  // ── Types ────────────────────────────────────────────────────────────────────
2
2
 
3
- interface ColorSet {
4
- accentLow: string;
5
- accent: string;
6
- accentHigh: string;
7
- white: string;
8
- gray1: string;
9
- gray2: string;
10
- gray3: string;
11
- gray4: string;
12
- gray5: string;
13
- gray6: string;
14
- gray7: string;
15
- black: string;
16
- }
17
-
18
- interface ThemePreset {
19
- dark: ColorSet;
20
- light: ColorSet;
21
- font?: string;
22
- fontMono?: string;
23
- }
24
-
25
3
  interface VeluColors {
26
4
  primary?: string;
27
5
  light?: string;
@@ -41,6 +19,42 @@ interface ThemeConfig {
41
19
  styling?: VeluStyling;
42
20
  }
43
21
 
22
+ const FUMADOCS_THEMES = [
23
+ "neutral",
24
+ "black",
25
+ "vitepress",
26
+ "dusk",
27
+ "catppuccin",
28
+ "ocean",
29
+ "emerald",
30
+ "ruby",
31
+ "purple",
32
+ "solar",
33
+ "aspen",
34
+ ] as const;
35
+
36
+ type FumadocsTheme = (typeof FUMADOCS_THEMES)[number];
37
+
38
+ const LEGACY_THEME_ALIASES: Record<string, FumadocsTheme> = {
39
+ violet: "purple",
40
+ maple: "catppuccin",
41
+ palm: "ocean",
42
+ willow: "neutral",
43
+ linden: "emerald",
44
+ almond: "solar",
45
+ aspen: "aspen",
46
+ };
47
+
48
+ function resolveThemeName(theme?: string): FumadocsTheme {
49
+ if (!theme) return "neutral";
50
+
51
+ if (FUMADOCS_THEMES.includes(theme as FumadocsTheme)) {
52
+ return theme as FumadocsTheme;
53
+ }
54
+
55
+ return LEGACY_THEME_ALIASES[theme] || "neutral";
56
+ }
57
+
44
58
  // ── Color utilities ──────────────────────────────────────────────────────────
45
59
 
46
60
  function hexToRgb(hex: string): [number, number, number] {
@@ -72,7 +86,10 @@ function mixColors(hex1: string, hex2: string, weight: number): string {
72
86
  );
73
87
  }
74
88
 
75
- function deriveAccentPalette(primary: string): { dark: Pick<ColorSet, "accentLow" | "accent" | "accentHigh">; light: Pick<ColorSet, "accentLow" | "accent" | "accentHigh"> } {
89
+ function deriveAccentPalette(primary: string): {
90
+ dark: { accentLow: string; accent: string; accentHigh: string };
91
+ light: { accentLow: string; accent: string; accentHigh: string };
92
+ } {
76
93
  return {
77
94
  dark: {
78
95
  accentLow: mixColors(primary, "#000000", 0.3),
@@ -87,193 +104,6 @@ function deriveAccentPalette(primary: string): { dark: Pick<ColorSet, "accentLow
87
104
  };
88
105
  }
89
106
 
90
- // ── Gray palettes ────────────────────────────────────────────────────────────
91
-
92
- const GRAY_SLATE = {
93
- dark: {
94
- white: "#ffffff",
95
- gray1: "#eceef2",
96
- gray2: "#c0c2c7",
97
- gray3: "#888b96",
98
- gray4: "#545861",
99
- gray5: "#353841",
100
- gray6: "#24272f",
101
- gray7: "#17181c",
102
- black: "#13141a",
103
- },
104
- light: {
105
- white: "#13141a",
106
- gray1: "#17181c",
107
- gray2: "#24272f",
108
- gray3: "#545861",
109
- gray4: "#888b96",
110
- gray5: "#c0c2c7",
111
- gray6: "#eceef2",
112
- gray7: "#f5f6f8",
113
- black: "#ffffff",
114
- },
115
- };
116
-
117
- const GRAY_ZINC = {
118
- dark: {
119
- white: "#ffffff",
120
- gray1: "#ececef",
121
- gray2: "#bfc0c4",
122
- gray3: "#878890",
123
- gray4: "#53545c",
124
- gray5: "#34353b",
125
- gray6: "#23242a",
126
- gray7: "#17171a",
127
- black: "#121214",
128
- },
129
- light: {
130
- white: "#121214",
131
- gray1: "#17171a",
132
- gray2: "#23242a",
133
- gray3: "#53545c",
134
- gray4: "#878890",
135
- gray5: "#bfc0c4",
136
- gray6: "#ececef",
137
- gray7: "#f5f5f7",
138
- black: "#ffffff",
139
- },
140
- };
141
-
142
- const GRAY_STONE = {
143
- dark: {
144
- white: "#ffffff",
145
- gray1: "#eeeceb",
146
- gray2: "#c3bfbb",
147
- gray3: "#8c8680",
148
- gray4: "#585550",
149
- gray5: "#383532",
150
- gray6: "#272421",
151
- gray7: "#1a1816",
152
- black: "#141210",
153
- },
154
- light: {
155
- white: "#141210",
156
- gray1: "#1a1816",
157
- gray2: "#272421",
158
- gray3: "#585550",
159
- gray4: "#8c8680",
160
- gray5: "#c3bfbb",
161
- gray6: "#eeeceb",
162
- gray7: "#f7f6f5",
163
- black: "#ffffff",
164
- },
165
- };
166
-
167
- // ── Theme presets ────────────────────────────────────────────────────────────
168
-
169
- const THEMES: Record<string, ThemePreset> = {
170
- violet: {
171
- dark: {
172
- accentLow: "#1e1b4b",
173
- accent: "#818cf8",
174
- accentHigh: "#e0e7ff",
175
- ...GRAY_SLATE.dark,
176
- },
177
- light: {
178
- accentLow: "#e0e7ff",
179
- accent: "#4f46e5",
180
- accentHigh: "#1e1b4b",
181
- ...GRAY_SLATE.light,
182
- },
183
- },
184
-
185
- maple: {
186
- dark: {
187
- accentLow: "#2e1065",
188
- accent: "#a78bfa",
189
- accentHigh: "#ede9fe",
190
- ...GRAY_ZINC.dark,
191
- },
192
- light: {
193
- accentLow: "#ede9fe",
194
- accent: "#7c3aed",
195
- accentHigh: "#2e1065",
196
- ...GRAY_ZINC.light,
197
- },
198
- },
199
-
200
- palm: {
201
- dark: {
202
- accentLow: "#0c2d44",
203
- accent: "#38bdf8",
204
- accentHigh: "#e0f2fe",
205
- ...GRAY_SLATE.dark,
206
- },
207
- light: {
208
- accentLow: "#e0f2fe",
209
- accent: "#0369a1",
210
- accentHigh: "#0c2d44",
211
- ...GRAY_SLATE.light,
212
- },
213
- },
214
-
215
- willow: {
216
- dark: {
217
- accentLow: "#292524",
218
- accent: "#a8a29e",
219
- accentHigh: "#fafaf9",
220
- ...GRAY_STONE.dark,
221
- },
222
- light: {
223
- accentLow: "#f5f5f4",
224
- accent: "#57534e",
225
- accentHigh: "#1c1917",
226
- ...GRAY_STONE.light,
227
- },
228
- },
229
-
230
- linden: {
231
- dark: {
232
- accentLow: "#052e16",
233
- accent: "#4ade80",
234
- accentHigh: "#dcfce7",
235
- ...GRAY_ZINC.dark,
236
- },
237
- light: {
238
- accentLow: "#dcfce7",
239
- accent: "#16a34a",
240
- accentHigh: "#052e16",
241
- ...GRAY_ZINC.light,
242
- },
243
- font: "'JetBrains Mono', 'Fira Code', 'Courier New', monospace",
244
- },
245
-
246
- almond: {
247
- dark: {
248
- accentLow: "#451a03",
249
- accent: "#fbbf24",
250
- accentHigh: "#fef3c7",
251
- ...GRAY_STONE.dark,
252
- },
253
- light: {
254
- accentLow: "#fef3c7",
255
- accent: "#b45309",
256
- accentHigh: "#451a03",
257
- ...GRAY_STONE.light,
258
- },
259
- },
260
-
261
- aspen: {
262
- dark: {
263
- accentLow: "#1e1b4b",
264
- accent: "#818cf8",
265
- accentHigh: "#e0e7ff",
266
- ...GRAY_SLATE.dark,
267
- },
268
- light: {
269
- accentLow: "#e0e7ff",
270
- accent: "#4f46e5",
271
- accentHigh: "#1e1b4b",
272
- ...GRAY_SLATE.light,
273
- },
274
- },
275
- };
276
-
277
107
  // ── CSS generator ────────────────────────────────────────────────────────────
278
108
 
279
109
  function textColorFor(hex: string): string {
@@ -283,67 +113,44 @@ function textColorFor(hex: string): string {
283
113
  }
284
114
 
285
115
  function generateThemeCss(config: ThemeConfig): string {
286
- const themeName = config.theme || "violet";
287
- const preset = THEMES[themeName] || THEMES["violet"];
116
+ const themeName = resolveThemeName(config.theme);
288
117
 
289
- const darkColors: ColorSet = { ...preset.dark };
290
- const lightColors: ColorSet = { ...preset.light };
118
+ const lines: string[] = [];
119
+ lines.push(`/* Velu Theme: ${themeName} */`);
120
+ lines.push(`@import 'fumadocs-ui/css/${themeName}.css';`);
121
+ lines.push("");
291
122
 
292
- // Apply color overrides
123
+ // Apply accent overrides on top of selected Fumadocs theme.
293
124
  if (config.colors) {
294
125
  const { primary, light, dark } = config.colors;
295
-
296
126
  const lightAccent = light || primary;
297
127
  const darkAccent = dark || primary;
298
128
 
299
129
  if (lightAccent) {
300
130
  const palette = deriveAccentPalette(lightAccent);
301
- lightColors.accentLow = palette.light.accentLow;
302
- lightColors.accent = palette.light.accent;
303
- lightColors.accentHigh = palette.light.accentHigh;
131
+ lines.push(":root {");
132
+ lines.push(` --color-fd-primary: ${palette.light.accent};`);
133
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(palette.light.accent)};`);
134
+ lines.push(` --color-fd-accent: ${palette.light.accentLow};`);
135
+ lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.light.accentLow)};`);
136
+ lines.push(` --color-fd-ring: ${palette.light.accent};`);
137
+ lines.push("}");
138
+ lines.push("");
304
139
  }
305
140
 
306
141
  if (darkAccent) {
307
142
  const palette = deriveAccentPalette(darkAccent);
308
- darkColors.accentLow = palette.dark.accentLow;
309
- darkColors.accent = palette.dark.accent;
310
- darkColors.accentHigh = palette.dark.accentHigh;
143
+ lines.push(".dark {");
144
+ lines.push(` --color-fd-primary: ${palette.dark.accent};`);
145
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(palette.dark.accent)};`);
146
+ lines.push(` --color-fd-accent: ${palette.dark.accentLow};`);
147
+ lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.dark.accentLow)};`);
148
+ lines.push(` --color-fd-ring: ${palette.dark.accent};`);
149
+ lines.push("}");
150
+ lines.push("");
311
151
  }
312
152
  }
313
153
 
314
- const lines: string[] = [];
315
- lines.push(`/* Velu Theme: ${themeName} */`);
316
- lines.push("");
317
-
318
- // Light mode (default)
319
- lines.push(":root {");
320
- lines.push(` --color-fd-primary: ${lightColors.accent};`);
321
- lines.push(` --color-fd-primary-foreground: ${textColorFor(lightColors.accent)};`);
322
- lines.push(` --color-fd-accent: ${lightColors.accentLow};`);
323
- lines.push(` --color-fd-accent-foreground: ${textColorFor(lightColors.accentLow)};`);
324
- lines.push(` --color-fd-ring: ${lightColors.accent};`);
325
- lines.push("}");
326
-
327
- // Dark mode
328
- lines.push("");
329
- lines.push(".dark {");
330
- lines.push(` --color-fd-primary: ${darkColors.accent};`);
331
- lines.push(` --color-fd-primary-foreground: ${textColorFor(darkColors.accent)};`);
332
- lines.push(` --color-fd-accent: ${darkColors.accentLow};`);
333
- lines.push(` --color-fd-accent-foreground: ${textColorFor(darkColors.accentLow)};`);
334
- lines.push(` --color-fd-ring: ${darkColors.accent};`);
335
- lines.push("}");
336
-
337
- if (preset.font || preset.fontMono) {
338
- lines.push("");
339
- }
340
- if (preset.font) {
341
- lines.push(`body { font-family: ${preset.font}; }`);
342
- }
343
- if (preset.fontMono) {
344
- lines.push(`code, pre, kbd, samp { font-family: ${preset.fontMono}; }`);
345
- }
346
-
347
154
  if (config.appearance === "light") {
348
155
  lines.push("");
349
156
  lines.push("html { color-scheme: light; }");
@@ -357,7 +164,9 @@ function generateThemeCss(config: ThemeConfig): string {
357
164
  }
358
165
 
359
166
  function getThemeNames(): string[] {
360
- return Object.keys(THEMES);
167
+ return [...FUMADOCS_THEMES];
361
168
  }
362
169
 
363
- export { generateThemeCss, getThemeNames, THEMES, ThemeConfig, VeluColors, VeluStyling };
170
+ const THEMES = [...FUMADOCS_THEMES];
171
+
172
+ export { generateThemeCss, getThemeNames, resolveThemeName, THEMES, ThemeConfig, VeluColors, VeluStyling };