@litodocs/cli 0.5.2 → 0.7.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/README.md +325 -124
- package/lito-manifest.schema.json +118 -0
- package/package.json +1 -1
- package/src/cli.js +49 -1
- package/src/commands/build.js +26 -17
- package/src/commands/dev.js +17 -12
- package/src/commands/doctor.js +311 -0
- package/src/commands/info.js +192 -0
- package/src/commands/init.js +284 -0
- package/src/commands/preview.js +98 -0
- package/src/commands/validate.js +124 -0
- package/src/core/config-sync.js +17 -1
- package/src/core/config-validator.js +229 -0
- package/src/core/config.js +62 -18
- package/src/core/framework-runner.js +208 -0
- package/src/core/landing-sync.js +708 -0
- package/src/core/output.js +10 -4
- package/src/core/sync.js +141 -15
- package/src/core/template-registry.js +8 -5
- package/src/schema/core-schema.json +262 -0
- package/src/schema/template-manifest.json +106 -0
package/src/core/output.js
CHANGED
|
@@ -2,12 +2,18 @@ import pkg from 'fs-extra';
|
|
|
2
2
|
const { copy, ensureDir } = pkg;
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Copy built output to the specified output path
|
|
7
|
+
* @param {string} projectDir - The scaffolded project directory
|
|
8
|
+
* @param {string} outputPath - User's desired output path
|
|
9
|
+
* @param {string} buildOutputDir - Framework's build output directory (default: 'dist')
|
|
10
|
+
*/
|
|
11
|
+
export async function copyOutput(projectDir, outputPath, buildOutputDir = 'dist') {
|
|
12
|
+
const distPath = join(projectDir, buildOutputDir);
|
|
13
|
+
|
|
8
14
|
// Ensure output directory exists
|
|
9
15
|
await ensureDir(outputPath);
|
|
10
|
-
|
|
16
|
+
|
|
11
17
|
// Copy built files to output
|
|
12
18
|
await copy(distPath, outputPath, {
|
|
13
19
|
overwrite: true,
|
package/src/core/sync.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import pkg from 'fs-extra';
|
|
2
2
|
const { copy, ensureDir, remove, readFile, writeFile, pathExists } = pkg;
|
|
3
3
|
import { join, relative, sep } from 'path';
|
|
4
|
-
import { readdir, stat } from 'fs/promises';
|
|
4
|
+
import { readdir, stat, utimes } from 'fs/promises';
|
|
5
|
+
import { syncLanding } from './landing-sync.js';
|
|
5
6
|
|
|
6
7
|
// Known locale codes (ISO 639-1)
|
|
7
8
|
const KNOWN_LOCALES = [
|
|
@@ -12,7 +13,7 @@ const KNOWN_LOCALES = [
|
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
// Special folders that are not content
|
|
15
|
-
const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', 'public'];
|
|
16
|
+
const SPECIAL_FOLDERS = ['_assets', '_css', '_images', '_static', '_landing', 'public'];
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Get i18n configuration from docs-config.json
|
|
@@ -30,6 +31,21 @@ async function getI18nConfig(sourcePath) {
|
|
|
30
31
|
return { defaultLocale: 'en', locales: ['en'] };
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Get full docs-config.json
|
|
36
|
+
*/
|
|
37
|
+
async function getDocsConfig(sourcePath) {
|
|
38
|
+
const configPath = join(sourcePath, 'docs-config.json');
|
|
39
|
+
if (await pathExists(configPath)) {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(await readFile(configPath, 'utf-8'));
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
/**
|
|
34
50
|
* Get versioning configuration from docs-config.json
|
|
35
51
|
*/
|
|
@@ -97,8 +113,17 @@ async function detectVersionFolders(sourcePath, versioningConfig) {
|
|
|
97
113
|
return versionFolders;
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Sync documentation files to the project
|
|
118
|
+
* @param {string} sourcePath - User's docs directory
|
|
119
|
+
* @param {string} projectDir - Scaffolded project directory
|
|
120
|
+
* @param {object} frameworkConfig - Framework configuration (optional, defaults to Astro)
|
|
121
|
+
*/
|
|
122
|
+
export async function syncDocs(sourcePath, projectDir, frameworkConfig = null) {
|
|
123
|
+
// Default to Astro's src/pages for backward compatibility
|
|
124
|
+
const contentDir = frameworkConfig?.contentDir || 'src/pages';
|
|
125
|
+
const targetPath = join(projectDir, contentDir);
|
|
126
|
+
const shouldInjectLayout = frameworkConfig?.layoutInjection !== false;
|
|
102
127
|
|
|
103
128
|
// Clear existing pages (except index if it exists in template)
|
|
104
129
|
await ensureDir(targetPath);
|
|
@@ -159,8 +184,10 @@ export async function syncDocs(sourcePath, projectDir) {
|
|
|
159
184
|
},
|
|
160
185
|
});
|
|
161
186
|
|
|
162
|
-
// Inject layout into version markdown files
|
|
163
|
-
|
|
187
|
+
// Inject layout into version markdown files (only for frameworks that need it)
|
|
188
|
+
if (shouldInjectLayout) {
|
|
189
|
+
await injectLayoutIntoMarkdown(versionTargetPath, targetPath, null, [], version, frameworkConfig);
|
|
190
|
+
}
|
|
164
191
|
}));
|
|
165
192
|
|
|
166
193
|
// Sync locale folders
|
|
@@ -177,12 +204,16 @@ export async function syncDocs(sourcePath, projectDir) {
|
|
|
177
204
|
},
|
|
178
205
|
});
|
|
179
206
|
|
|
180
|
-
// Inject layout into locale markdown files
|
|
181
|
-
|
|
207
|
+
// Inject layout into locale markdown files (only for frameworks that need it)
|
|
208
|
+
if (shouldInjectLayout) {
|
|
209
|
+
await injectLayoutIntoMarkdown(localeTargetPath, targetPath, locale, [], null, frameworkConfig);
|
|
210
|
+
}
|
|
182
211
|
}));
|
|
183
212
|
|
|
184
|
-
// Inject layout into default locale markdown files
|
|
185
|
-
|
|
213
|
+
// Inject layout into default locale markdown files (only for frameworks that need it)
|
|
214
|
+
if (shouldInjectLayout) {
|
|
215
|
+
await injectLayoutIntoMarkdown(targetPath, targetPath, null, excludeFolders, null, frameworkConfig);
|
|
216
|
+
}
|
|
186
217
|
|
|
187
218
|
// Check for custom landing page conflict
|
|
188
219
|
const hasUserIndex = ['index.md', 'index.mdx'].some(file =>
|
|
@@ -198,6 +229,18 @@ export async function syncDocs(sourcePath, projectDir) {
|
|
|
198
229
|
|
|
199
230
|
// Sync user assets (images, css, static files)
|
|
200
231
|
await syncUserAssets(sourcePath, projectDir);
|
|
232
|
+
|
|
233
|
+
// Sync landing page (custom HTML/CSS/JS or sections)
|
|
234
|
+
const docsConfig = await getDocsConfig(sourcePath);
|
|
235
|
+
if (docsConfig.landing) {
|
|
236
|
+
await syncLanding(sourcePath, projectDir, frameworkConfig, docsConfig);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Trigger HMR for non-Astro frameworks (Vite-based)
|
|
240
|
+
// Vite doesn't automatically watch content directories, so we need to trigger a reload
|
|
241
|
+
if (frameworkConfig && frameworkConfig.name !== 'astro') {
|
|
242
|
+
await triggerHMR(projectDir, frameworkConfig);
|
|
243
|
+
}
|
|
201
244
|
}
|
|
202
245
|
|
|
203
246
|
/**
|
|
@@ -283,7 +326,27 @@ async function syncUserAssets(sourcePath, projectDir) {
|
|
|
283
326
|
const customImport = '@import "./custom.css";';
|
|
284
327
|
|
|
285
328
|
if (!globalCss.includes(customImport)) {
|
|
286
|
-
|
|
329
|
+
// @import must come at the very top of the file (before @tailwind directives)
|
|
330
|
+
// Only @charset can precede @import
|
|
331
|
+
const lines = globalCss.split('\n');
|
|
332
|
+
let insertIndex = 0;
|
|
333
|
+
|
|
334
|
+
// Find position after @charset if it exists (only thing allowed before @import)
|
|
335
|
+
for (let i = 0; i < lines.length; i++) {
|
|
336
|
+
const trimmed = lines[i].trim();
|
|
337
|
+
if (trimmed.startsWith('@charset')) {
|
|
338
|
+
insertIndex = i + 1;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
// Stop at first non-empty, non-comment line
|
|
342
|
+
if (trimmed && !trimmed.startsWith('/*') && !trimmed.startsWith('*') && !trimmed.startsWith('//')) {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Insert the import at the top (after @charset if present)
|
|
348
|
+
lines.splice(insertIndex, 0, '/* User custom styles */', customImport, '');
|
|
349
|
+
globalCss = lines.join('\n');
|
|
287
350
|
await writeFile(globalCssPath, globalCss, 'utf-8');
|
|
288
351
|
}
|
|
289
352
|
}
|
|
@@ -299,10 +362,15 @@ async function syncUserAssets(sourcePath, projectDir) {
|
|
|
299
362
|
* @param {string|null} locale - Current locale (null for default)
|
|
300
363
|
* @param {string[]} skipFolders - Folders to skip (locale folders when processing root)
|
|
301
364
|
* @param {string|null} version - Current version (null for non-versioned)
|
|
365
|
+
* @param {object|null} frameworkConfig - Framework configuration for layout paths
|
|
302
366
|
*/
|
|
303
|
-
async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders = [], version = null) {
|
|
367
|
+
async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders = [], version = null, frameworkConfig = null) {
|
|
304
368
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
305
369
|
|
|
370
|
+
// Get layout paths from framework config or use defaults
|
|
371
|
+
const baseLayoutPath = frameworkConfig?.layoutPath || 'layouts/MarkdownLayout.astro';
|
|
372
|
+
const apiLayoutPath = frameworkConfig?.apiLayoutPath || 'layouts/APILayout.astro';
|
|
373
|
+
|
|
306
374
|
await Promise.all(entries.map(async (entry) => {
|
|
307
375
|
const fullPath = join(dir, entry.name);
|
|
308
376
|
|
|
@@ -311,14 +379,14 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
|
|
|
311
379
|
if (skipFolders.includes(entry.name)) {
|
|
312
380
|
return;
|
|
313
381
|
}
|
|
314
|
-
await injectLayoutIntoMarkdown(fullPath, rootDir, locale, [], version);
|
|
382
|
+
await injectLayoutIntoMarkdown(fullPath, rootDir, locale, [], version, frameworkConfig);
|
|
315
383
|
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
|
|
316
384
|
let content = await readFile(fullPath, 'utf-8');
|
|
317
385
|
|
|
318
386
|
// Calculate relative path to layout
|
|
319
387
|
const relPath = relative(rootDir, fullPath);
|
|
320
388
|
const depth = relPath.split(sep).length - 1;
|
|
321
|
-
const layoutPath = '../'.repeat(depth + 1) +
|
|
389
|
+
const layoutPath = '../'.repeat(depth + 1) + baseLayoutPath;
|
|
322
390
|
|
|
323
391
|
// Check if file already has frontmatter
|
|
324
392
|
if (content.startsWith('---')) {
|
|
@@ -330,7 +398,7 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
|
|
|
330
398
|
// Determine which layout to use
|
|
331
399
|
let targetLayout = layoutPath;
|
|
332
400
|
if (frontmatterBlock.includes('api:')) {
|
|
333
|
-
targetLayout = '../'.repeat(depth + 1) +
|
|
401
|
+
targetLayout = '../'.repeat(depth + 1) + apiLayoutPath;
|
|
334
402
|
}
|
|
335
403
|
|
|
336
404
|
// Add locale to frontmatter if present
|
|
@@ -362,3 +430,61 @@ async function injectLayoutIntoMarkdown(dir, rootDir, locale = null, skipFolders
|
|
|
362
430
|
}
|
|
363
431
|
}));
|
|
364
432
|
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Trigger HMR for Vite-based frameworks
|
|
436
|
+
* Vite watches certain files for changes - we touch a trigger file to force a reload
|
|
437
|
+
* @param {string} projectDir - Project directory
|
|
438
|
+
* @param {object} frameworkConfig - Framework configuration
|
|
439
|
+
*/
|
|
440
|
+
async function triggerHMR(projectDir, frameworkConfig) {
|
|
441
|
+
// Use framework-specific HMR trigger file if defined
|
|
442
|
+
const triggerFilePath = frameworkConfig.hmrTriggerFile
|
|
443
|
+
? join(projectDir, frameworkConfig.hmrTriggerFile)
|
|
444
|
+
: join(projectDir, 'src', '.lito-hmr-trigger.js');
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
// Ensure parent directory exists
|
|
448
|
+
await ensureDir(join(projectDir, 'src'));
|
|
449
|
+
|
|
450
|
+
// Write a timestamp to the trigger file
|
|
451
|
+
// This file should be imported in the app's entry point
|
|
452
|
+
const timestamp = Date.now();
|
|
453
|
+
const content = `// Auto-generated by Lito CLI for HMR triggering
|
|
454
|
+
// This file is updated when docs content changes
|
|
455
|
+
// Import this file in your app's entry point to enable content hot reload
|
|
456
|
+
export const LITO_CONTENT_VERSION = ${timestamp};
|
|
457
|
+
export const LITO_LAST_UPDATE = "${new Date().toISOString()}";
|
|
458
|
+
|
|
459
|
+
// Force Vite to treat this as a module that triggers HMR
|
|
460
|
+
if (import.meta.hot) {
|
|
461
|
+
import.meta.hot.accept();
|
|
462
|
+
}
|
|
463
|
+
`;
|
|
464
|
+
|
|
465
|
+
await writeFile(triggerFilePath, content, 'utf-8');
|
|
466
|
+
} catch {
|
|
467
|
+
// Fallback: Touch the main entry file or vite config
|
|
468
|
+
const fallbackFiles = [
|
|
469
|
+
join(projectDir, 'src', 'main.jsx'),
|
|
470
|
+
join(projectDir, 'src', 'main.tsx'),
|
|
471
|
+
join(projectDir, 'src', 'App.jsx'),
|
|
472
|
+
join(projectDir, 'src', 'App.tsx'),
|
|
473
|
+
join(projectDir, 'src', 'index.jsx'),
|
|
474
|
+
join(projectDir, 'src', 'index.tsx'),
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
for (const file of fallbackFiles) {
|
|
478
|
+
if (await pathExists(file)) {
|
|
479
|
+
try {
|
|
480
|
+
// Touch the file by updating its mtime
|
|
481
|
+
const now = new Date();
|
|
482
|
+
await utimes(file, now, now);
|
|
483
|
+
break;
|
|
484
|
+
} catch {
|
|
485
|
+
// Continue to next file
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export const TEMPLATE_REGISTRY = {
|
|
12
|
-
// Default template
|
|
13
|
-
'default': 'github:Lito-docs/template',
|
|
12
|
+
// Default template - Astro-based
|
|
13
|
+
'default': 'github:Lito-docs/lito-astro-template',
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Framework-specific templates
|
|
16
|
+
'astro': 'github:Lito-docs/lito-astro-template',
|
|
17
|
+
'react': 'github:Lito-docs/lito-react-template',
|
|
18
|
+
'next': 'github:Lito-docs/lito-next-template',
|
|
19
|
+
'vue': 'github:Lito-docs/lito-vue-template',
|
|
20
|
+
'nuxt': 'github:Lito-docs/lito-nuxt-template',
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://lito.dev/schemas/core-config.json",
|
|
4
|
+
"title": "Lito Core Configuration Schema",
|
|
5
|
+
"description": "This schema defines the REQUIRED configuration that ALL Lito templates must support. Users can swap templates without changing any of these options.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["metadata"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"metadata": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"description": "Site metadata - REQUIRED by all templates",
|
|
12
|
+
"properties": {
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "The name of your documentation site"
|
|
16
|
+
},
|
|
17
|
+
"description": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "A brief description of your documentation"
|
|
20
|
+
},
|
|
21
|
+
"url": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"format": "uri",
|
|
24
|
+
"description": "The production URL of your documentation site"
|
|
25
|
+
},
|
|
26
|
+
"version": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "The version of your documentation"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": ["name"]
|
|
32
|
+
},
|
|
33
|
+
"branding": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"description": "Core branding options - supported by all templates",
|
|
36
|
+
"properties": {
|
|
37
|
+
"logo": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"description": "Logo configuration",
|
|
40
|
+
"properties": {
|
|
41
|
+
"light": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Path to logo for light mode"
|
|
44
|
+
},
|
|
45
|
+
"dark": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Path to logo for dark mode"
|
|
48
|
+
},
|
|
49
|
+
"href": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Link when clicking the logo"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"favicon": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Path to favicon"
|
|
58
|
+
},
|
|
59
|
+
"colors": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"description": "Core color configuration",
|
|
62
|
+
"properties": {
|
|
63
|
+
"primary": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Primary brand color (hex, rgb, or color name)"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"navigation": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"description": "Navigation structure - REQUIRED by all templates",
|
|
74
|
+
"properties": {
|
|
75
|
+
"navbar": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "Top navigation bar configuration",
|
|
78
|
+
"properties": {
|
|
79
|
+
"links": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"description": "Navigation links in the header",
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"label": { "type": "string" },
|
|
86
|
+
"href": { "type": "string" }
|
|
87
|
+
},
|
|
88
|
+
"required": ["label", "href"]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"cta": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"description": "Call-to-action button",
|
|
94
|
+
"properties": {
|
|
95
|
+
"label": { "type": "string" },
|
|
96
|
+
"href": { "type": "string" }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"sidebar": {
|
|
102
|
+
"type": "array",
|
|
103
|
+
"description": "Sidebar navigation groups",
|
|
104
|
+
"items": {
|
|
105
|
+
"$ref": "#/$defs/sidebarGroup"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"search": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"description": "Search configuration - supported by all templates",
|
|
113
|
+
"properties": {
|
|
114
|
+
"enabled": {
|
|
115
|
+
"type": "boolean",
|
|
116
|
+
"default": true,
|
|
117
|
+
"description": "Enable search functionality"
|
|
118
|
+
},
|
|
119
|
+
"provider": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"enum": ["local", "algolia"],
|
|
122
|
+
"default": "local",
|
|
123
|
+
"description": "Search provider"
|
|
124
|
+
},
|
|
125
|
+
"placeholder": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"default": "Search docs...",
|
|
128
|
+
"description": "Search input placeholder text"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"seo": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"description": "SEO configuration - supported by all templates",
|
|
135
|
+
"properties": {
|
|
136
|
+
"ogImage": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"description": "Default Open Graph image URL"
|
|
139
|
+
},
|
|
140
|
+
"twitterHandle": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "Twitter handle (e.g., @username)"
|
|
143
|
+
},
|
|
144
|
+
"defaultAuthor": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"description": "Default author name"
|
|
147
|
+
},
|
|
148
|
+
"defaultKeywords": {
|
|
149
|
+
"type": "array",
|
|
150
|
+
"items": { "type": "string" },
|
|
151
|
+
"description": "Default keywords for all pages"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"i18n": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"description": "Internationalization - supported by all templates",
|
|
158
|
+
"properties": {
|
|
159
|
+
"defaultLocale": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"default": "en",
|
|
162
|
+
"description": "Default locale"
|
|
163
|
+
},
|
|
164
|
+
"locales": {
|
|
165
|
+
"type": "array",
|
|
166
|
+
"items": { "type": "string" },
|
|
167
|
+
"default": ["en"],
|
|
168
|
+
"description": "Supported locales"
|
|
169
|
+
},
|
|
170
|
+
"routing": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {
|
|
173
|
+
"prefixDefaultLocale": {
|
|
174
|
+
"type": "boolean",
|
|
175
|
+
"default": false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"assets": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"description": "Asset folder configuration - supported by all templates",
|
|
184
|
+
"properties": {
|
|
185
|
+
"images": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"default": "_images",
|
|
188
|
+
"description": "Images folder name"
|
|
189
|
+
},
|
|
190
|
+
"css": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"default": "_css",
|
|
193
|
+
"description": "CSS folder name"
|
|
194
|
+
},
|
|
195
|
+
"static": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"default": "_assets",
|
|
198
|
+
"description": "Static assets folder name"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"$defs": {
|
|
204
|
+
"sidebarGroup": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"description": "A sidebar navigation group",
|
|
207
|
+
"properties": {
|
|
208
|
+
"label": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Group heading"
|
|
211
|
+
},
|
|
212
|
+
"icon": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Icon name (Iconify format)"
|
|
215
|
+
},
|
|
216
|
+
"items": {
|
|
217
|
+
"type": "array",
|
|
218
|
+
"description": "Navigation items in this group",
|
|
219
|
+
"items": {
|
|
220
|
+
"$ref": "#/$defs/sidebarItem"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
"required": ["label", "items"]
|
|
225
|
+
},
|
|
226
|
+
"sidebarItem": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"description": "A sidebar navigation item",
|
|
229
|
+
"properties": {
|
|
230
|
+
"label": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"description": "Link text"
|
|
233
|
+
},
|
|
234
|
+
"slug": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"description": "Page slug (relative path without extension)"
|
|
237
|
+
},
|
|
238
|
+
"href": {
|
|
239
|
+
"type": "string",
|
|
240
|
+
"description": "External URL (use instead of slug for external links)"
|
|
241
|
+
},
|
|
242
|
+
"icon": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"description": "Icon name (Iconify format)"
|
|
245
|
+
},
|
|
246
|
+
"method": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
249
|
+
"description": "HTTP method for API endpoints"
|
|
250
|
+
},
|
|
251
|
+
"items": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"description": "Nested items for sub-groups",
|
|
254
|
+
"items": {
|
|
255
|
+
"$ref": "#/$defs/sidebarItem"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"required": ["label"]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://lito.dev/schemas/template-manifest.json",
|
|
4
|
+
"title": "Lito Template Manifest",
|
|
5
|
+
"description": "Defines a template's capabilities and supported configuration extensions",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name", "version", "coreSchemaVersion"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Template name"
|
|
12
|
+
},
|
|
13
|
+
"version": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Template version (semver)"
|
|
16
|
+
},
|
|
17
|
+
"description": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Brief description of the template"
|
|
20
|
+
},
|
|
21
|
+
"author": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Template author"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Template repository URL"
|
|
28
|
+
},
|
|
29
|
+
"coreSchemaVersion": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The core schema version this template implements (e.g., '1.0.0')",
|
|
32
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
33
|
+
},
|
|
34
|
+
"extensions": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "Theme-specific configuration extensions supported by this template",
|
|
37
|
+
"properties": {
|
|
38
|
+
"footer": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"description": "Extended footer configuration support",
|
|
41
|
+
"properties": {
|
|
42
|
+
"enabled": { "type": "boolean", "default": true },
|
|
43
|
+
"layouts": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": { "type": "string" },
|
|
46
|
+
"description": "Supported footer layouts"
|
|
47
|
+
},
|
|
48
|
+
"socials": { "type": "boolean", "default": true },
|
|
49
|
+
"linkSections": { "type": "boolean", "default": true }
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"theme": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"description": "Extended theme options",
|
|
55
|
+
"properties": {
|
|
56
|
+
"modes": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"items": { "type": "string", "enum": ["light", "dark", "auto"] },
|
|
59
|
+
"description": "Supported theme modes"
|
|
60
|
+
},
|
|
61
|
+
"customColors": { "type": "boolean", "default": false },
|
|
62
|
+
"customFonts": { "type": "boolean", "default": false }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"landing": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"description": "Landing page support",
|
|
68
|
+
"properties": {
|
|
69
|
+
"enabled": { "type": "boolean", "default": false },
|
|
70
|
+
"hero": { "type": "boolean", "default": false },
|
|
71
|
+
"features": { "type": "boolean", "default": false }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"integrations": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"description": "Third-party integration support",
|
|
77
|
+
"properties": {
|
|
78
|
+
"analytics": {
|
|
79
|
+
"type": "array",
|
|
80
|
+
"items": { "type": "string" },
|
|
81
|
+
"description": "Supported analytics providers"
|
|
82
|
+
},
|
|
83
|
+
"feedback": { "type": "boolean", "default": false },
|
|
84
|
+
"copyPage": { "type": "boolean", "default": false }
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"versioning": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"description": "Documentation versioning support",
|
|
90
|
+
"properties": {
|
|
91
|
+
"enabled": { "type": "boolean", "default": false },
|
|
92
|
+
"versionBanner": { "type": "boolean", "default": false }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"additionalProperties": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"description": "Custom extensions defined by the template"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"extensionSchema": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Path to JSON schema for template-specific extensions (relative to template root)"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|