@litodocs/cli 0.5.2 → 0.6.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/src/cli.js +1 -1
- package/src/core/config-sync.js +17 -1
- package/src/core/config-validator.js +229 -0
- package/src/schema/core-schema.json +262 -0
- package/src/schema/template-manifest.json +106 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
package/src/core/config-sync.js
CHANGED
|
@@ -2,6 +2,7 @@ import pkg from 'fs-extra';
|
|
|
2
2
|
const { readFile, writeFile, pathExists } = pkg;
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { readdir } from 'fs/promises';
|
|
5
|
+
import { validateConfig } from './config-validator.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Auto-generates navigation structure from docs folder
|
|
@@ -70,8 +71,11 @@ export async function generateNavigationFromDocs(docsPath) {
|
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Merges user config with auto-generated navigation
|
|
74
|
+
* @param {object} options - Sync options
|
|
75
|
+
* @param {boolean} options.validate - Whether to validate the config (default: true)
|
|
73
76
|
*/
|
|
74
|
-
export async function syncDocsConfig(projectDir, docsPath, userConfigPath) {
|
|
77
|
+
export async function syncDocsConfig(projectDir, docsPath, userConfigPath, options = {}) {
|
|
78
|
+
const { validate = true } = options;
|
|
75
79
|
const configPath = join(projectDir, 'docs-config.json');
|
|
76
80
|
|
|
77
81
|
// Read base config from template
|
|
@@ -80,6 +84,16 @@ export async function syncDocsConfig(projectDir, docsPath, userConfigPath) {
|
|
|
80
84
|
// If user has a custom config, merge it
|
|
81
85
|
if (userConfigPath && await pathExists(userConfigPath)) {
|
|
82
86
|
const userConfig = JSON.parse(await readFile(userConfigPath, 'utf-8'));
|
|
87
|
+
|
|
88
|
+
// Validate user config before merging (only core config is validated)
|
|
89
|
+
if (validate) {
|
|
90
|
+
const validationResult = validateConfig(userConfig, projectDir);
|
|
91
|
+
if (!validationResult.valid) {
|
|
92
|
+
const errorMessages = validationResult.errors.map(e => `${e.path}: ${e.message}`).join('\n ');
|
|
93
|
+
throw new Error(`Configuration validation failed:\n ${errorMessages}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
config = deepMerge(config, userConfig);
|
|
84
98
|
}
|
|
85
99
|
|
|
@@ -90,6 +104,8 @@ export async function syncDocsConfig(projectDir, docsPath, userConfigPath) {
|
|
|
90
104
|
|
|
91
105
|
// Write merged config
|
|
92
106
|
await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
107
|
+
|
|
108
|
+
return { config };
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
function formatLabel(str) {
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates user docs-config.json against the core schema.
|
|
5
|
+
* Core config is strictly validated - extensions are always allowed
|
|
6
|
+
* (templates simply ignore what they don't support).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, existsSync } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import pc from 'picocolors';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
// Core schema defines portable config across all templates
|
|
17
|
+
const CORE_SCHEMA_PATH = join(__dirname, '../schema/core-schema.json');
|
|
18
|
+
|
|
19
|
+
// Core config keys that all templates must support
|
|
20
|
+
const CORE_CONFIG_KEYS = [
|
|
21
|
+
'metadata',
|
|
22
|
+
'branding',
|
|
23
|
+
'navigation',
|
|
24
|
+
'search',
|
|
25
|
+
'seo',
|
|
26
|
+
'i18n',
|
|
27
|
+
'assets'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Extension keys that are template-specific (optional, never cause errors)
|
|
31
|
+
const EXTENSION_KEYS = [
|
|
32
|
+
'footer',
|
|
33
|
+
'theme',
|
|
34
|
+
'landing',
|
|
35
|
+
'integrations',
|
|
36
|
+
'versioning'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load the core schema
|
|
41
|
+
*/
|
|
42
|
+
function loadCoreSchema() {
|
|
43
|
+
if (!existsSync(CORE_SCHEMA_PATH)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return JSON.parse(readFileSync(CORE_SCHEMA_PATH, 'utf-8'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load template manifest from project directory
|
|
51
|
+
*/
|
|
52
|
+
export function loadTemplateManifest(projectDir) {
|
|
53
|
+
const manifestPath = join(projectDir, 'template.json');
|
|
54
|
+
if (!existsSync(manifestPath)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate configuration against core schema (basic validation)
|
|
66
|
+
* Only validates REQUIRED fields and types - extensions are always allowed.
|
|
67
|
+
*/
|
|
68
|
+
function validateCoreConfig(config, schema) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
|
|
71
|
+
// Check required fields
|
|
72
|
+
if (schema.required) {
|
|
73
|
+
for (const field of schema.required) {
|
|
74
|
+
if (!(field in config)) {
|
|
75
|
+
errors.push({
|
|
76
|
+
path: field,
|
|
77
|
+
message: `Required field '${field}' is missing`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check metadata.name is required
|
|
84
|
+
if (config.metadata && !config.metadata.name) {
|
|
85
|
+
errors.push({
|
|
86
|
+
path: 'metadata.name',
|
|
87
|
+
message: "Required field 'metadata.name' is missing"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate types for core fields
|
|
92
|
+
if (config.metadata && typeof config.metadata !== 'object') {
|
|
93
|
+
errors.push({
|
|
94
|
+
path: 'metadata',
|
|
95
|
+
message: "'metadata' must be an object"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (config.navigation?.sidebar && !Array.isArray(config.navigation.sidebar)) {
|
|
100
|
+
errors.push({
|
|
101
|
+
path: 'navigation.sidebar',
|
|
102
|
+
message: "'navigation.sidebar' must be an array"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (config.navigation?.navbar?.links && !Array.isArray(config.navigation.navbar.links)) {
|
|
107
|
+
errors.push({
|
|
108
|
+
path: 'navigation.navbar.links',
|
|
109
|
+
message: "'navigation.navbar.links' must be an array"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate sidebar items structure
|
|
114
|
+
if (config.navigation?.sidebar && Array.isArray(config.navigation.sidebar)) {
|
|
115
|
+
config.navigation.sidebar.forEach((group, i) => {
|
|
116
|
+
if (!group.label) {
|
|
117
|
+
errors.push({
|
|
118
|
+
path: `navigation.sidebar[${i}].label`,
|
|
119
|
+
message: `Sidebar group at index ${i} is missing required 'label' field`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (group.items && !Array.isArray(group.items)) {
|
|
123
|
+
errors.push({
|
|
124
|
+
path: `navigation.sidebar[${i}].items`,
|
|
125
|
+
message: `Sidebar group '${group.label}' items must be an array`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return errors;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate user configuration
|
|
136
|
+
*
|
|
137
|
+
* Only validates core config structure. Extensions are always allowed -
|
|
138
|
+
* templates simply ignore what they don't support.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} config - User's docs-config.json content
|
|
141
|
+
* @param {string} projectDir - Path to the project directory
|
|
142
|
+
* @param {object} options - Validation options
|
|
143
|
+
* @param {boolean} options.silent - If true, don't print anything
|
|
144
|
+
* @returns {{ valid: boolean, errors: Array }}
|
|
145
|
+
*/
|
|
146
|
+
export function validateConfig(config, projectDir, options = {}) {
|
|
147
|
+
const { silent = false } = options;
|
|
148
|
+
|
|
149
|
+
const coreSchema = loadCoreSchema();
|
|
150
|
+
|
|
151
|
+
// Only validate core config - extensions are always allowed
|
|
152
|
+
const coreErrors = coreSchema ? validateCoreConfig(config, coreSchema) : [];
|
|
153
|
+
|
|
154
|
+
const result = {
|
|
155
|
+
valid: coreErrors.length === 0,
|
|
156
|
+
errors: coreErrors,
|
|
157
|
+
manifest: loadTemplateManifest(projectDir)
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (!silent && coreErrors.length > 0) {
|
|
161
|
+
printValidationErrors(coreErrors);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Print validation errors to console
|
|
169
|
+
*/
|
|
170
|
+
function printValidationErrors(errors) {
|
|
171
|
+
console.log(pc.red('\n✗ Configuration validation failed:\n'));
|
|
172
|
+
for (const error of errors) {
|
|
173
|
+
console.log(pc.red(` • ${error.path}: ${error.message}`));
|
|
174
|
+
}
|
|
175
|
+
console.log('');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get list of portable (core) config keys
|
|
180
|
+
*/
|
|
181
|
+
export function getCoreConfigKeys() {
|
|
182
|
+
return [...CORE_CONFIG_KEYS];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get list of extension config keys
|
|
187
|
+
*/
|
|
188
|
+
export function getExtensionKeys() {
|
|
189
|
+
return [...EXTENSION_KEYS];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract only core config from a full config object
|
|
194
|
+
*/
|
|
195
|
+
export function extractCoreConfig(config) {
|
|
196
|
+
const coreConfig = {};
|
|
197
|
+
for (const key of CORE_CONFIG_KEYS) {
|
|
198
|
+
if (key in config) {
|
|
199
|
+
coreConfig[key] = config[key];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return coreConfig;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract only extension config from a full config object
|
|
207
|
+
*/
|
|
208
|
+
export function extractExtensionConfig(config) {
|
|
209
|
+
const extensionConfig = {};
|
|
210
|
+
for (const key of EXTENSION_KEYS) {
|
|
211
|
+
if (key in config) {
|
|
212
|
+
extensionConfig[key] = config[key];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return extensionConfig;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if config is portable (uses only core config)
|
|
220
|
+
* Useful for users who want to ensure their config works with any template.
|
|
221
|
+
*/
|
|
222
|
+
export function isPortableConfig(config) {
|
|
223
|
+
for (const key of EXTENSION_KEYS) {
|
|
224
|
+
if (key in config) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
@@ -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
|
+
}
|