@alliance-droid/svelte-docs-system 0.0.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/COMPONENTS.md +365 -0
- package/COVERAGE_REPORT.md +663 -0
- package/README.md +42 -0
- package/SEARCH_VERIFICATION.md +229 -0
- package/TEST_SUMMARY.md +344 -0
- package/bin/init.js +821 -0
- package/docs/E2E_TESTS.md +354 -0
- package/docs/TESTING.md +754 -0
- package/docs/de/index.md +41 -0
- package/docs/en/COMPONENTS.md +443 -0
- package/docs/en/api/examples.md +100 -0
- package/docs/en/api/overview.md +69 -0
- package/docs/en/components/index.md +622 -0
- package/docs/en/config/navigation.md +505 -0
- package/docs/en/config/theme-and-colors.md +395 -0
- package/docs/en/getting-started/integration.md +406 -0
- package/docs/en/guides/common-setups.md +651 -0
- package/docs/en/index.md +243 -0
- package/docs/en/markdown.md +102 -0
- package/docs/en/routing.md +64 -0
- package/docs/en/setup.md +52 -0
- package/docs/en/troubleshooting.md +704 -0
- package/docs/es/index.md +41 -0
- package/docs/fr/index.md +41 -0
- package/docs/ja/index.md +41 -0
- package/package.json +40 -0
- package/pagefind.toml +8 -0
- package/postcss.config.js +5 -0
- package/src/app.css +119 -0
- package/src/app.d.ts +13 -0
- package/src/app.html +11 -0
- package/src/lib/assets/favicon.svg +1 -0
- package/src/lib/components/APITable.svelte +120 -0
- package/src/lib/components/APITable.test.ts +153 -0
- package/src/lib/components/Breadcrumbs.svelte +85 -0
- package/src/lib/components/Breadcrumbs.test.ts +148 -0
- package/src/lib/components/Callout.svelte +60 -0
- package/src/lib/components/Callout.test.ts +100 -0
- package/src/lib/components/CodeBlock.svelte +68 -0
- package/src/lib/components/CodeBlock.test.ts +133 -0
- package/src/lib/components/DocLayout.svelte +84 -0
- package/src/lib/components/Footer.svelte +78 -0
- package/src/lib/components/Image.svelte +100 -0
- package/src/lib/components/Image.test.ts +163 -0
- package/src/lib/components/Navbar.svelte +141 -0
- package/src/lib/components/Search.svelte +248 -0
- package/src/lib/components/Sidebar.svelte +110 -0
- package/src/lib/components/Tabs.svelte +48 -0
- package/src/lib/components/Tabs.test.ts +102 -0
- package/src/lib/config.test.ts +140 -0
- package/src/lib/config.ts +179 -0
- package/src/lib/configIntegration.test.ts +272 -0
- package/src/lib/configLoader.ts +231 -0
- package/src/lib/configParser.test.ts +217 -0
- package/src/lib/configParser.ts +234 -0
- package/src/lib/index.ts +34 -0
- package/src/lib/integration.test.ts +426 -0
- package/src/lib/navigationBuilder.test.ts +338 -0
- package/src/lib/navigationBuilder.ts +268 -0
- package/src/lib/performance.test.ts +369 -0
- package/src/lib/routing.test.ts +202 -0
- package/src/lib/routing.ts +127 -0
- package/src/lib/search-functionality.test.ts +493 -0
- package/src/lib/stores/i18n.test.ts +180 -0
- package/src/lib/stores/i18n.ts +143 -0
- package/src/lib/stores/nav.ts +36 -0
- package/src/lib/stores/search.test.ts +140 -0
- package/src/lib/stores/search.ts +162 -0
- package/src/lib/stores/theme.ts +59 -0
- package/src/lib/stores/version.test.ts +139 -0
- package/src/lib/stores/version.ts +111 -0
- package/src/lib/themeCustomization.test.ts +223 -0
- package/src/lib/themeCustomization.ts +212 -0
- package/src/lib/utils/highlight.test.ts +136 -0
- package/src/lib/utils/highlight.ts +100 -0
- package/src/lib/utils/index.ts +7 -0
- package/src/lib/utils/markdown.test.ts +357 -0
- package/src/lib/utils/markdown.ts +77 -0
- package/src/routes/+layout.server.ts +1 -0
- package/src/routes/+layout.svelte +28 -0
- package/src/routes/+page.svelte +165 -0
- package/static/robots.txt +3 -0
- package/svelte.config.js +18 -0
- package/tailwind.config.ts +55 -0
- package/template-starter/.github/workflows/build.yml +40 -0
- package/template-starter/.github/workflows/deploy-github-pages.yml +47 -0
- package/template-starter/.github/workflows/deploy-netlify.yml +41 -0
- package/template-starter/.github/workflows/deploy-vercel.yml +64 -0
- package/template-starter/NPM-PACKAGE-SETUP.md +233 -0
- package/template-starter/README.md +320 -0
- package/template-starter/docs/_config.json +39 -0
- package/template-starter/docs/api/components.md +257 -0
- package/template-starter/docs/api/overview.md +169 -0
- package/template-starter/docs/guides/configuration.md +145 -0
- package/template-starter/docs/guides/github-pages-deployment.md +254 -0
- package/template-starter/docs/guides/netlify-deployment.md +159 -0
- package/template-starter/docs/guides/vercel-deployment.md +131 -0
- package/template-starter/docs/index.md +49 -0
- package/template-starter/docs/setup.md +149 -0
- package/template-starter/package.json +31 -0
- package/template-starter/pagefind.toml +3 -0
- package/template-starter/postcss.config.js +5 -0
- package/template-starter/src/app.css +34 -0
- package/template-starter/src/app.d.ts +13 -0
- package/template-starter/src/app.html +11 -0
- package/template-starter/src/lib/components/APITable.svelte +120 -0
- package/template-starter/src/lib/components/APITable.test.ts +19 -0
- package/template-starter/src/lib/components/Breadcrumbs.svelte +85 -0
- package/template-starter/src/lib/components/Breadcrumbs.test.ts +19 -0
- package/template-starter/src/lib/components/Callout.svelte +60 -0
- package/template-starter/src/lib/components/Callout.test.ts +16 -0
- package/template-starter/src/lib/components/CodeBlock.svelte +68 -0
- package/template-starter/src/lib/components/CodeBlock.test.ts +12 -0
- package/template-starter/src/lib/components/DocLayout.svelte +84 -0
- package/template-starter/src/lib/components/Footer.svelte +78 -0
- package/template-starter/src/lib/components/Image.svelte +100 -0
- package/template-starter/src/lib/components/Image.test.ts +15 -0
- package/template-starter/src/lib/components/Navbar.svelte +141 -0
- package/template-starter/src/lib/components/Search.svelte +248 -0
- package/template-starter/src/lib/components/Sidebar.svelte +110 -0
- package/template-starter/src/lib/components/Tabs.svelte +48 -0
- package/template-starter/src/lib/components/Tabs.test.ts +17 -0
- package/template-starter/src/lib/index.ts +15 -0
- package/template-starter/src/routes/+layout.svelte +28 -0
- package/template-starter/src/routes/+page.svelte +92 -0
- package/template-starter/svelte.config.js +17 -0
- package/template-starter/tailwind.config.ts +17 -0
- package/template-starter/tsconfig.json +13 -0
- package/template-starter/vite.config.ts +6 -0
- package/tests/e2e/example.spec.ts +345 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +6 -0
- package/vitest.config.ts +34 -0
- package/vitest.setup.ts +21 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mergeConfig, validateConfig, DEFAULT_CONFIG } from './config';
|
|
3
|
+
import { parseConfigFile } from './configParser';
|
|
4
|
+
import { createRouteConfig, validateRoute, buildNavLink, isDocsRoute } from './routing';
|
|
5
|
+
import { generateNavigationFromFiles } from './navigationBuilder';
|
|
6
|
+
import { generateCSSVariables, validateTheme, getThemeTemplate } from './themeCustomization';
|
|
7
|
+
|
|
8
|
+
describe('Configuration System Integration', () => {
|
|
9
|
+
describe('Full Config Workflow', () => {
|
|
10
|
+
it('should load and validate a complete configuration', () => {
|
|
11
|
+
const configContent = JSON.stringify({
|
|
12
|
+
name: 'My Documentation',
|
|
13
|
+
docsRoute: '/help',
|
|
14
|
+
theme: {
|
|
15
|
+
primary: '#0066cc',
|
|
16
|
+
secondary: '#ff6b6b',
|
|
17
|
+
},
|
|
18
|
+
navigation: {
|
|
19
|
+
title: 'Help Center',
|
|
20
|
+
autoGenerate: true,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const parseResult = parseConfigFile(configContent, 'config.json');
|
|
25
|
+
expect(parseResult.errors).toHaveLength(0);
|
|
26
|
+
|
|
27
|
+
const validation = validateConfig(parseResult.config);
|
|
28
|
+
expect(validation.valid).toBe(true);
|
|
29
|
+
|
|
30
|
+
const merged = mergeConfig(parseResult.config);
|
|
31
|
+
expect(merged.docsRoute).toBe('/help');
|
|
32
|
+
expect(merged.theme?.primary).toBe('#0066cc');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle config with custom route mount point', () => {
|
|
36
|
+
const config = mergeConfig({
|
|
37
|
+
docsRoute: '/guides',
|
|
38
|
+
docsFolderPath: './guides',
|
|
39
|
+
basePath: '/guides',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(config.docsRoute).toBe('/guides');
|
|
43
|
+
expect(config.basePath).toBe('/guides');
|
|
44
|
+
|
|
45
|
+
// Validate the route
|
|
46
|
+
expect(validateRoute(config.docsRoute!)).toBe(true);
|
|
47
|
+
|
|
48
|
+
// Create route config
|
|
49
|
+
const routeConfig = createRouteConfig(config);
|
|
50
|
+
expect(routeConfig.docsRoute).toBe('/guides');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should build correct navigation links with custom route', () => {
|
|
54
|
+
const config = mergeConfig({ docsRoute: '/help' });
|
|
55
|
+
const docsRoute = config.docsRoute || '/docs';
|
|
56
|
+
|
|
57
|
+
const link1 = buildNavLink('setup', docsRoute);
|
|
58
|
+
expect(link1).toBe('/help/setup');
|
|
59
|
+
|
|
60
|
+
const link2 = buildNavLink('api/overview', docsRoute);
|
|
61
|
+
expect(link2).toBe('/help/api/overview');
|
|
62
|
+
|
|
63
|
+
const link3 = buildNavLink('', docsRoute);
|
|
64
|
+
expect(link3).toBe('/help');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should verify routes with custom mount point', () => {
|
|
68
|
+
const config = mergeConfig({ docsRoute: '/documentation' });
|
|
69
|
+
const docsRoute = config.docsRoute || '/docs';
|
|
70
|
+
|
|
71
|
+
expect(isDocsRoute('/documentation', docsRoute)).toBe(true);
|
|
72
|
+
expect(isDocsRoute('/documentation/api', docsRoute)).toBe(true);
|
|
73
|
+
expect(isDocsRoute('/help', docsRoute)).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('Theme Configuration Integration', () => {
|
|
78
|
+
it('should apply theme configuration from config file', () => {
|
|
79
|
+
const themeConfig = getThemeTemplate('default');
|
|
80
|
+
const cssVars = generateCSSVariables(themeConfig);
|
|
81
|
+
|
|
82
|
+
expect(cssVars['--color-primary']).toBeDefined();
|
|
83
|
+
expect(cssVars['--color-secondary']).toBeDefined();
|
|
84
|
+
expect(cssVars['--font-family-body']).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should validate custom theme config', () => {
|
|
88
|
+
const config = mergeConfig({
|
|
89
|
+
theme: {
|
|
90
|
+
primary: '#ff0000',
|
|
91
|
+
secondary: '#00ff00',
|
|
92
|
+
fontFamily: 'Arial, sans-serif',
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const themeValidation = validateTheme(config.theme!);
|
|
97
|
+
expect(themeValidation.valid).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should override default theme with config', () => {
|
|
101
|
+
const config = mergeConfig({
|
|
102
|
+
theme: {
|
|
103
|
+
primary: '#custom-color',
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(config.theme?.primary).toBe('#custom-color');
|
|
108
|
+
// Should still have default secondary
|
|
109
|
+
expect(config.theme?.secondary).toBe(DEFAULT_CONFIG.theme?.secondary);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should generate CSS for theme customization', () => {
|
|
113
|
+
const darkTheme = getThemeTemplate('dark');
|
|
114
|
+
const cssVars = generateCSSVariables(darkTheme);
|
|
115
|
+
|
|
116
|
+
const stylesheet = Object.entries(cssVars)
|
|
117
|
+
.map(([key, value]) => ` ${key}: ${value};`)
|
|
118
|
+
.join('\n');
|
|
119
|
+
|
|
120
|
+
expect(stylesheet).toContain('--color-primary');
|
|
121
|
+
expect(stylesheet).toContain('--font-family');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Navigation Configuration Integration', () => {
|
|
126
|
+
it('should generate navigation from config', () => {
|
|
127
|
+
const files = [
|
|
128
|
+
{ path: 'docs/index.md', name: 'index', title: 'Home', isIndex: true },
|
|
129
|
+
{ path: 'docs/setup.md', name: 'setup', title: 'Setup', isIndex: false },
|
|
130
|
+
{ path: 'docs/api/overview.md', name: 'overview', title: 'Overview', isIndex: false },
|
|
131
|
+
{ path: 'docs/api/reference.md', name: 'reference', title: 'Reference', isIndex: false },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const nav = generateNavigationFromFiles(files, '/docs');
|
|
135
|
+
expect(nav.length).toBeGreaterThan(0);
|
|
136
|
+
|
|
137
|
+
// Should have an API section
|
|
138
|
+
const apiSection = nav.find((s) => s.title.includes('api') || s.title.includes('API'));
|
|
139
|
+
if (apiSection) {
|
|
140
|
+
expect(apiSection.items.length).toBeGreaterThan(0);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should respect exclude patterns in navigation', () => {
|
|
145
|
+
const configContent = JSON.stringify({
|
|
146
|
+
navigation: {
|
|
147
|
+
excludePaths: ['draft', 'private'],
|
|
148
|
+
autoGenerate: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const parseResult = parseConfigFile(configContent, 'config.json');
|
|
153
|
+
expect(parseResult.errors).toHaveLength(0);
|
|
154
|
+
expect(parseResult.config.navigation?.excludePaths).toEqual(['draft', 'private']);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('Multi-Route Configuration', () => {
|
|
159
|
+
it('should support switching between different doc routes', () => {
|
|
160
|
+
// Project 1: /docs route
|
|
161
|
+
const config1 = mergeConfig({ docsRoute: '/docs' });
|
|
162
|
+
expect(config1.docsRoute).toBe('/docs');
|
|
163
|
+
expect(isDocsRoute('/docs/setup', config1.docsRoute!)).toBe(true);
|
|
164
|
+
expect(isDocsRoute('/help/setup', config1.docsRoute!)).toBe(false);
|
|
165
|
+
|
|
166
|
+
// Project 2: /help route
|
|
167
|
+
const config2 = mergeConfig({ docsRoute: '/help' });
|
|
168
|
+
expect(config2.docsRoute).toBe('/help');
|
|
169
|
+
expect(isDocsRoute('/help/setup', config2.docsRoute!)).toBe(true);
|
|
170
|
+
expect(isDocsRoute('/docs/setup', config2.docsRoute!)).toBe(false);
|
|
171
|
+
|
|
172
|
+
// Project 3: /guides route
|
|
173
|
+
const config3 = mergeConfig({ docsRoute: '/guides' });
|
|
174
|
+
expect(config3.docsRoute).toBe('/guides');
|
|
175
|
+
expect(isDocsRoute('/guides/tutorial', config3.docsRoute!)).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('End-to-End Configuration Scenario', () => {
|
|
180
|
+
it('should handle a complete doc system setup', () => {
|
|
181
|
+
// Simulate loading a config file
|
|
182
|
+
const configJSON = JSON.stringify({
|
|
183
|
+
name: 'API Documentation',
|
|
184
|
+
docsRoute: '/api-docs',
|
|
185
|
+
docsFolderPath: './docs/api',
|
|
186
|
+
enableSearch: true,
|
|
187
|
+
layout: 'full-width',
|
|
188
|
+
theme: {
|
|
189
|
+
primary: '#2563eb',
|
|
190
|
+
secondary: '#dc2626',
|
|
191
|
+
fontFamily: 'Inter, sans-serif',
|
|
192
|
+
headingFont: 'Poppins, sans-serif',
|
|
193
|
+
},
|
|
194
|
+
navigation: {
|
|
195
|
+
title: 'API Reference',
|
|
196
|
+
autoGenerate: true,
|
|
197
|
+
excludePaths: ['examples', 'draft'],
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Parse the config
|
|
202
|
+
const parseResult = parseConfigFile(configJSON, 'config.json');
|
|
203
|
+
expect(parseResult.errors).toHaveLength(0);
|
|
204
|
+
|
|
205
|
+
// Validate it
|
|
206
|
+
const validation = validateConfig(parseResult.config);
|
|
207
|
+
expect(validation.valid).toBe(true);
|
|
208
|
+
|
|
209
|
+
// Merge with defaults
|
|
210
|
+
const finalConfig = mergeConfig(parseResult.config);
|
|
211
|
+
|
|
212
|
+
// Verify all parts work together
|
|
213
|
+
expect(finalConfig.name).toBe('API Documentation');
|
|
214
|
+
expect(finalConfig.docsRoute).toBe('/api-docs');
|
|
215
|
+
expect(finalConfig.enableSearch).toBe(true);
|
|
216
|
+
expect(finalConfig.layout).toBe('full-width');
|
|
217
|
+
|
|
218
|
+
// Verify theme
|
|
219
|
+
const themeValidation = validateTheme(finalConfig.theme!);
|
|
220
|
+
expect(themeValidation.valid).toBe(true);
|
|
221
|
+
|
|
222
|
+
const cssVars = generateCSSVariables(finalConfig.theme!);
|
|
223
|
+
expect(cssVars['--color-primary']).toBe('#2563eb');
|
|
224
|
+
expect(cssVars['--font-family-body']).toBe('Inter, sans-serif');
|
|
225
|
+
|
|
226
|
+
// Verify routing
|
|
227
|
+
const routeConfig = createRouteConfig(finalConfig);
|
|
228
|
+
expect(validateRoute(routeConfig.docsRoute)).toBe(true);
|
|
229
|
+
|
|
230
|
+
// Verify navigation config
|
|
231
|
+
expect(finalConfig.navigation?.title).toBe('API Reference');
|
|
232
|
+
expect(finalConfig.navigation?.excludePaths).toEqual(['examples', 'draft']);
|
|
233
|
+
|
|
234
|
+
// Verify navigation with custom route
|
|
235
|
+
const testLink = buildNavLink('endpoints/users', routeConfig.docsRoute);
|
|
236
|
+
expect(testLink).toBe('/api-docs/endpoints/users');
|
|
237
|
+
|
|
238
|
+
// Verify route detection
|
|
239
|
+
expect(isDocsRoute('/api-docs', routeConfig.docsRoute)).toBe(true);
|
|
240
|
+
expect(isDocsRoute('/api-docs/endpoints', routeConfig.docsRoute)).toBe(true);
|
|
241
|
+
expect(isDocsRoute('/docs', routeConfig.docsRoute)).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('Configuration Validation Scenarios', () => {
|
|
246
|
+
it('should reject invalid configurations', () => {
|
|
247
|
+
const invalidConfigs = [
|
|
248
|
+
{ docsRoute: 123 }, // Not a string
|
|
249
|
+
{ layout: 'invalid-layout' },
|
|
250
|
+
{ defaultTheme: 'blue' }, // Invalid theme value
|
|
251
|
+
{ theme: 'not-an-object' }, // Theme must be object
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
invalidConfigs.forEach((config) => {
|
|
255
|
+
const validation = validateConfig(config);
|
|
256
|
+
expect(validation.valid).toBe(false);
|
|
257
|
+
expect(validation.errors.length).toBeGreaterThan(0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should provide helpful error messages', () => {
|
|
262
|
+
const invalidConfig = {
|
|
263
|
+
layout: 'unknown',
|
|
264
|
+
defaultTheme: 'invalid',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const validation = validateConfig(invalidConfig);
|
|
268
|
+
expect(validation.errors.length).toBeGreaterThan(0);
|
|
269
|
+
expect(validation.errors.some((e) => e.includes('layout') || e.includes('defaultTheme'))).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader and manager
|
|
3
|
+
* Loads, validates, and manages the docs system configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ConfigOptions } from './config';
|
|
7
|
+
import { DEFAULT_CONFIG, mergeConfig, validateConfig } from './config';
|
|
8
|
+
import { parseConfigFile, loadConfigFile, parseFrontmatter } from './configParser';
|
|
9
|
+
import { createRouteConfig, validateRoute } from './routing';
|
|
10
|
+
import { validateTheme } from './themeCustomization';
|
|
11
|
+
import { validateNavigation } from './navigationBuilder';
|
|
12
|
+
|
|
13
|
+
export interface ConfigLoadResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
config: ConfigOptions;
|
|
16
|
+
errors: string[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
source: 'default' | 'loaded' | 'merged';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load configuration from a file
|
|
23
|
+
*/
|
|
24
|
+
export async function loadConfiguration(
|
|
25
|
+
configPath: string = '/docs/_config.json'
|
|
26
|
+
): Promise<ConfigLoadResult> {
|
|
27
|
+
const errors: string[] = [];
|
|
28
|
+
const warnings: string[] = [];
|
|
29
|
+
let userConfig: ConfigOptions = {};
|
|
30
|
+
|
|
31
|
+
// Try to load the config file
|
|
32
|
+
const result = await loadConfigFile(configPath);
|
|
33
|
+
|
|
34
|
+
if (result.errors.length > 0 && !configPath.includes('_config')) {
|
|
35
|
+
// If it's not a standard config file, it might not exist - that's ok
|
|
36
|
+
errors.push(...result.errors);
|
|
37
|
+
} else if (result.errors.length > 0) {
|
|
38
|
+
// For standard config file, log errors but continue with defaults
|
|
39
|
+
warnings.push(...result.errors);
|
|
40
|
+
} else {
|
|
41
|
+
userConfig = result.config;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Validate the loaded config
|
|
45
|
+
const validation = validateConfig(userConfig);
|
|
46
|
+
if (!validation.valid) {
|
|
47
|
+
warnings.push(...validation.errors);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate sub-configs
|
|
51
|
+
if (userConfig.theme) {
|
|
52
|
+
const themeValidation = validateTheme(userConfig.theme);
|
|
53
|
+
if (!themeValidation.valid) {
|
|
54
|
+
warnings.push(`Theme validation errors: ${themeValidation.errors.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (userConfig.navigation) {
|
|
59
|
+
const navValidation = validateNavigation(userConfig.navigation);
|
|
60
|
+
if (!navValidation.valid) {
|
|
61
|
+
warnings.push(`Navigation validation errors: ${navValidation.errors.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate route
|
|
66
|
+
if (userConfig.docsRoute && !validateRoute(userConfig.docsRoute)) {
|
|
67
|
+
errors.push(`Invalid docsRoute: ${userConfig.docsRoute}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Merge with defaults
|
|
71
|
+
const finalConfig = mergeConfig(userConfig, DEFAULT_CONFIG);
|
|
72
|
+
|
|
73
|
+
// Validate final config
|
|
74
|
+
const finalValidation = validateConfig(finalConfig);
|
|
75
|
+
if (!finalValidation.valid) {
|
|
76
|
+
errors.push(...finalValidation.errors);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const success = errors.length === 0;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success,
|
|
83
|
+
config: finalConfig,
|
|
84
|
+
errors,
|
|
85
|
+
warnings,
|
|
86
|
+
source: success ? 'loaded' : warnings.length > 0 ? 'merged' : 'default',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load and cache configuration (for browser environment)
|
|
92
|
+
*/
|
|
93
|
+
let cachedConfig: ConfigOptions | null = null;
|
|
94
|
+
let configLoadPromise: Promise<ConfigLoadResult> | null = null;
|
|
95
|
+
|
|
96
|
+
export async function getConfiguration(configPath: string = '/docs/_config.json'): Promise<ConfigOptions> {
|
|
97
|
+
if (cachedConfig) {
|
|
98
|
+
return cachedConfig;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!configLoadPromise) {
|
|
102
|
+
configLoadPromise = loadConfiguration(configPath);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await configLoadPromise;
|
|
106
|
+
cachedConfig = result.config;
|
|
107
|
+
return result.config;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Clear configuration cache (useful for testing or reloading)
|
|
112
|
+
*/
|
|
113
|
+
export function clearConfigCache(): void {
|
|
114
|
+
cachedConfig = null;
|
|
115
|
+
configLoadPromise = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse frontmatter from markdown content and merge with config
|
|
120
|
+
*/
|
|
121
|
+
export function parseMarkdownFrontmatter(content: string): {
|
|
122
|
+
frontmatter: Record<string, unknown>;
|
|
123
|
+
body: string;
|
|
124
|
+
errors: string[];
|
|
125
|
+
} {
|
|
126
|
+
const result = parseFrontmatter(content);
|
|
127
|
+
|
|
128
|
+
if (result.error) {
|
|
129
|
+
return {
|
|
130
|
+
frontmatter: {},
|
|
131
|
+
body: result.body,
|
|
132
|
+
errors: [result.error],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
frontmatter: result.frontmatter || {},
|
|
138
|
+
body: result.body,
|
|
139
|
+
errors: [],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate a custom config object
|
|
145
|
+
*/
|
|
146
|
+
export function validateCustomConfig(config: unknown): { valid: boolean; errors: string[] } {
|
|
147
|
+
const validation = validateConfig(config);
|
|
148
|
+
|
|
149
|
+
if (!validation.valid) {
|
|
150
|
+
return validation;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const cfg = config as ConfigOptions;
|
|
154
|
+
const errors: string[] = [];
|
|
155
|
+
|
|
156
|
+
// Additional validations
|
|
157
|
+
if (cfg.theme) {
|
|
158
|
+
const themeValidation = validateTheme(cfg.theme);
|
|
159
|
+
if (!themeValidation.valid) {
|
|
160
|
+
errors.push(`Theme: ${themeValidation.errors.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (cfg.navigation) {
|
|
165
|
+
const navValidation = validateNavigation(cfg.navigation);
|
|
166
|
+
if (!navValidation.valid) {
|
|
167
|
+
errors.push(`Navigation: ${navValidation.errors.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (cfg.docsRoute && !validateRoute(cfg.docsRoute)) {
|
|
172
|
+
errors.push(`Invalid docsRoute: ${cfg.docsRoute}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { valid: errors.length === 0, errors };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Create a configuration from minimal user input
|
|
180
|
+
*/
|
|
181
|
+
export function createMinimalConfig(options: Partial<ConfigOptions> = {}): ConfigOptions {
|
|
182
|
+
return mergeConfig(options, DEFAULT_CONFIG);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Export configuration as JSON
|
|
187
|
+
*/
|
|
188
|
+
export function exportConfigAsJSON(config: ConfigOptions): string {
|
|
189
|
+
return JSON.stringify(config, null, 2);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Export configuration as YAML
|
|
194
|
+
*/
|
|
195
|
+
export function exportConfigAsYAML(config: ConfigOptions): string {
|
|
196
|
+
const lines: string[] = [];
|
|
197
|
+
|
|
198
|
+
function addValue(key: string, value: unknown, indent: number = 0): void {
|
|
199
|
+
const indentStr = ' '.repeat(indent);
|
|
200
|
+
|
|
201
|
+
if (value === null || value === undefined) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof value === 'string') {
|
|
206
|
+
lines.push(`${indentStr}${key}: ${value}`);
|
|
207
|
+
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
208
|
+
lines.push(`${indentStr}${key}: ${value}`);
|
|
209
|
+
} else if (Array.isArray(value)) {
|
|
210
|
+
lines.push(`${indentStr}${key}:`);
|
|
211
|
+
value.forEach((item, idx) => {
|
|
212
|
+
if (typeof item === 'object') {
|
|
213
|
+
addValue(`- `, item, indent + 1);
|
|
214
|
+
} else {
|
|
215
|
+
lines.push(`${indentStr} - ${item}`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
} else if (typeof value === 'object') {
|
|
219
|
+
lines.push(`${indentStr}${key}:`);
|
|
220
|
+
Object.entries(value).forEach(([k, v]) => {
|
|
221
|
+
addValue(k, v, indent + 1);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
227
|
+
addValue(key, value);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|