@aravindc26/velu 0.11.0 → 0.11.3
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 +1251 -115
- package/src/build.ts +1121 -304
- package/src/cli.ts +90 -26
- package/src/engine/_server.mjs +1684 -277
- package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
- 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 +3157 -3
- package/src/engine/app/layout.tsx +56 -1
- 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 +63 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
- package/src/engine/app/sitemap.xml/route.ts +82 -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/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 +1682 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +27 -3
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +1 -6
- package/src/engine/components/search.tsx +4 -0
- package/src/engine/components/sidebar-links.tsx +13 -15
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/toc-examples.tsx +110 -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 +30 -2
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +481 -412
- package/src/engine/lib/navigation-normalize.ts +261 -54
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +107 -4
- package/src/engine/lib/velu.ts +368 -2
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- 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 +11 -3
- package/src/navigation-normalize.ts +252 -54
- package/src/themes.ts +6 -6
- package/src/validate.ts +119 -6
- package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -146
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, cpSync, rmSync } 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,7 +36,7 @@ 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)
|
|
32
42
|
velu paths Output all navigation paths and their source files as JSON
|
|
@@ -35,19 +45,19 @@ function printHelp() {
|
|
|
35
45
|
--port <number> Port for the dev server (default: 4321)
|
|
36
46
|
--help Show this help message
|
|
37
47
|
|
|
38
|
-
Run lint/run/build/paths from a directory containing velu.json.
|
|
48
|
+
Run lint/run/build/paths from a directory containing docs.json (or velu.json).
|
|
39
49
|
`);
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
// ── init ────────────────────────────────────────────────────────────────────────
|
|
43
53
|
|
|
44
54
|
function init(targetDir: string) {
|
|
45
|
-
if (
|
|
46
|
-
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.");
|
|
47
57
|
process.exit(1);
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
//
|
|
60
|
+
// docs.json
|
|
51
61
|
const config = {
|
|
52
62
|
$schema: "https://raw.githubusercontent.com/aravindc26/velu/main/schema/velu.schema.json",
|
|
53
63
|
theme: "neutral" as const,
|
|
@@ -86,13 +96,13 @@ function init(targetDir: string) {
|
|
|
86
96
|
],
|
|
87
97
|
},
|
|
88
98
|
};
|
|
89
|
-
writeFileSync(join(targetDir,
|
|
99
|
+
writeFileSync(join(targetDir, PRIMARY_CONFIG_NAME), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
90
100
|
|
|
91
101
|
// Example pages
|
|
92
102
|
const pages: Record<string, string> = {
|
|
93
|
-
"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`,
|
|
94
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`,
|
|
95
|
-
"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`,
|
|
96
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`,
|
|
97
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`,
|
|
98
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`,
|
|
@@ -108,7 +118,7 @@ function init(targetDir: string) {
|
|
|
108
118
|
console.log(" \x1b[36mvelu\x1b[0m project initialized");
|
|
109
119
|
console.log("");
|
|
110
120
|
console.log(" Created:");
|
|
111
|
-
console.log("
|
|
121
|
+
console.log(" docs.json");
|
|
112
122
|
for (const filePath of Object.keys(pages)) {
|
|
113
123
|
console.log(` ${filePath}`);
|
|
114
124
|
}
|
|
@@ -126,7 +136,7 @@ async function lint(docsDir: string) {
|
|
|
126
136
|
const result = validateVeluConfig(docsDir, SCHEMA_PATH);
|
|
127
137
|
|
|
128
138
|
if (result.valid) {
|
|
129
|
-
console.log("✅ velu.json is valid. All referenced pages exist.");
|
|
139
|
+
console.log("✅ docs.json/velu.json is valid. All referenced pages exist.");
|
|
130
140
|
} else {
|
|
131
141
|
console.error("❌ Validation failed:\n");
|
|
132
142
|
for (const err of result.errors) {
|
|
@@ -149,9 +159,9 @@ async function paths(docsDir: string) {
|
|
|
149
159
|
const { readFileSync, existsSync } = await import("node:fs");
|
|
150
160
|
const { join } = await import("node:path");
|
|
151
161
|
|
|
152
|
-
const configPath =
|
|
153
|
-
if (!
|
|
154
|
-
console.error("❌ velu.json not found.");
|
|
162
|
+
const configPath = resolveConfigPath(docsDir);
|
|
163
|
+
if (!configPath) {
|
|
164
|
+
console.error("❌ docs.json or velu.json not found.");
|
|
155
165
|
process.exit(1);
|
|
156
166
|
}
|
|
157
167
|
|
|
@@ -185,13 +195,25 @@ async function paths(docsDir: string) {
|
|
|
185
195
|
|
|
186
196
|
async function generateProject(docsDir: string): Promise<string> {
|
|
187
197
|
const { build } = await import("./build.js");
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
const outDir = join(PACKAGE_ROOT, ".velu-out");
|
|
198
|
+
// Generate into the active docs project directory.
|
|
199
|
+
const outDir = join(docsDir, ".velu-out");
|
|
191
200
|
build(docsDir, outDir);
|
|
192
201
|
return outDir;
|
|
193
202
|
}
|
|
194
203
|
|
|
204
|
+
function samePath(a: string, b: string): boolean {
|
|
205
|
+
return resolve(a).replace(/\\/g, "/").toLowerCase() === resolve(b).replace(/\\/g, "/").toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function prepareRuntimeOutDir(docsOutDir: string): string {
|
|
209
|
+
const runtimeOutDir = join(PACKAGE_ROOT, ".velu-out");
|
|
210
|
+
if (samePath(docsOutDir, runtimeOutDir)) return runtimeOutDir;
|
|
211
|
+
|
|
212
|
+
rmSync(runtimeOutDir, { recursive: true, force: true });
|
|
213
|
+
cpSync(docsOutDir, runtimeOutDir, { recursive: true, force: true });
|
|
214
|
+
return runtimeOutDir;
|
|
215
|
+
}
|
|
216
|
+
|
|
195
217
|
async function buildStatic(outDir: string, docsDir: string) {
|
|
196
218
|
await new Promise<void>((res, rej) => {
|
|
197
219
|
const child = spawn("node", ["_server.mjs", "build"], {
|
|
@@ -203,10 +225,51 @@ async function buildStatic(outDir: string, docsDir: string) {
|
|
|
203
225
|
});
|
|
204
226
|
}
|
|
205
227
|
|
|
228
|
+
function exportMarkdownRoutes(outDir: string) {
|
|
229
|
+
const distDir = join(outDir, "dist");
|
|
230
|
+
const mdRouteRoot = join(distDir, "md-file");
|
|
231
|
+
if (!existsSync(mdRouteRoot)) return;
|
|
232
|
+
|
|
233
|
+
let copied = 0;
|
|
234
|
+
|
|
235
|
+
function walk(relDir: string) {
|
|
236
|
+
const absDir = join(mdRouteRoot, relDir);
|
|
237
|
+
const entries = readdirSync(absDir, { withFileTypes: true });
|
|
238
|
+
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
const relPath = relDir ? join(relDir, entry.name) : entry.name;
|
|
241
|
+
if (entry.isDirectory()) {
|
|
242
|
+
walk(relPath);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
|
|
246
|
+
|
|
247
|
+
const src = join(mdRouteRoot, relPath);
|
|
248
|
+
const dest = join(distDir, relPath);
|
|
249
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
250
|
+
copyFileSync(src, dest);
|
|
251
|
+
copied += 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
walk("");
|
|
256
|
+
console.log(`📝 Exported ${copied} markdown files to static route paths`);
|
|
257
|
+
}
|
|
258
|
+
|
|
206
259
|
async function buildSite(docsDir: string) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
260
|
+
const docsOutDir = await generateProject(docsDir);
|
|
261
|
+
const runtimeOutDir = prepareRuntimeOutDir(docsOutDir);
|
|
262
|
+
await buildStatic(runtimeOutDir, docsDir);
|
|
263
|
+
exportMarkdownRoutes(runtimeOutDir);
|
|
264
|
+
|
|
265
|
+
if (!samePath(docsOutDir, runtimeOutDir)) {
|
|
266
|
+
const docsDistDir = join(docsOutDir, "dist");
|
|
267
|
+
const runtimeDistDir = join(runtimeOutDir, "dist");
|
|
268
|
+
rmSync(docsDistDir, { recursive: true, force: true });
|
|
269
|
+
cpSync(runtimeDistDir, docsDistDir, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const staticOutDir = join(docsOutDir, "dist");
|
|
210
273
|
console.log(`\n📁 Static site output: ${staticOutDir}`);
|
|
211
274
|
}
|
|
212
275
|
|
|
@@ -227,8 +290,9 @@ function spawnServer(outDir: string, command: string, port: number, docsDir: str
|
|
|
227
290
|
}
|
|
228
291
|
|
|
229
292
|
async function run(docsDir: string, port: number) {
|
|
230
|
-
const
|
|
231
|
-
|
|
293
|
+
const docsOutDir = await generateProject(docsDir);
|
|
294
|
+
const runtimeOutDir = prepareRuntimeOutDir(docsOutDir);
|
|
295
|
+
spawnServer(runtimeOutDir, "dev", port, docsDir);
|
|
232
296
|
}
|
|
233
297
|
|
|
234
298
|
// ── Parse args ───────────────────────────────────────────────────────────────────
|
|
@@ -243,15 +307,15 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
243
307
|
|
|
244
308
|
const docsDir = process.cwd();
|
|
245
309
|
|
|
246
|
-
// init doesn't require
|
|
310
|
+
// init doesn't require docs.json
|
|
247
311
|
if (command === "init") {
|
|
248
312
|
init(docsDir);
|
|
249
313
|
process.exit(0);
|
|
250
314
|
}
|
|
251
315
|
|
|
252
|
-
if (!
|
|
253
|
-
console.error("❌ No velu.json found in the current directory.");
|
|
254
|
-
console.error(" Run `velu init` to scaffold a new project, or run from a directory containing
|
|
316
|
+
if (!resolveConfigPath(docsDir)) {
|
|
317
|
+
console.error("❌ No docs.json or velu.json found in the current directory.");
|
|
318
|
+
console.error(" Run `velu init` to scaffold a new project, or run from a directory containing docs.json.");
|
|
255
319
|
process.exit(1);
|
|
256
320
|
}
|
|
257
321
|
|