@aravindc26/velu 0.6.0 → 0.8.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 +1 -1
- package/schema/velu.schema.json +17 -19
- package/src/build.ts +120 -128
- package/src/cli.ts +13 -10
- package/src/validate.ts +9 -23
package/package.json
CHANGED
package/schema/velu.schema.json
CHANGED
|
@@ -75,28 +75,16 @@
|
|
|
75
75
|
},
|
|
76
76
|
"navigation": {
|
|
77
77
|
"type": "object",
|
|
78
|
-
"description": "Defines the site navigation hierarchy: tabs → groups → pages.",
|
|
78
|
+
"description": "Defines the site navigation hierarchy: tabs → groups → pages. Groups must belong to a tab.",
|
|
79
|
+
"required": ["tabs"],
|
|
79
80
|
"properties": {
|
|
80
81
|
"tabs": {
|
|
81
82
|
"type": "array",
|
|
82
|
-
"description": "Top-level navigation tabs.",
|
|
83
|
+
"description": "Top-level navigation tabs. Each tab contains groups and/or pages.",
|
|
83
84
|
"items": {
|
|
84
85
|
"$ref": "#/definitions/tab"
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
"groups": {
|
|
88
|
-
"type": "array",
|
|
89
|
-
"description": "Top-level navigation groups (used when there are no tabs, or as default content).",
|
|
90
|
-
"items": {
|
|
91
|
-
"$ref": "#/definitions/group"
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
"pages": {
|
|
95
|
-
"type": "array",
|
|
96
|
-
"description": "Top-level standalone pages.",
|
|
97
|
-
"items": {
|
|
98
|
-
"$ref": "#/definitions/page"
|
|
99
|
-
}
|
|
86
|
+
},
|
|
87
|
+
"minItems": 1
|
|
100
88
|
}
|
|
101
89
|
},
|
|
102
90
|
"additionalProperties": false
|
|
@@ -111,12 +99,17 @@
|
|
|
111
99
|
"group": {
|
|
112
100
|
"type": "object",
|
|
113
101
|
"description": "A group of pages, optionally nested.",
|
|
114
|
-
"required": ["group", "pages"],
|
|
102
|
+
"required": ["group", "slug", "pages"],
|
|
115
103
|
"properties": {
|
|
116
104
|
"group": {
|
|
117
105
|
"type": "string",
|
|
118
106
|
"description": "Display name for the group."
|
|
119
107
|
},
|
|
108
|
+
"slug": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "URL slug prefix for pages in this group. Used as the route prefix: /<slug>/<page-basename>.",
|
|
111
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
112
|
+
},
|
|
120
113
|
"icon": {
|
|
121
114
|
"type": "string",
|
|
122
115
|
"description": "Icon identifier for the group."
|
|
@@ -146,12 +139,17 @@
|
|
|
146
139
|
"tab": {
|
|
147
140
|
"type": "object",
|
|
148
141
|
"description": "A top-level navigation tab.",
|
|
149
|
-
"required": ["tab"],
|
|
142
|
+
"required": ["tab", "slug"],
|
|
150
143
|
"properties": {
|
|
151
144
|
"tab": {
|
|
152
145
|
"type": "string",
|
|
153
146
|
"description": "Display name for the tab."
|
|
154
147
|
},
|
|
148
|
+
"slug": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "URL slug prefix for pages in this tab. Used as the route prefix: /<slug>/<page-basename>.",
|
|
151
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
152
|
+
},
|
|
155
153
|
"icon": {
|
|
156
154
|
"type": "string",
|
|
157
155
|
"description": "Icon identifier for the tab."
|
package/src/build.ts
CHANGED
|
@@ -6,11 +6,13 @@ import { generateThemeCss, type ThemeConfig, type VeluColors, type VeluStyling }
|
|
|
6
6
|
|
|
7
7
|
interface VeluGroup {
|
|
8
8
|
group: string;
|
|
9
|
+
slug: string;
|
|
9
10
|
pages: (string | VeluGroup)[];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface VeluTab {
|
|
13
14
|
tab: string;
|
|
15
|
+
slug: string;
|
|
14
16
|
href?: string;
|
|
15
17
|
pages?: string[];
|
|
16
18
|
groups?: VeluGroup[];
|
|
@@ -23,9 +25,7 @@ interface VeluConfig {
|
|
|
23
25
|
appearance?: "system" | "light" | "dark";
|
|
24
26
|
styling?: VeluStyling;
|
|
25
27
|
navigation: {
|
|
26
|
-
tabs
|
|
27
|
-
groups?: VeluGroup[];
|
|
28
|
-
pages?: string[];
|
|
28
|
+
tabs: VeluTab[];
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -41,6 +41,10 @@ function pageLabelFromSlug(slug: string): string {
|
|
|
41
41
|
return last.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function pageBasename(page: string): string {
|
|
45
|
+
return page.split("/").pop()!;
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
function collectPagesFromGroup(group: VeluGroup): string[] {
|
|
45
49
|
const pages: string[] = [];
|
|
46
50
|
for (const item of group.pages) {
|
|
@@ -50,18 +54,45 @@ function collectPagesFromGroup(group: VeluGroup): string[] {
|
|
|
50
54
|
return pages;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
interface PageMapping {
|
|
58
|
+
src: string; // original page reference (file path without .md)
|
|
59
|
+
dest: string; // slug-based destination path (slug/basename)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildPageMap(config: VeluConfig): PageMapping[] {
|
|
63
|
+
const mappings: PageMapping[] = [];
|
|
64
|
+
|
|
65
|
+
function addPagesFromGroup(group: VeluGroup, tabSlug: string) {
|
|
66
|
+
for (const item of group.pages) {
|
|
67
|
+
if (typeof item === "string") {
|
|
68
|
+
mappings.push({ src: item, dest: `${tabSlug}/${group.slug}/${pageBasename(item)}` });
|
|
69
|
+
} else {
|
|
70
|
+
addPagesFromGroup(item, tabSlug);
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
|
-
|
|
74
|
+
|
|
75
|
+
for (const tab of config.navigation.tabs) {
|
|
76
|
+
if (tab.href) continue;
|
|
77
|
+
// Direct pages in tab use tab slug
|
|
78
|
+
if (tab.pages) {
|
|
79
|
+
for (const page of tab.pages) {
|
|
80
|
+
mappings.push({ src: page, dest: `${tab.slug}/${pageBasename(page)}` });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Groups inside tab: <tab-slug>/<group-slug>/<page-basename>
|
|
84
|
+
if (tab.groups) {
|
|
85
|
+
for (const group of tab.groups) {
|
|
86
|
+
addPagesFromGroup(group, tab.slug);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return mappings;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function collectAllPages(config: VeluConfig): string[] {
|
|
95
|
+
return buildPageMap(config).map(m => m.src);
|
|
65
96
|
}
|
|
66
97
|
|
|
67
98
|
// ── Build ──────────────────────────────────────────────────────────────────────
|
|
@@ -85,11 +116,11 @@ function build(docsDir: string, outDir: string) {
|
|
|
85
116
|
copyFileSync(join(docsDir, "velu.json"), join(outDir, "velu.json"));
|
|
86
117
|
console.log("📋 Copied velu.json");
|
|
87
118
|
|
|
88
|
-
// ── 2. Copy all referenced .md files
|
|
89
|
-
const
|
|
90
|
-
for (const
|
|
91
|
-
const srcPath = join(docsDir, `${
|
|
92
|
-
const destPath = join(outDir, "src", "content", "docs", `${
|
|
119
|
+
// ── 2. Copy all referenced .md files (slug-based destinations) ───────────
|
|
120
|
+
const pageMap = buildPageMap(config);
|
|
121
|
+
for (const { src, dest } of pageMap) {
|
|
122
|
+
const srcPath = join(docsDir, `${src}.md`);
|
|
123
|
+
const destPath = join(outDir, "src", "content", "docs", `${dest}.md`);
|
|
93
124
|
|
|
94
125
|
if (!existsSync(srcPath)) {
|
|
95
126
|
console.warn(`⚠️ Missing: ${srcPath}`);
|
|
@@ -101,7 +132,7 @@ function build(docsDir: string, outDir: string) {
|
|
|
101
132
|
let content = readFileSync(srcPath, "utf-8");
|
|
102
133
|
if (!content.startsWith("---")) {
|
|
103
134
|
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
104
|
-
const title = titleMatch ? titleMatch[1] : pageLabelFromSlug(
|
|
135
|
+
const title = titleMatch ? titleMatch[1] : pageLabelFromSlug(src);
|
|
105
136
|
if (titleMatch) {
|
|
106
137
|
content = content.replace(/^#\s+.+$/m, "").trimStart();
|
|
107
138
|
}
|
|
@@ -110,7 +141,7 @@ function build(docsDir: string, outDir: string) {
|
|
|
110
141
|
|
|
111
142
|
writeFileSync(destPath, content, "utf-8");
|
|
112
143
|
}
|
|
113
|
-
console.log(`📄 Copied ${
|
|
144
|
+
console.log(`📄 Copied ${pageMap.length} pages`);
|
|
114
145
|
|
|
115
146
|
// ── 3. Generate src/lib/velu.ts — the single source of truth ──────────────
|
|
116
147
|
// This module reads velu.json at Astro build/render time. No hardcoded data.
|
|
@@ -121,6 +152,7 @@ import { resolve } from 'node:path';
|
|
|
121
152
|
|
|
122
153
|
export interface VeluGroup {
|
|
123
154
|
group: string;
|
|
155
|
+
slug: string;
|
|
124
156
|
icon?: string;
|
|
125
157
|
tag?: string;
|
|
126
158
|
expanded?: boolean;
|
|
@@ -129,6 +161,7 @@ export interface VeluGroup {
|
|
|
129
161
|
|
|
130
162
|
export interface VeluTab {
|
|
131
163
|
tab: string;
|
|
164
|
+
slug: string;
|
|
132
165
|
icon?: string;
|
|
133
166
|
href?: string;
|
|
134
167
|
pages?: string[];
|
|
@@ -142,9 +175,7 @@ export interface VeluConfig {
|
|
|
142
175
|
appearance?: 'system' | 'light' | 'dark';
|
|
143
176
|
styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
|
|
144
177
|
navigation: {
|
|
145
|
-
tabs
|
|
146
|
-
groups?: VeluGroup[];
|
|
147
|
-
pages?: string[];
|
|
178
|
+
tabs: VeluTab[];
|
|
148
179
|
};
|
|
149
180
|
}
|
|
150
181
|
|
|
@@ -152,7 +183,7 @@ export interface TabMeta {
|
|
|
152
183
|
label: string;
|
|
153
184
|
icon?: string;
|
|
154
185
|
href?: string;
|
|
155
|
-
|
|
186
|
+
slugs: string[];
|
|
156
187
|
firstPage?: string;
|
|
157
188
|
}
|
|
158
189
|
|
|
@@ -170,37 +201,19 @@ export function loadVeluConfig(): VeluConfig {
|
|
|
170
201
|
|
|
171
202
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
172
203
|
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
for (const item of group.pages) {
|
|
176
|
-
if (typeof item === 'string') pages.push(item);
|
|
177
|
-
else pages.push(...collectPagesFromGroup(item));
|
|
178
|
-
}
|
|
179
|
-
return pages;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function collectTabPages(tab: VeluTab): string[] {
|
|
183
|
-
const pages: string[] = [];
|
|
184
|
-
if (tab.pages) pages.push(...tab.pages);
|
|
185
|
-
if (tab.groups) for (const g of tab.groups) pages.push(...collectPagesFromGroup(g));
|
|
186
|
-
return pages;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function detectPathPrefix(slugs: string[]): string {
|
|
190
|
-
if (slugs.length === 0) return '';
|
|
191
|
-
const first = slugs[0];
|
|
192
|
-
const idx = first.indexOf('/');
|
|
193
|
-
if (idx === -1) return '';
|
|
194
|
-
const prefix = first.substring(0, idx);
|
|
195
|
-
if (slugs.every((s) => s.startsWith(prefix + '/'))) return prefix;
|
|
196
|
-
return '';
|
|
204
|
+
function pageBasename(page: string): string {
|
|
205
|
+
return page.split('/').pop()!;
|
|
197
206
|
}
|
|
198
207
|
|
|
199
|
-
|
|
208
|
+
/** Convert a group to a Starlight sidebar entry, using slug-based page paths */
|
|
209
|
+
function veluGroupToSidebar(group: VeluGroup, tabSlug: string): any {
|
|
200
210
|
const items: any[] = [];
|
|
201
211
|
for (const item of group.pages) {
|
|
202
|
-
if (typeof item === 'string')
|
|
203
|
-
|
|
212
|
+
if (typeof item === 'string') {
|
|
213
|
+
items.push(tabSlug + '/' + group.slug + '/' + pageBasename(item));
|
|
214
|
+
} else {
|
|
215
|
+
items.push(veluGroupToSidebar(item, tabSlug));
|
|
216
|
+
}
|
|
204
217
|
}
|
|
205
218
|
const result: any = { label: group.group, items };
|
|
206
219
|
if (group.tag) result.badge = group.tag;
|
|
@@ -208,33 +221,44 @@ function veluGroupToSidebar(group: VeluGroup): any {
|
|
|
208
221
|
return result;
|
|
209
222
|
}
|
|
210
223
|
|
|
224
|
+
/** Get the first page dest path for a tab */
|
|
225
|
+
function firstTabPage(tab: VeluTab): string | undefined {
|
|
226
|
+
if (tab.pages && tab.pages.length > 0) {
|
|
227
|
+
return tab.slug + '/' + pageBasename(tab.pages[0]);
|
|
228
|
+
}
|
|
229
|
+
if (tab.groups) {
|
|
230
|
+
for (const g of tab.groups) {
|
|
231
|
+
const first = firstGroupPage(g, tab.slug);
|
|
232
|
+
if (first) return first;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function firstGroupPage(group: VeluGroup, tabSlug: string): string | undefined {
|
|
239
|
+
for (const item of group.pages) {
|
|
240
|
+
if (typeof item === 'string') return tabSlug + '/' + group.slug + '/' + pageBasename(item);
|
|
241
|
+
const nested = firstGroupPage(item, tabSlug);
|
|
242
|
+
if (nested) return nested;
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
211
247
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
212
248
|
|
|
213
249
|
/** Build the full Starlight sidebar array from velu.json */
|
|
214
250
|
export function getSidebar(): any[] {
|
|
215
251
|
const config = loadVeluConfig();
|
|
216
|
-
const nav = config.navigation;
|
|
217
252
|
const sidebar: any[] = [];
|
|
218
253
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (nav.pages) {
|
|
226
|
-
for (const page of nav.pages) sidebar.push(page);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Tab content as top-level groups
|
|
230
|
-
if (nav.tabs) {
|
|
231
|
-
for (const tab of nav.tabs) {
|
|
232
|
-
if (tab.href) continue;
|
|
233
|
-
const items: any[] = [];
|
|
234
|
-
if (tab.groups) for (const g of tab.groups) items.push(veluGroupToSidebar(g));
|
|
235
|
-
if (tab.pages) for (const p of tab.pages) items.push(p);
|
|
236
|
-
sidebar.push({ label: tab.tab, items });
|
|
254
|
+
for (const tab of config.navigation.tabs) {
|
|
255
|
+
if (tab.href) continue;
|
|
256
|
+
const items: any[] = [];
|
|
257
|
+
if (tab.groups) for (const g of tab.groups) items.push(veluGroupToSidebar(g, tab.slug));
|
|
258
|
+
if (tab.pages) {
|
|
259
|
+
for (const p of tab.pages) items.push(tab.slug + '/' + pageBasename(p));
|
|
237
260
|
}
|
|
261
|
+
sidebar.push({ label: tab.tab, items });
|
|
238
262
|
}
|
|
239
263
|
|
|
240
264
|
return sidebar;
|
|
@@ -243,60 +267,33 @@ export function getSidebar(): any[] {
|
|
|
243
267
|
/** Get tab metadata for the header navigation */
|
|
244
268
|
export function getTabs(): TabMeta[] {
|
|
245
269
|
const config = loadVeluConfig();
|
|
246
|
-
const nav = config.navigation;
|
|
247
270
|
const tabs: TabMeta[] = [];
|
|
248
271
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
firstPage: defaultPages[0],
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (nav.tabs) {
|
|
264
|
-
for (const tab of nav.tabs) {
|
|
265
|
-
if (tab.href) {
|
|
266
|
-
tabs.push({ label: tab.tab, icon: tab.icon, href: tab.href, pathPrefix: '' });
|
|
267
|
-
} else {
|
|
268
|
-
const tabPages = collectTabPages(tab);
|
|
269
|
-
tabs.push({
|
|
270
|
-
label: tab.tab,
|
|
271
|
-
icon: tab.icon,
|
|
272
|
-
pathPrefix: detectPathPrefix(tabPages) || tabPages[0]?.split('/')[0] || '',
|
|
273
|
-
firstPage: tabPages[0],
|
|
274
|
-
});
|
|
275
|
-
}
|
|
272
|
+
for (const tab of config.navigation.tabs) {
|
|
273
|
+
if (tab.href) {
|
|
274
|
+
tabs.push({ label: tab.tab, icon: tab.icon, href: tab.href, slugs: [] });
|
|
275
|
+
} else {
|
|
276
|
+
tabs.push({
|
|
277
|
+
label: tab.tab,
|
|
278
|
+
icon: tab.icon,
|
|
279
|
+
slugs: [tab.slug],
|
|
280
|
+
firstPage: firstTabPage(tab),
|
|
281
|
+
});
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
return tabs;
|
|
280
286
|
}
|
|
281
287
|
|
|
282
|
-
/** Get the mapping of
|
|
288
|
+
/** Get the mapping of slug → sidebar group labels for filtering.
|
|
289
|
+
* Maps every slug (tab, group, nested group) to the labels that should be visible. */
|
|
283
290
|
export function getTabSidebarMap(): Record<string, string[]> {
|
|
284
291
|
const config = loadVeluConfig();
|
|
285
|
-
const nav = config.navigation;
|
|
286
292
|
const map: Record<string, string[]> = {};
|
|
287
293
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
map['__default__'] = defaultLabels;
|
|
292
|
-
|
|
293
|
-
if (nav.tabs) {
|
|
294
|
-
for (const tab of nav.tabs) {
|
|
295
|
-
if (tab.href) continue;
|
|
296
|
-
const tabPages = collectTabPages(tab);
|
|
297
|
-
const prefix = detectPathPrefix(tabPages) || tabPages[0]?.split('/')[0] || '';
|
|
298
|
-
map[prefix] = [tab.tab];
|
|
299
|
-
}
|
|
294
|
+
for (const tab of config.navigation.tabs) {
|
|
295
|
+
if (tab.href) continue;
|
|
296
|
+
map[tab.slug] = [tab.tab];
|
|
300
297
|
}
|
|
301
298
|
|
|
302
299
|
return map;
|
|
@@ -364,30 +361,25 @@ const currentPath = Astro.url.pathname;
|
|
|
364
361
|
|
|
365
362
|
function isTabActive(tab: any, path: string): boolean {
|
|
366
363
|
if (tab.href) return false;
|
|
367
|
-
if (tab.
|
|
368
|
-
|
|
369
|
-
.filter((t: any) => t.pathPrefix && t.pathPrefix !== '__default__' && !t.href)
|
|
370
|
-
.map((t: any) => t.pathPrefix);
|
|
371
|
-
return !otherPrefixes.some((p: string) => path.startsWith('/' + p + '/'));
|
|
372
|
-
}
|
|
373
|
-
return path.startsWith('/' + tab.pathPrefix + '/');
|
|
364
|
+
if (!tab.slugs || tab.slugs.length === 0) return false;
|
|
365
|
+
return tab.slugs.some((s: string) => path.startsWith('/' + s + '/'));
|
|
374
366
|
}
|
|
375
367
|
|
|
376
|
-
function
|
|
377
|
-
const
|
|
378
|
-
for (const prefix of
|
|
368
|
+
function getActiveTabSlug(path: string): string {
|
|
369
|
+
const allPrefixes = Object.keys(tabSidebarMap);
|
|
370
|
+
for (const prefix of allPrefixes) {
|
|
379
371
|
if (path.startsWith('/' + prefix + '/')) return prefix;
|
|
380
372
|
}
|
|
381
|
-
return '
|
|
373
|
+
return '';
|
|
382
374
|
}
|
|
383
375
|
|
|
384
|
-
const
|
|
385
|
-
const visibleLabels = new Set(tabSidebarMap[
|
|
376
|
+
const activeSlug = getActiveTabSlug(currentPath);
|
|
377
|
+
const visibleLabels = new Set(tabSidebarMap[activeSlug] || []);
|
|
386
378
|
|
|
387
379
|
const { sidebar } = Astro.locals.starlightRoute;
|
|
388
380
|
const filteredSidebar = sidebar.filter((entry: any) => {
|
|
389
381
|
if (entry.type === 'group') return visibleLabels.has(entry.label);
|
|
390
|
-
return
|
|
382
|
+
return false;
|
|
391
383
|
});
|
|
392
384
|
---
|
|
393
385
|
|
|
@@ -726,7 +718,7 @@ const title = Astro.locals.starlightRoute.entry.data.title;
|
|
|
726
718
|
|
|
727
719
|
function connectSSE() {
|
|
728
720
|
if (state.eventSource) state.eventSource.close();
|
|
729
|
-
var url = API_BASE + '/conversations/' + state.conversationId + '/events?after_seq=' + state.lastSeq;
|
|
721
|
+
var url = API_BASE + '/conversations/' + state.conversationId + '/events?after_seq=' + state.lastSeq + '&token=' + encodeURIComponent(state.conversationToken || '');
|
|
730
722
|
state.eventSource = new EventSource(url);
|
|
731
723
|
|
|
732
724
|
state.eventSource.addEventListener('assistant.completed', function(e) {
|
|
@@ -1497,7 +1489,7 @@ html.velu-assistant-wide .velu-ask-bar {
|
|
|
1497
1489
|
"utf-8"
|
|
1498
1490
|
);
|
|
1499
1491
|
|
|
1500
|
-
const firstPage =
|
|
1492
|
+
const firstPage = pageMap[0]?.dest || "quickstart";
|
|
1501
1493
|
writeFileSync(
|
|
1502
1494
|
join(outDir, "src", "content", "docs", "index.mdx"),
|
|
1503
1495
|
`---\ntitle: "Welcome to Velu Docs"\ndescription: Documentation powered by Velu\n---\n\nWelcome to the documentation. Head over to the [Quickstart](/${firstPage}/) to get started.\n`,
|
package/src/cli.ts
CHANGED
|
@@ -42,18 +42,21 @@ function init(targetDir: string) {
|
|
|
42
42
|
navigation: {
|
|
43
43
|
tabs: [
|
|
44
44
|
{
|
|
45
|
-
tab: "
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
groups: [
|
|
50
|
-
{
|
|
51
|
-
group: "Getting Started",
|
|
45
|
+
tab: "Getting Started",
|
|
46
|
+
slug: "getting-started",
|
|
52
47
|
pages: ["quickstart", "installation"],
|
|
48
|
+
groups: [
|
|
49
|
+
{
|
|
50
|
+
group: "Guides",
|
|
51
|
+
slug: "guides",
|
|
52
|
+
pages: ["guides/configuration", "guides/deployment"],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
53
55
|
},
|
|
54
56
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
tab: "API Reference",
|
|
58
|
+
slug: "api-reference",
|
|
59
|
+
pages: ["api-reference/overview", "api-reference/authentication"],
|
|
57
60
|
},
|
|
58
61
|
],
|
|
59
62
|
},
|
|
@@ -64,7 +67,7 @@ function init(targetDir: string) {
|
|
|
64
67
|
const pages: Record<string, string> = {
|
|
65
68
|
"quickstart.md": `# Quickstart\n\nWelcome to your new documentation site!\n\n## Prerequisites\n\n- Node.js 18+\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`,
|
|
66
69
|
"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`,
|
|
67
|
-
"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\n- **Pages** — Individual markdown documents\n\n## Example\n\n\`\`\`json\n{\n "navigation": {\n "
|
|
70
|
+
"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`,
|
|
68
71
|
"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`,
|
|
69
72
|
"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`,
|
|
70
73
|
"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`,
|
package/src/validate.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { resolve, join } from "node:path";
|
|
|
5
5
|
|
|
6
6
|
interface VeluGroup {
|
|
7
7
|
group: string;
|
|
8
|
+
slug: string;
|
|
8
9
|
icon?: string;
|
|
9
10
|
tag?: string;
|
|
10
11
|
expanded?: boolean;
|
|
@@ -13,6 +14,7 @@ interface VeluGroup {
|
|
|
13
14
|
|
|
14
15
|
interface VeluTab {
|
|
15
16
|
tab: string;
|
|
17
|
+
slug: string;
|
|
16
18
|
icon?: string;
|
|
17
19
|
href?: string;
|
|
18
20
|
pages?: string[];
|
|
@@ -26,9 +28,7 @@ interface VeluConfig {
|
|
|
26
28
|
appearance?: "system" | "light" | "dark";
|
|
27
29
|
styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
|
|
28
30
|
navigation: {
|
|
29
|
-
tabs
|
|
30
|
-
groups?: VeluGroup[];
|
|
31
|
-
pages?: string[];
|
|
31
|
+
tabs: VeluTab[];
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -50,27 +50,13 @@ function collectPages(config: VeluConfig): string[] {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
pages.push(...nav.pages);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (nav.groups) {
|
|
60
|
-
for (const group of nav.groups) {
|
|
61
|
-
collectFromGroup(group);
|
|
53
|
+
for (const tab of config.navigation.tabs) {
|
|
54
|
+
if (tab.pages) {
|
|
55
|
+
pages.push(...tab.pages);
|
|
62
56
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (const tab of nav.tabs) {
|
|
67
|
-
if (tab.pages) {
|
|
68
|
-
pages.push(...tab.pages);
|
|
69
|
-
}
|
|
70
|
-
if (tab.groups) {
|
|
71
|
-
for (const group of tab.groups) {
|
|
72
|
-
collectFromGroup(group);
|
|
73
|
-
}
|
|
57
|
+
if (tab.groups) {
|
|
58
|
+
for (const group of tab.groups) {
|
|
59
|
+
collectFromGroup(group);
|
|
74
60
|
}
|
|
75
61
|
}
|
|
76
62
|
}
|