themebooth 0.2.0 → 0.3.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/CHANGELOG.md +57 -0
- package/README.md +1 -0
- package/dist/bin/themebooth.js +31 -1
- package/dist/bin/themebooth.js.map +1 -1
- package/dist/cli/validate.d.ts +7 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +689 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/core/colors.d.ts +11 -0
- package/dist/core/colors.d.ts.map +1 -0
- package/dist/core/colors.js +214 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +3 -3
- package/dist/core/schemas.js.map +1 -1
- package/dist/preview/renderer.d.ts.map +1 -1
- package/dist/preview/renderer.js +578 -117
- package/dist/preview/renderer.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateCommand = validateCommand;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logger_1 = require("../utils/logger");
|
|
40
|
+
const manifest_1 = require("../core/manifest");
|
|
41
|
+
const validation_1 = require("../utils/validation");
|
|
42
|
+
const colors_1 = require("../core/colors");
|
|
43
|
+
const schemas_1 = require("../core/schemas");
|
|
44
|
+
const vscode_1 = require("../exporters/vscode");
|
|
45
|
+
const zed_1 = require("../exporters/zed");
|
|
46
|
+
const notepad_plus_1 = require("../exporters/notepad-plus");
|
|
47
|
+
async function validateCommand(manifestPath, options = {}) {
|
|
48
|
+
const resolvedPath = path.resolve(manifestPath || "./manifest.json");
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
51
|
+
const errors = [];
|
|
52
|
+
const warnings = [];
|
|
53
|
+
const conversions = [];
|
|
54
|
+
let manifest;
|
|
55
|
+
// Parse JSON
|
|
56
|
+
const parseResult = (0, validation_1.parseManifestJSON)(content);
|
|
57
|
+
if (!parseResult.success) {
|
|
58
|
+
if (options.ci) {
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
valid: false,
|
|
61
|
+
errors: [parseResult.error],
|
|
62
|
+
warnings: [],
|
|
63
|
+
stats: getStats(undefined),
|
|
64
|
+
}, null, 2));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
logger_1.logger.error("manifest.json invalid (1 error)");
|
|
68
|
+
console.log(`\n ERROR [line ${parseResult.error.line}, col ${parseResult.error.column}]: ${parseResult.error.message}`);
|
|
69
|
+
if (parseResult.error.suggestion) {
|
|
70
|
+
console.log(` ${parseResult.error.suggestion}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Pre-validate and normalize colors before schema validation
|
|
76
|
+
const preValidationData = JSON.parse(JSON.stringify(parseResult.data));
|
|
77
|
+
normalizeColorsInManifest(preValidationData, conversions);
|
|
78
|
+
// Validate schema
|
|
79
|
+
const schemaValidation = (0, manifest_1.validateManifest)(preValidationData);
|
|
80
|
+
if (!schemaValidation.success) {
|
|
81
|
+
schemaValidation.errors.forEach((err) => {
|
|
82
|
+
errors.push({
|
|
83
|
+
severity: "error",
|
|
84
|
+
field: err.field || "unknown",
|
|
85
|
+
message: err.message,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
manifest = schemaValidation.data;
|
|
91
|
+
// Validate colors with conversion
|
|
92
|
+
const colorErrors = validateColors(manifest, conversions);
|
|
93
|
+
errors.push(...colorErrors.errors);
|
|
94
|
+
warnings.push(...colorErrors.warnings);
|
|
95
|
+
// Validate variables
|
|
96
|
+
const varErrors = validateVariables(manifest);
|
|
97
|
+
errors.push(...varErrors.errors);
|
|
98
|
+
warnings.push(...varErrors.warnings);
|
|
99
|
+
// Validate computed colors
|
|
100
|
+
const computedErrors = validateComputedColors(manifest);
|
|
101
|
+
errors.push(...computedErrors);
|
|
102
|
+
// Validate tokens
|
|
103
|
+
const tokenErrors = validateTokens(manifest);
|
|
104
|
+
errors.push(...tokenErrors);
|
|
105
|
+
// Validate semantic tokens
|
|
106
|
+
const semanticErrors = validateSemanticTokens(manifest);
|
|
107
|
+
errors.push(...semanticErrors);
|
|
108
|
+
// Validate language tokens
|
|
109
|
+
const languageErrors = validateLanguageTokens(manifest);
|
|
110
|
+
errors.push(...languageErrors);
|
|
111
|
+
// Validate presets
|
|
112
|
+
const presetErrors = validatePresets(manifest);
|
|
113
|
+
errors.push(...presetErrors);
|
|
114
|
+
// Validate extends
|
|
115
|
+
if (manifest.extends) {
|
|
116
|
+
const extendsErrors = await validateExtends(manifest.extends, resolvedPath);
|
|
117
|
+
errors.push(...extendsErrors);
|
|
118
|
+
}
|
|
119
|
+
// Dry-run exporter validation
|
|
120
|
+
const exporterErrors = validateExporters(manifest);
|
|
121
|
+
errors.push(...exporterErrors);
|
|
122
|
+
}
|
|
123
|
+
// Generate output
|
|
124
|
+
if (options.ci) {
|
|
125
|
+
const output = {
|
|
126
|
+
valid: errors.length === 0,
|
|
127
|
+
errors,
|
|
128
|
+
warnings,
|
|
129
|
+
stats: getStats(manifest),
|
|
130
|
+
};
|
|
131
|
+
if (conversions.length > 0) {
|
|
132
|
+
output.conversions = conversions;
|
|
133
|
+
}
|
|
134
|
+
console.log(JSON.stringify(output, null, 2));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
printValidationResult(errors, warnings, manifest, conversions, options.fix);
|
|
138
|
+
}
|
|
139
|
+
// Apply fixes if requested
|
|
140
|
+
if (options.fix && conversions.length > 0) {
|
|
141
|
+
// preValidationData already has conversions applied
|
|
142
|
+
await fs.writeFile(resolvedPath, JSON.stringify(preValidationData, null, 2) + "\n");
|
|
143
|
+
if (!options.ci) {
|
|
144
|
+
logger_1.logger.success(`manifest.json fixed (${conversions.length} conversions)`);
|
|
145
|
+
conversions.forEach((c) => {
|
|
146
|
+
console.log(` • ${c.field}: ${c.from} → ${c.to}`);
|
|
147
|
+
});
|
|
148
|
+
console.log(` Written back to manifest.json`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Exit with appropriate code
|
|
152
|
+
if (errors.length > 0) {
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
else if (warnings.length > 0) {
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (options.ci) {
|
|
161
|
+
console.log(JSON.stringify({
|
|
162
|
+
valid: false,
|
|
163
|
+
errors: [
|
|
164
|
+
{
|
|
165
|
+
severity: "error",
|
|
166
|
+
field: "manifest.json",
|
|
167
|
+
message: `Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
warnings: [],
|
|
171
|
+
stats: getStats(undefined),
|
|
172
|
+
}, null, 2));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
logger_1.logger.error(`Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`);
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function validateColors(manifest, conversions) {
|
|
181
|
+
const errors = [];
|
|
182
|
+
const warnings = [];
|
|
183
|
+
if (!manifest.colors)
|
|
184
|
+
return { errors, warnings };
|
|
185
|
+
Object.entries(manifest.colors).forEach(([key, value]) => {
|
|
186
|
+
if (value === null)
|
|
187
|
+
return;
|
|
188
|
+
// Skip variable references
|
|
189
|
+
if (value.startsWith("$")) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Try to parse and normalize
|
|
193
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
194
|
+
if (result) {
|
|
195
|
+
if (result.changes.length > 0) {
|
|
196
|
+
conversions.push({
|
|
197
|
+
line: 0, // TODO: get actual line number from source
|
|
198
|
+
field: `colors.${key}`,
|
|
199
|
+
from: value,
|
|
200
|
+
to: result.hex,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const format = (0, colors_1.validateColorFormat)(value);
|
|
206
|
+
if (!format.valid) {
|
|
207
|
+
errors.push({
|
|
208
|
+
severity: "error",
|
|
209
|
+
field: `colors.${key}`,
|
|
210
|
+
message: `Invalid color format "${value}"`,
|
|
211
|
+
suggestion: format.suggestion || "Use #RRGGBB, rgb(r,g,b), or $variableName",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return { errors, warnings };
|
|
217
|
+
}
|
|
218
|
+
function validateVariables(manifest) {
|
|
219
|
+
const errors = [];
|
|
220
|
+
const warnings = [];
|
|
221
|
+
if (!manifest.variables)
|
|
222
|
+
return { errors, warnings };
|
|
223
|
+
// Check for circular dependencies
|
|
224
|
+
const visited = new Set();
|
|
225
|
+
const recStack = new Set();
|
|
226
|
+
const hasCycle = (varName) => {
|
|
227
|
+
visited.add(varName);
|
|
228
|
+
recStack.add(varName);
|
|
229
|
+
const refs = (0, schemas_1.extractVariableReferences)(String(manifest.variables[varName]));
|
|
230
|
+
for (const ref of refs) {
|
|
231
|
+
if (!visited.has(ref)) {
|
|
232
|
+
if (hasCycle(ref)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (recStack.has(ref)) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
recStack.delete(varName);
|
|
241
|
+
return false;
|
|
242
|
+
};
|
|
243
|
+
for (const varName of Object.keys(manifest.variables)) {
|
|
244
|
+
visited.clear();
|
|
245
|
+
recStack.clear();
|
|
246
|
+
if (hasCycle(varName)) {
|
|
247
|
+
errors.push({
|
|
248
|
+
severity: "error",
|
|
249
|
+
field: `variables.${varName}`,
|
|
250
|
+
message: `Circular variable dependency detected`,
|
|
251
|
+
suggestion: "Break the reference cycle in your variable definitions",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Check for unused variables
|
|
256
|
+
const usedVars = new Set();
|
|
257
|
+
// Collect all references from colors
|
|
258
|
+
if (manifest.colors) {
|
|
259
|
+
Object.values(manifest.colors).forEach((val) => {
|
|
260
|
+
if (val) {
|
|
261
|
+
(0, schemas_1.extractVariableReferences)(String(val)).forEach((v) => usedVars.add(v));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
// Collect from tokens
|
|
266
|
+
if (manifest.tokens) {
|
|
267
|
+
Object.values(manifest.tokens).forEach((settings) => {
|
|
268
|
+
Object.values(settings).forEach((val) => {
|
|
269
|
+
(0, schemas_1.extractVariableReferences)(String(val)).forEach((v) => usedVars.add(v));
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// Collect from semantic tokens
|
|
274
|
+
if (manifest.semanticTokens) {
|
|
275
|
+
Object.values(manifest.semanticTokens).forEach((settings) => {
|
|
276
|
+
Object.values(settings).forEach((val) => {
|
|
277
|
+
(0, schemas_1.extractVariableReferences)(String(val)).forEach((v) => usedVars.add(v));
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// Collect from language tokens
|
|
282
|
+
if (manifest.languageTokens) {
|
|
283
|
+
Object.values(manifest.languageTokens).forEach((langTokens) => {
|
|
284
|
+
Object.values(langTokens).forEach((settings) => {
|
|
285
|
+
Object.values(settings).forEach((val) => {
|
|
286
|
+
(0, schemas_1.extractVariableReferences)(String(val)).forEach((v) => usedVars.add(v));
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Collect from presets
|
|
292
|
+
if (manifest.presets) {
|
|
293
|
+
Object.values(manifest.presets).forEach((preset) => {
|
|
294
|
+
if (preset.variableOverrides) {
|
|
295
|
+
Object.values(preset.variableOverrides).forEach((val) => {
|
|
296
|
+
(0, schemas_1.extractVariableReferences)(String(val)).forEach((v) => usedVars.add(v));
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// Check unused
|
|
302
|
+
for (const varName of Object.keys(manifest.variables)) {
|
|
303
|
+
if (!usedVars.has(varName)) {
|
|
304
|
+
warnings.push({
|
|
305
|
+
severity: "warning",
|
|
306
|
+
field: `variables.${varName}`,
|
|
307
|
+
message: `Unused variable $${varName}`,
|
|
308
|
+
suggestion: "Safe to remove if not needed",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return { errors, warnings };
|
|
313
|
+
}
|
|
314
|
+
function validateComputedColors(manifest) {
|
|
315
|
+
const errors = [];
|
|
316
|
+
if (!manifest.computed)
|
|
317
|
+
return errors;
|
|
318
|
+
Object.entries(manifest.computed).forEach(([name, entry]) => {
|
|
319
|
+
// Validate base
|
|
320
|
+
if (entry.base.startsWith("$")) {
|
|
321
|
+
if (!manifest.variables?.[entry.base.slice(1)]) {
|
|
322
|
+
errors.push({
|
|
323
|
+
severity: "error",
|
|
324
|
+
field: `computed.${name}`,
|
|
325
|
+
message: `Base variable ${entry.base} is undefined`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (!(0, colors_1.parseAndNormalizeColor)(entry.base)) {
|
|
330
|
+
errors.push({
|
|
331
|
+
severity: "error",
|
|
332
|
+
field: `computed.${name}`,
|
|
333
|
+
message: `Base color ${entry.base} has invalid format`,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Validate transform
|
|
337
|
+
if (!["darken", "lighten", "alpha"].includes(entry.transform)) {
|
|
338
|
+
errors.push({
|
|
339
|
+
severity: "error",
|
|
340
|
+
field: `computed.${name}`,
|
|
341
|
+
message: `Invalid transform "${entry.transform}". Use: darken, lighten, alpha`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Validate amount
|
|
345
|
+
if (entry.amount < 0 || entry.amount > 100) {
|
|
346
|
+
errors.push({
|
|
347
|
+
severity: "error",
|
|
348
|
+
field: `computed.${name}`,
|
|
349
|
+
message: `Amount must be 0-100, got ${entry.amount}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
return errors;
|
|
354
|
+
}
|
|
355
|
+
function validateTokens(manifest) {
|
|
356
|
+
const errors = [];
|
|
357
|
+
if (!manifest.tokens)
|
|
358
|
+
return errors;
|
|
359
|
+
Object.entries(manifest.tokens).forEach(([scope, settings]) => {
|
|
360
|
+
validateTokenProperties(scope, settings, "tokens", errors);
|
|
361
|
+
});
|
|
362
|
+
return errors;
|
|
363
|
+
}
|
|
364
|
+
function validateSemanticTokens(manifest) {
|
|
365
|
+
const errors = [];
|
|
366
|
+
if (!manifest.semanticTokens)
|
|
367
|
+
return errors;
|
|
368
|
+
Object.entries(manifest.semanticTokens).forEach(([scope, settings]) => {
|
|
369
|
+
validateTokenProperties(scope, settings, "semanticTokens", errors);
|
|
370
|
+
});
|
|
371
|
+
return errors;
|
|
372
|
+
}
|
|
373
|
+
function validateLanguageTokens(manifest) {
|
|
374
|
+
const errors = [];
|
|
375
|
+
if (!manifest.languageTokens)
|
|
376
|
+
return errors;
|
|
377
|
+
Object.entries(manifest.languageTokens).forEach(([language, langTokens]) => {
|
|
378
|
+
Object.entries(langTokens).forEach(([scope, settings]) => {
|
|
379
|
+
validateTokenProperties(scope, settings, `languageTokens.${language}`, errors);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
return errors;
|
|
383
|
+
}
|
|
384
|
+
function validateTokenProperties(scope, settings, prefix, errors) {
|
|
385
|
+
Object.entries(settings).forEach(([prop, value]) => {
|
|
386
|
+
const strVal = String(value);
|
|
387
|
+
// Check foreground/background colors
|
|
388
|
+
if ((prop === "foreground" || prop === "background") && !strVal.startsWith("$")) {
|
|
389
|
+
const result = (0, colors_1.parseAndNormalizeColor)(strVal);
|
|
390
|
+
if (!result) {
|
|
391
|
+
errors.push({
|
|
392
|
+
severity: "error",
|
|
393
|
+
field: `${prefix}.${scope}.${prop}`,
|
|
394
|
+
message: `Invalid color format "${strVal}"`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Check fontStyle - can be single or space-separated multiple
|
|
399
|
+
if (prop === "fontStyle" && strVal !== "") {
|
|
400
|
+
const styles = strVal.split(/\s+/);
|
|
401
|
+
const invalid = styles.filter((s) => !["bold", "italic", "underline"].includes(s));
|
|
402
|
+
if (invalid.length > 0) {
|
|
403
|
+
errors.push({
|
|
404
|
+
severity: "error",
|
|
405
|
+
field: `${prefix}.${scope}.${prop}`,
|
|
406
|
+
message: `Invalid fontStyle "${strVal}". Use: bold, italic, underline (can combine)`,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Check fontWeight
|
|
411
|
+
if (prop === "fontWeight") {
|
|
412
|
+
const weight = parseInt(strVal, 10);
|
|
413
|
+
if (!["normal", "bold"].includes(strVal) &&
|
|
414
|
+
(isNaN(weight) || weight < 100 || weight > 900 || weight % 100 !== 0)) {
|
|
415
|
+
errors.push({
|
|
416
|
+
severity: "error",
|
|
417
|
+
field: `${prefix}.${scope}.${prop}`,
|
|
418
|
+
message: `Invalid fontWeight "${strVal}". Use: normal, bold, or 100-900`,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Check opacity
|
|
423
|
+
if (prop === "opacity") {
|
|
424
|
+
const opacity = parseFloat(strVal);
|
|
425
|
+
if (isNaN(opacity) || opacity < 0 || opacity > 1) {
|
|
426
|
+
errors.push({
|
|
427
|
+
severity: "error",
|
|
428
|
+
field: `${prefix}.${scope}.${prop}`,
|
|
429
|
+
message: `Opacity must be 0-1, got ${strVal}`,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function validatePresets(manifest) {
|
|
436
|
+
const errors = [];
|
|
437
|
+
if (!manifest.presets)
|
|
438
|
+
return errors;
|
|
439
|
+
Object.entries(manifest.presets).forEach(([presetName, preset]) => {
|
|
440
|
+
// Validate variable overrides reference defined variables
|
|
441
|
+
if (preset.variableOverrides) {
|
|
442
|
+
Object.keys(preset.variableOverrides).forEach((varName) => {
|
|
443
|
+
if (!manifest.variables?.[varName]) {
|
|
444
|
+
errors.push({
|
|
445
|
+
severity: "error",
|
|
446
|
+
field: `presets.${presetName}`,
|
|
447
|
+
message: `Preset references undefined variable $${varName}`,
|
|
448
|
+
suggestion: `Define $${varName} in variables section`,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
return errors;
|
|
455
|
+
}
|
|
456
|
+
async function validateExtends(extendsPath, manifestPath) {
|
|
457
|
+
const errors = [];
|
|
458
|
+
const resolvedExtends = path.resolve(path.dirname(manifestPath), extendsPath);
|
|
459
|
+
try {
|
|
460
|
+
await fs.stat(resolvedExtends);
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
errors.push({
|
|
464
|
+
severity: "error",
|
|
465
|
+
field: "extends",
|
|
466
|
+
message: `Extends file not found: ${extendsPath}`,
|
|
467
|
+
suggestion: `Check the path relative to manifest location`,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return errors;
|
|
471
|
+
}
|
|
472
|
+
function applyColorConversions(data, conversions) {
|
|
473
|
+
const result = JSON.parse(JSON.stringify(data));
|
|
474
|
+
conversions.forEach((conv) => {
|
|
475
|
+
const parts = conv.field.split(".");
|
|
476
|
+
let obj = result;
|
|
477
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
478
|
+
obj = obj[parts[i]];
|
|
479
|
+
}
|
|
480
|
+
obj[parts[parts.length - 1]] = conv.to;
|
|
481
|
+
});
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
function getStats(manifest) {
|
|
485
|
+
return {
|
|
486
|
+
variables: Object.keys(manifest?.variables || {}).length,
|
|
487
|
+
colors: Object.keys(manifest?.colors || {}).length,
|
|
488
|
+
tokens: Object.keys(manifest?.tokens || {}).length,
|
|
489
|
+
semanticTokens: Object.keys(manifest?.semanticTokens || {}).length,
|
|
490
|
+
languageTokens: Object.keys(manifest?.languageTokens || {}).length,
|
|
491
|
+
presets: Object.keys(manifest?.presets || {}).length,
|
|
492
|
+
extends: !!manifest?.extends,
|
|
493
|
+
computed: Object.keys(manifest?.computed || {}).length,
|
|
494
|
+
colorConversions: 0,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function normalizeColorsInManifest(data, conversions) {
|
|
498
|
+
// Normalize variables
|
|
499
|
+
if (data.variables) {
|
|
500
|
+
Object.entries(data.variables).forEach(([key, value]) => {
|
|
501
|
+
if (typeof value === "string" && !value.startsWith("$")) {
|
|
502
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
503
|
+
if (result && result.changes.length > 0) {
|
|
504
|
+
conversions.push({
|
|
505
|
+
line: 0,
|
|
506
|
+
field: `variables.${key}`,
|
|
507
|
+
from: value,
|
|
508
|
+
to: result.hex,
|
|
509
|
+
});
|
|
510
|
+
data.variables[key] = result.hex;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// Normalize colors
|
|
516
|
+
if (data.colors) {
|
|
517
|
+
Object.entries(data.colors).forEach(([key, value]) => {
|
|
518
|
+
if (value !== null && typeof value === "string" && !value.startsWith("$")) {
|
|
519
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
520
|
+
if (result && result.changes.length > 0) {
|
|
521
|
+
conversions.push({
|
|
522
|
+
line: 0,
|
|
523
|
+
field: `colors.${key}`,
|
|
524
|
+
from: value,
|
|
525
|
+
to: result.hex,
|
|
526
|
+
});
|
|
527
|
+
data.colors[key] = result.hex;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
// Normalize tokens
|
|
533
|
+
if (data.tokens) {
|
|
534
|
+
Object.entries(data.tokens).forEach(([scope, settings]) => {
|
|
535
|
+
Object.entries(settings).forEach(([prop, value]) => {
|
|
536
|
+
if (typeof value === "string" &&
|
|
537
|
+
(prop === "foreground" || prop === "background") &&
|
|
538
|
+
!value.startsWith("$")) {
|
|
539
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
540
|
+
if (result && result.changes.length > 0) {
|
|
541
|
+
conversions.push({
|
|
542
|
+
line: 0,
|
|
543
|
+
field: `tokens.${scope}.${prop}`,
|
|
544
|
+
from: value,
|
|
545
|
+
to: result.hex,
|
|
546
|
+
});
|
|
547
|
+
data.tokens[scope][prop] = result.hex;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// Normalize semanticTokens
|
|
554
|
+
if (data.semanticTokens) {
|
|
555
|
+
Object.entries(data.semanticTokens).forEach(([scope, settings]) => {
|
|
556
|
+
Object.entries(settings).forEach(([prop, value]) => {
|
|
557
|
+
if (typeof value === "string" &&
|
|
558
|
+
(prop === "foreground" || prop === "background") &&
|
|
559
|
+
!value.startsWith("$")) {
|
|
560
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
561
|
+
if (result && result.changes.length > 0) {
|
|
562
|
+
conversions.push({
|
|
563
|
+
line: 0,
|
|
564
|
+
field: `semanticTokens.${scope}.${prop}`,
|
|
565
|
+
from: value,
|
|
566
|
+
to: result.hex,
|
|
567
|
+
});
|
|
568
|
+
data.semanticTokens[scope][prop] = result.hex;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
// Normalize languageTokens
|
|
575
|
+
if (data.languageTokens) {
|
|
576
|
+
Object.entries(data.languageTokens).forEach(([lang, langTokens]) => {
|
|
577
|
+
Object.entries(langTokens).forEach(([scope, settings]) => {
|
|
578
|
+
Object.entries(settings).forEach(([prop, value]) => {
|
|
579
|
+
if (typeof value === "string" &&
|
|
580
|
+
(prop === "foreground" || prop === "background") &&
|
|
581
|
+
!value.startsWith("$")) {
|
|
582
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
583
|
+
if (result && result.changes.length > 0) {
|
|
584
|
+
conversions.push({
|
|
585
|
+
line: 0,
|
|
586
|
+
field: `languageTokens.${lang}.${scope}.${prop}`,
|
|
587
|
+
from: value,
|
|
588
|
+
to: result.hex,
|
|
589
|
+
});
|
|
590
|
+
data.languageTokens[lang][scope][prop] = result.hex;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
// Normalize presets
|
|
598
|
+
if (data.presets) {
|
|
599
|
+
Object.entries(data.presets).forEach(([presetName, preset]) => {
|
|
600
|
+
if (preset.variableOverrides) {
|
|
601
|
+
Object.entries(preset.variableOverrides).forEach(([varName, value]) => {
|
|
602
|
+
if (typeof value === "string" && !value.startsWith("$")) {
|
|
603
|
+
const result = (0, colors_1.parseAndNormalizeColor)(value);
|
|
604
|
+
if (result && result.changes.length > 0) {
|
|
605
|
+
conversions.push({
|
|
606
|
+
line: 0,
|
|
607
|
+
field: `presets.${presetName}.variableOverrides.${varName}`,
|
|
608
|
+
from: value,
|
|
609
|
+
to: result.hex,
|
|
610
|
+
});
|
|
611
|
+
data.presets[presetName].variableOverrides[varName] = result.hex;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function validateExporters(manifest) {
|
|
620
|
+
const errors = [];
|
|
621
|
+
try {
|
|
622
|
+
(0, vscode_1.exportVSCode)(manifest);
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
errors.push({
|
|
626
|
+
severity: "error",
|
|
627
|
+
field: "exporters.vscode",
|
|
628
|
+
message: `VS Code export failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
(0, zed_1.exportZed)(manifest);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
errors.push({
|
|
636
|
+
severity: "error",
|
|
637
|
+
field: "exporters.zed",
|
|
638
|
+
message: `Zed export failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
(0, notepad_plus_1.exportNotepadPlus)(manifest);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
errors.push({
|
|
646
|
+
severity: "error",
|
|
647
|
+
field: "exporters.notepad++",
|
|
648
|
+
message: `Notepad++ export failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return errors;
|
|
652
|
+
}
|
|
653
|
+
function printValidationResult(errors, warnings, manifest, conversions, isFixing) {
|
|
654
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
655
|
+
const stats = getStats(manifest);
|
|
656
|
+
logger_1.logger.success("manifest.json valid");
|
|
657
|
+
console.log(` • ${stats.variables} variables, ${stats.colors} colors, ${stats.tokens} tokens`);
|
|
658
|
+
if (stats.presets > 0)
|
|
659
|
+
console.log(` • ${stats.presets} presets`);
|
|
660
|
+
if (stats.computed > 0)
|
|
661
|
+
console.log(` • ${stats.computed} computed colors`);
|
|
662
|
+
if (conversions.length > 0) {
|
|
663
|
+
console.log(` • All colors converted: ${conversions.map((c) => `${c.from} → ${c.to}`).join(", ")}`);
|
|
664
|
+
}
|
|
665
|
+
console.log(` • No issues`);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const errorCount = errors.length;
|
|
669
|
+
const warningCount = warnings.length;
|
|
670
|
+
logger_1.logger.error(`manifest.json invalid (${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""})`);
|
|
671
|
+
console.log("");
|
|
672
|
+
errors.forEach((err) => {
|
|
673
|
+
console.log(` ERROR [${err.field}]: ${err.message}`);
|
|
674
|
+
if (err.suggestion) {
|
|
675
|
+
console.log(` ${err.suggestion}`);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
if (warningCount > 0) {
|
|
679
|
+
console.log("");
|
|
680
|
+
}
|
|
681
|
+
warnings.forEach((warn) => {
|
|
682
|
+
console.log(` WARNING [${warn.field}]: ${warn.message}`);
|
|
683
|
+
if (warn.suggestion) {
|
|
684
|
+
console.log(` ${warn.suggestion}`);
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
//# sourceMappingURL=validate.js.map
|