@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.
- package/package.json +15 -6
- package/schema/velu.schema.json +1864 -30
- package/src/build.ts +1161 -180
- package/src/cli.ts +121 -16
- package/src/engine/_server.mjs +1708 -192
- package/src/engine/app/(docs)/[...slug]/layout.tsx +377 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +917 -0
- package/src/engine/app/(docs)/layout.tsx +1 -13
- package/src/engine/app/api/proxy/route.ts +23 -0
- package/src/engine/app/copy-page.css +59 -1
- package/src/engine/app/global.css +3487 -6
- package/src/engine/app/layout.tsx +59 -8
- package/src/engine/app/llms-file/route.ts +87 -0
- package/src/engine/app/llms-full-file/route.ts +62 -0
- package/src/engine/app/md-file/[...slug]/route.ts +409 -0
- package/src/engine/app/page.tsx +45 -0
- package/src/engine/app/robots.txt/route.ts +61 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +176 -0
- package/src/engine/app/search.css +20 -0
- package/src/engine/app/sitemap.xml/route.ts +80 -0
- package/src/engine/components/assistant.tsx +16 -5
- package/src/engine/components/changelog-filters.tsx +114 -0
- package/src/engine/components/code-group.tsx +383 -0
- package/src/engine/components/color.tsx +118 -0
- package/src/engine/components/expandable.tsx +77 -0
- package/src/engine/components/icon.tsx +136 -0
- package/src/engine/components/image-zoom-fallback.tsx +147 -0
- package/src/engine/components/image.tsx +111 -0
- package/src/engine/components/lang-switcher.tsx +95 -0
- package/src/engine/components/manual-api-playground.tsx +154 -0
- package/src/engine/components/mermaid.tsx +142 -0
- package/src/engine/components/openapi-toc-sync.tsx +59 -0
- package/src/engine/components/openapi.tsx +1679 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +102 -0
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +21 -0
- package/src/engine/components/search.tsx +70 -3
- package/src/engine/components/sidebar-links.tsx +49 -0
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/theme-toggle.tsx +39 -0
- package/src/engine/components/toc-examples.tsx +110 -0
- package/src/engine/components/version-switcher.tsx +89 -0
- package/src/engine/components/view.tsx +344 -0
- package/src/engine/generated/redirects.ts +3 -0
- package/src/engine/lib/changelog.ts +246 -0
- package/src/engine/lib/layout.shared.ts +57 -7
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +525 -0
- package/src/engine/lib/navigation-normalize.ts +695 -0
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +121 -4
- package/src/engine/lib/velu.ts +635 -5
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- package/src/engine/next.config.mjs +2 -2
- package/src/engine/public/icons/cursor-dark.svg +12 -0
- package/src/engine/public/icons/cursor-light.svg +12 -0
- package/src/engine/source.config.ts +98 -1
- package/src/engine/src/components/PageTitle.astro +16 -5
- package/src/engine/src/lib/velu.ts +97 -16
- package/src/navigation-normalize.ts +686 -0
- package/src/themes.ts +6 -6
- package/src/validate.ts +235 -24
- 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 (
|
|
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
|
-
//
|
|
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: [
|
|
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,
|
|
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 \`
|
|
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 \`
|
|
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("
|
|
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
|
|
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 (!
|
|
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
|
|
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;
|