@aravindc26/velu 0.10.0 → 0.11.1

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 (65) hide show
  1. package/package.json +15 -6
  2. package/schema/velu.schema.json +1864 -30
  3. package/src/build.ts +1161 -180
  4. package/src/cli.ts +121 -16
  5. package/src/engine/_server.mjs +1708 -192
  6. package/src/engine/app/(docs)/[...slug]/layout.tsx +377 -0
  7. package/src/engine/app/(docs)/[...slug]/page.tsx +917 -0
  8. package/src/engine/app/(docs)/layout.tsx +1 -13
  9. package/src/engine/app/api/proxy/route.ts +23 -0
  10. package/src/engine/app/copy-page.css +59 -1
  11. package/src/engine/app/global.css +3487 -6
  12. package/src/engine/app/layout.tsx +59 -8
  13. package/src/engine/app/llms-file/route.ts +87 -0
  14. package/src/engine/app/llms-full-file/route.ts +62 -0
  15. package/src/engine/app/md-file/[...slug]/route.ts +409 -0
  16. package/src/engine/app/page.tsx +45 -0
  17. package/src/engine/app/robots.txt/route.ts +61 -0
  18. package/src/engine/app/rss-file/[...slug]/route.ts +176 -0
  19. package/src/engine/app/search.css +20 -0
  20. package/src/engine/app/sitemap.xml/route.ts +80 -0
  21. package/src/engine/components/assistant.tsx +16 -5
  22. package/src/engine/components/changelog-filters.tsx +114 -0
  23. package/src/engine/components/code-group.tsx +383 -0
  24. package/src/engine/components/color.tsx +118 -0
  25. package/src/engine/components/expandable.tsx +77 -0
  26. package/src/engine/components/icon.tsx +136 -0
  27. package/src/engine/components/image-zoom-fallback.tsx +147 -0
  28. package/src/engine/components/image.tsx +111 -0
  29. package/src/engine/components/lang-switcher.tsx +95 -0
  30. package/src/engine/components/manual-api-playground.tsx +154 -0
  31. package/src/engine/components/mermaid.tsx +142 -0
  32. package/src/engine/components/openapi-toc-sync.tsx +59 -0
  33. package/src/engine/components/openapi.tsx +1679 -0
  34. package/src/engine/components/page-feedback.tsx +153 -0
  35. package/src/engine/components/product-switcher.tsx +102 -0
  36. package/src/engine/components/prompt.tsx +90 -0
  37. package/src/engine/components/providers.tsx +21 -0
  38. package/src/engine/components/search.tsx +70 -3
  39. package/src/engine/components/sidebar-links.tsx +49 -0
  40. package/src/engine/components/synced-tabs.tsx +57 -0
  41. package/src/engine/components/theme-toggle.tsx +39 -0
  42. package/src/engine/components/toc-examples.tsx +110 -0
  43. package/src/engine/components/version-switcher.tsx +89 -0
  44. package/src/engine/components/view.tsx +344 -0
  45. package/src/engine/generated/redirects.ts +3 -0
  46. package/src/engine/lib/changelog.ts +246 -0
  47. package/src/engine/lib/layout.shared.ts +57 -7
  48. package/src/engine/lib/llms.ts +444 -0
  49. package/src/engine/lib/navigation-normalize.mjs +525 -0
  50. package/src/engine/lib/navigation-normalize.ts +695 -0
  51. package/src/engine/lib/redirects.ts +194 -0
  52. package/src/engine/lib/source.ts +121 -4
  53. package/src/engine/lib/velu.ts +635 -5
  54. package/src/engine/mdx-components.tsx +648 -0
  55. package/src/engine/middleware.ts +66 -0
  56. package/src/engine/next.config.mjs +2 -2
  57. package/src/engine/public/icons/cursor-dark.svg +12 -0
  58. package/src/engine/public/icons/cursor-light.svg +12 -0
  59. package/src/engine/source.config.ts +98 -1
  60. package/src/engine/src/components/PageTitle.astro +16 -5
  61. package/src/engine/src/lib/velu.ts +97 -16
  62. package/src/navigation-normalize.ts +686 -0
  63. package/src/themes.ts +6 -6
  64. package/src/validate.ts +235 -24
  65. package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -69
package/src/cli.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolve, join, dirname, delimiter } from "node:path";
2
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, copyFileSync } from "node:fs";
3
3
  import { spawn } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
