@aravindc26/velu 0.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aravindc26/velu",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A modern documentation site generator powered by Markdown and JSON configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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?: VeluTab[];
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
- function collectAllPages(config: VeluConfig): string[] {
54
- const pages: string[] = [];
55
- const nav = config.navigation;
56
- if (nav.pages) pages.push(...nav.pages);
57
- if (nav.groups) for (const g of nav.groups) pages.push(...collectPagesFromGroup(g));
58
- if (nav.tabs) {
59
- for (const tab of nav.tabs) {
60
- if (tab.pages) pages.push(...tab.pages);
61
- if (tab.groups) for (const g of tab.groups) pages.push(...collectPagesFromGroup(g));
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
- return pages;
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 allPages = collectAllPages(config);
90
- for (const page of allPages) {
91
- const srcPath = join(docsDir, `${page}.md`);
92
- const destPath = join(outDir, "src", "content", "docs", `${page}.md`);
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(page);
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 ${allPages.length} pages`);
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?: VeluTab[];
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
- pathPrefix: string;
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 collectPagesFromGroup(group: VeluGroup): string[] {
174
- const pages: string[] = [];
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
- function veluGroupToSidebar(group: VeluGroup): any {
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') items.push(item);
203
- else items.push(veluGroupToSidebar(item));
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
- // Default groups
220
- if (nav.groups) {
221
- for (const group of nav.groups) sidebar.push(veluGroupToSidebar(group));
222
- }
223
-
224
- // Default standalone pages
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
- // Default "Docs" tab from groups/pages
250
- const defaultPages: string[] = [];
251
- if (nav.groups) for (const g of nav.groups) defaultPages.push(...collectPagesFromGroup(g));
252
- if (nav.pages) defaultPages.push(...nav.pages);
253
-
254
- if (defaultPages.length > 0) {
255
- tabs.push({
256
- label: 'Docs',
257
- icon: 'book-open',
258
- pathPrefix: detectPathPrefix(defaultPages) || '__default__',
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 path prefix → sidebar group labels for filtering */
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
- // Default tab owns top-level groups
289
- const defaultLabels: string[] = [];
290
- if (nav.groups) for (const g of nav.groups) defaultLabels.push(g.group);
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.pathPrefix === '__default__') {
368
- const otherPrefixes = tabs
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 getActivePrefix(path: string): string {
377
- const prefixes = Object.keys(tabSidebarMap).filter(p => p !== '__default__');
378
- for (const prefix of prefixes) {
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 '__default__';
373
+ return '';
382
374
  }
383
375
 
384
- const activePrefix = getActivePrefix(currentPath);
385
- const visibleLabels = new Set(tabSidebarMap[activePrefix] || []);
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 activePrefix === '__default__';
382
+ return false;
391
383
  });
392
384
  ---
393
385
 
@@ -1497,7 +1489,7 @@ html.velu-assistant-wide .velu-ask-bar {
1497
1489
  "utf-8"
1498
1490
  );
1499
1491
 
1500
- const firstPage = allPages[0] || "quickstart";
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: "API Reference",
46
- pages: ["api-reference/overview", "api-reference/authentication"],
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
- group: "Guides",
56
- pages: ["guides/configuration", "guides/deployment"],
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 "groups": [\n {\n "group": "Getting Started",\n "pages": ["quickstart"]\n }\n ]\n }\n}\n\`\`\`\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?: VeluTab[];
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 nav = config.navigation;
54
-
55
- if (nav.pages) {
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
- if (nav.tabs) {
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
  }