@@ -7,6 +7,16 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const PACKAGE_ROOT = resolve(dirname(__filename), "..");
8
8
  const SCHEMA_PATH = join(PACKAGE_ROOT, "schema", "velu.schema.json");
9
9
  const NODE_MODULES_PATH = join(PACKAGE_ROOT, "node_modules");
10
+ const PRIMARY_CONFIG_NAME = "docs.json";
11
+ const LEGACY_CONFIG_NAME = "velu.json";
12
+
13
+ function resolveConfigPath(dir: string): string | null {
14
+ const primary = join(dir, PRIMARY_CONFIG_NAME);
15
+ if (existsSync(primary)) return primary;
16
+ const legacy = join(dir, LEGACY_CONFIG_NAME);
17
+ if (existsSync(legacy)) return legacy;
18
+ return null;
19
+ }
10
20
 
11
21
  /** Build env that lets spawned processes resolve deps from the CLI's own node_modules */
12
22
  function engineEnv(docsDir?: string): NodeJS.ProcessEnv {
@@ -26,27 +36,28 @@ function printHelp() {
26
36
 
27
37
  Usage:
28
38
  velu init Scaffold a new docs project with example files
29
- velu lint Validate velu.json and check referenced pages
39
+ velu lint Validate docs.json (or velu.json) and check referenced pages
30
40
  velu run [--port N] Build site and start dev server (default: 4321)
31
41
  velu build Build a deployable static site (SSG)
42
+ velu paths Output all navigation paths and their source files as JSON
32
43
 
33
44
  Options:
34
45
  --port <number> Port for the dev server (default: 4321)
35
46
  --help Show this help message
36
47
 
37
- Run lint/run/build from a directory containing velu.json.
48
+ Run lint/run/build/paths from a directory containing docs.json (or velu.json).
38
49
  `);
39
50
  }
40
51
 
41
52
  // ── init ────────────────────────────────────────────────────────────────────────
42
53
 
43
54
  function init(targetDir: string) {
44
- if (existsSync(join(targetDir, "velu.json"))) {
45
- console.error("❌ velu.json already exists in this directory.");
55
+ if (resolveConfigPath(targetDir)) {
56
+ console.error("❌ docs.json or velu.json already exists in this directory.");
46
57
  process.exit(1);
47
58
  }
48
59
 
49
- // velu.json
60
+ // docs.json
50
61
  const config = {
51
62
  $schema: "https://raw.githubusercontent.com/aravindc26/velu/main/schema/velu.schema.json",
52
63
  theme: "neutral" as const,
@@ -55,11 +66,17 @@ function init(targetDir: string) {
55
66
  {
56
67
  tab: "Getting Started",
57
68
  slug: "getting-started",
58
- pages: ["quickstart", "installation"],
69
+ pages: [
70
+ "quickstart",
71
+ "installation",
72
+ { separator: "Resources" },
73
+ { label: "Velu Website", href: "https://getvelu.com" },
74
+ ],
59
75
  groups: [
60
76
  {
61
77
  group: "Guides",
62
78
  slug: "guides",
79
+ description: "Step-by-step guides to configure and deploy your docs.",
63
80
  pages: ["guides/configuration", "guides/deployment"],
64
81
  },
65
82
  ],
@@ -70,15 +87,22 @@ function init(targetDir: string) {
70
87
  pages: ["api-reference/overview", "api-reference/authentication"],
71
88
  },
72
89
  ],
90
+ anchors: [
91
+ {
92
+ anchor: "GitHub",
93
+ href: "https://github.com/aravindc26/velu",
94
+ icon: "Github",
95
+ },
96
+ ],
73
97
  },
74
98
  };
75
- writeFileSync(join(targetDir, "velu.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
99
+ writeFileSync(join(targetDir, PRIMARY_CONFIG_NAME), JSON.stringify(config, null, 2) + "\n", "utf-8");
76
100
 
77
101
  // Example pages
78
102
  const pages: Record<string, string> = {
79
- "quickstart.md": `# Quickstart\n\nWelcome to your new documentation site!\n\n## Prerequisites\n\n- Node.js 20.9+\n- npm\n\n## Getting Started\n\n1. Edit the markdown files in this directory\n2. Update \`velu.json\` to configure navigation\n3. Run \`velu run\` to start the dev server\n\n\`\`\`bash\nvelu run\n\`\`\`\n\nYour site is live at \`http://localhost:4321\`.\n`,
103
+ "quickstart.md": `# Quickstart\n\nWelcome to your new documentation site!\n\n## Prerequisites\n\n- Node.js 20.9+\n- npm\n\n## Getting Started\n\n1. Edit the markdown files in this directory\n2. Update \`docs.json\` to configure navigation\n3. Run \`velu run\` to start the dev server\n\n\`\`\`bash\nvelu run\n\`\`\`\n\nYour site is live at \`http://localhost:4321\`.\n`,
80
104
  "installation.md": `# Installation\n\nInstall Velu globally:\n\n\`\`\`bash\nnpm install -g @aravindc26/velu\n\`\`\`\n\nOr run directly with npx:\n\n\`\`\`bash\nnpx @aravindc26/velu run\n\`\`\`\n`,
81
- "guides/configuration.md": `# Configuration\n\nVelu uses a \`velu.json\` file to define your site's navigation.\n\n## Navigation Structure\n\n- **Tabs** — Top-level horizontal navigation\n- **Groups** — Collapsible sidebar sections within a tab\n- **Pages** — Individual markdown documents\n\n## Example\n\n\`\`\`json\n{\n "navigation": {\n "tabs": [\n {\n "tab": "Getting Started",\n "slug": "getting-started",\n "groups": [\n {\n "group": "Basics",\n "slug": "getting-started",\n "pages": ["quickstart"]\n }\n ]\n }\n ]\n }\n}\n\`\`\`\n`,
105
+ "guides/configuration.md": `# Configuration\n\nVelu uses a \`docs.json\` file to define your site's navigation.\n\n## Navigation Structure\n\n- **Tabs** — Top-level horizontal navigation\n- **Groups** — Collapsible sidebar sections within a tab\n- **Pages** — Individual markdown documents\n\n## Example\n\n\`\`\`json\n{\n "navigation": {\n "tabs": [\n {\n "tab": "Getting Started",\n "slug": "getting-started",\n "groups": [\n {\n "group": "Basics",\n "slug": "getting-started",\n "pages": ["quickstart"]\n }\n ]\n }\n ]\n }\n}\n\`\`\`\n`,
82
106
  "guides/deployment.md": `# Deployment\n\nBuild your site for production:\n\n\`\`\`bash\nvelu build\n\`\`\`\n\nThe output is a static site you can deploy anywhere — Netlify, Vercel, GitHub Pages, etc.\n`,
83
107
  "api-reference/overview.md": `# API Overview\n\nThis section covers the API reference for your project.\n\n## Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | /api/health | Health check |\n| POST | /api/data | Create data |\n`,
84
108
  "api-reference/authentication.md": `# Authentication\n\nAll API requests require an API key.\n\n## Headers\n\n\`\`\`\nAuthorization: Bearer YOUR_API_KEY\n\`\`\`\n\n## Getting an API Key\n\nVisit the dashboard to generate your API key.\n`,
@@ -94,7 +118,7 @@ function init(targetDir: string) {
94
118
  console.log(" \x1b[36mvelu\x1b[0m project initialized");
95
119
  console.log("");
96
120
  console.log(" Created:");
97
- console.log(" velu.json");
121
+ console.log(" docs.json");
98
122
  for (const filePath of Object.keys(pages)) {
99
123
  console.log(` ${filePath}`);
100
124
  }
@@ -112,7 +136,7 @@ async function lint(docsDir: string) {
112
136
  const result = validateVeluConfig(docsDir, SCHEMA_PATH);
113
137
 
114
138
  if (result.valid) {
115
- console.log("✅ velu.json is valid. All referenced pages exist.");
139
+ console.log("✅ docs.json/velu.json is valid. All referenced pages exist.");
116
140
  } else {
117
141
  console.error("❌ Validation failed:\n");
118
142
  for (const err of result.errors) {
@@ -122,6 +146,51 @@ async function lint(docsDir: string) {
122
146
  }
123
147
  }
124
148
 
149
+ // ── paths ───────────────────────────────────────────────────────────────────────
150
+
151
+ interface PathEntry {
152
+ path: string;
153
+ file: string | null;
154
+ }
155
+
156
+ async function paths(docsDir: string) {
157
+ const { collectPages } = await import("./validate.js");
158
+ const { normalizeConfigNavigation } = await import("./navigation-normalize.js");
159
+ const { readFileSync, existsSync } = await import("node:fs");
160
+ const { join } = await import("node:path");
161
+
162
+ const configPath = resolveConfigPath(docsDir);
163
+ if (!configPath) {
164
+ console.error("❌ docs.json or velu.json not found.");
165
+ process.exit(1);
166
+ }
167
+
168
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
169
+ const config = normalizeConfigNavigation(raw);
170
+ const pages = collectPages(config);
171
+
172
+ const entries: PathEntry[] = pages.map((pagePath) => {
173
+ // Check for .mdx first, then .md
174
+ const mdxPath = join(docsDir, `${pagePath}.mdx`);
175
+ const mdPath = join(docsDir, `${pagePath}.md`);
176
+
177
+ if (existsSync(mdxPath)) {
178
+ return { path: pagePath, file: `${pagePath}.mdx` };
179
+ }
180
+ if (existsSync(mdPath)) {
181
+ return { path: pagePath, file: `${pagePath}.md` };
182
+ }
183
+ return { path: pagePath, file: null };
184
+ });
185
+
186
+ const output = {
187
+ paths: entries,
188
+ count: entries.length,
189
+ };
190
+
191
+ console.log(JSON.stringify(output, null, 2));
192
+ }
193
+
125
194
  // ── build ────────────────────────────────────────────────────────────────────────
126
195
 
127
196
  async function generateProject(docsDir: string): Promise<string> {
@@ -144,9 +213,41 @@ async function buildStatic(outDir: string, docsDir: string) {
144
213
  });
145
214
  }
146
215
 
216
+ function exportMarkdownRoutes(outDir: string) {
217
+ const distDir = join(outDir, "dist");
218
+ const mdRouteRoot = join(distDir, "md-file");
219
+ if (!existsSync(mdRouteRoot)) return;
220
+
221
+ let copied = 0;
222
+
223
+ function walk(relDir: string) {
224
+ const absDir = join(mdRouteRoot, relDir);
225
+ const entries = readdirSync(absDir, { withFileTypes: true });
226
+
227
+ for (const entry of entries) {
228
+ const relPath = relDir ? join(relDir, entry.name) : entry.name;
229
+ if (entry.isDirectory()) {
230
+ walk(relPath);
231
+ continue;
232
+ }
233
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
234
+
235
+ const src = join(mdRouteRoot, relPath);
236
+ const dest = join(distDir, relPath);
237
+ mkdirSync(dirname(dest), { recursive: true });
238
+ copyFileSync(src, dest);
239
+ copied += 1;
240
+ }
241
+ }
242
+
243
+ walk("");
244
+ console.log(`📝 Exported ${copied} markdown files to static route paths`);
245
+ }
246
+
147
247
  async function buildSite(docsDir: string) {
148
248
  const outDir = await generateProject(docsDir);
149
249
  await buildStatic(outDir, docsDir);
250
+ exportMarkdownRoutes(outDir);
150
251
  const staticOutDir = join(outDir, "dist");
151
252
  console.log(`\n📁 Static site output: ${staticOutDir}`);
152
253
  }
@@ -184,15 +285,15 @@ if (!command || command === "--help" || command === "-h") {
184
285
 
185
286
  const docsDir = process.cwd();
186
287
 
187
- // init doesn't require velu.json
288
+ // init doesn't require docs.json
188
289
  if (command === "init") {
189
290
  init(docsDir);
190
291
  process.exit(0);
191
292
  }
192
293
 
193
- if (!existsSync(join(docsDir, "velu.json"))) {
194
- console.error("❌ No velu.json found in the current directory.");
195
- console.error(" Run `velu init` to scaffold a new project, or run from a directory containing velu.json.");
294
+ if (!resolveConfigPath(docsDir)) {
295
+ console.error("❌ No docs.json or velu.json found in the current directory.");
296
+ console.error(" Run `velu init` to scaffold a new project, or run from a directory containing docs.json.");
196
297
  process.exit(1);
197
298
  }
198
299
 
@@ -201,6 +302,10 @@ switch (command) {
201
302
  await lint(docsDir);
202
303
  break;
203
304
 
305
+ case "paths":
306
+ await paths(docsDir);
307
+ break;
308
+
204
309
  case "build":
205
310
  await buildSite(docsDir);
206
311
  break;