@constela/cli 0.3.30 → 0.4.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/dist/index.js +1019 -28
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -4,9 +4,213 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/compile.ts
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { validatePass, analyzePass, transformPass } from "@constela/compiler";
|
|
8
|
+
import { ConstelaError } from "@constela/core";
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, watch as fsWatch } from "fs";
|
|
9
10
|
import { dirname, basename, join } from "path";
|
|
11
|
+
function shouldUseColors() {
|
|
12
|
+
if (process.env["NO_COLOR"] !== void 0) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (process.env["FORCE_COLOR"] === "1") {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return process.stdout.isTTY === true;
|
|
19
|
+
}
|
|
20
|
+
var colors = {
|
|
21
|
+
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
22
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
23
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
24
|
+
reset: "\x1B[0m"
|
|
25
|
+
};
|
|
26
|
+
function getColors() {
|
|
27
|
+
if (shouldUseColors()) {
|
|
28
|
+
return colors;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
red: (s) => s,
|
|
32
|
+
green: (s) => s,
|
|
33
|
+
yellow: (s) => s,
|
|
34
|
+
reset: ""
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function countViewNodes(node) {
|
|
38
|
+
let count = 1;
|
|
39
|
+
switch (node.kind) {
|
|
40
|
+
case "element":
|
|
41
|
+
if (node.children) {
|
|
42
|
+
for (const child of node.children) {
|
|
43
|
+
count += countViewNodes(child);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case "if":
|
|
48
|
+
count += countViewNodes(node.then);
|
|
49
|
+
if (node.else) {
|
|
50
|
+
count += countViewNodes(node.else);
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case "each":
|
|
54
|
+
count += countViewNodes(node.template);
|
|
55
|
+
break;
|
|
56
|
+
// text, markdown, code, slot nodes have no children
|
|
57
|
+
default:
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
function createVerboseLogger(enabled, c) {
|
|
63
|
+
if (!enabled) {
|
|
64
|
+
return {
|
|
65
|
+
phaseStart: () => {
|
|
66
|
+
},
|
|
67
|
+
phaseOk: () => {
|
|
68
|
+
},
|
|
69
|
+
phaseFail: () => {
|
|
70
|
+
},
|
|
71
|
+
detail: () => {
|
|
72
|
+
},
|
|
73
|
+
summary: () => {
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
phaseStart(phase, total, message) {
|
|
79
|
+
process.stdout.write(`[${phase}/${total}] ${message}...`);
|
|
80
|
+
},
|
|
81
|
+
phaseOk(phase, total, message, durationMs) {
|
|
82
|
+
console.log(`[${phase}/${total}] ${message}... ${c.green("OK")} (${durationMs}ms)`);
|
|
83
|
+
},
|
|
84
|
+
phaseFail(phase, total, message) {
|
|
85
|
+
console.log(`[${phase}/${total}] ${message}... ${c.red("FAILED")}`);
|
|
86
|
+
},
|
|
87
|
+
detail(message) {
|
|
88
|
+
console.log(` ${message}`);
|
|
89
|
+
},
|
|
90
|
+
summary(states, actions, viewNodes, totalMs) {
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log("Summary:");
|
|
93
|
+
console.log(` States: ${states}`);
|
|
94
|
+
console.log(` Actions: ${actions}`);
|
|
95
|
+
console.log(` View nodes: ${viewNodes}`);
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(`${c.green("Compilation successful")} (${totalMs}ms total)`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createDebugLogger(enabled) {
|
|
102
|
+
if (!enabled) {
|
|
103
|
+
return {
|
|
104
|
+
inputFile: () => {
|
|
105
|
+
},
|
|
106
|
+
parseTime: () => {
|
|
107
|
+
},
|
|
108
|
+
validatePass: () => {
|
|
109
|
+
},
|
|
110
|
+
analyzePass: () => {
|
|
111
|
+
},
|
|
112
|
+
transformPass: () => {
|
|
113
|
+
},
|
|
114
|
+
getInfo: () => void 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const info = {
|
|
118
|
+
inputFile: "",
|
|
119
|
+
inputSize: 0,
|
|
120
|
+
parseTime: 0,
|
|
121
|
+
validateTime: 0,
|
|
122
|
+
analyzeTime: 0,
|
|
123
|
+
transformTime: 0,
|
|
124
|
+
nodesValidated: 0,
|
|
125
|
+
stateCount: 0,
|
|
126
|
+
actionCount: 0,
|
|
127
|
+
viewNodeCount: 0,
|
|
128
|
+
outputSize: 0
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
inputFile(filename, size) {
|
|
132
|
+
info.inputFile = filename;
|
|
133
|
+
info.inputSize = size;
|
|
134
|
+
console.log(`[DEBUG] Input file: ${filename} (${size} bytes)`);
|
|
135
|
+
},
|
|
136
|
+
parseTime(ms) {
|
|
137
|
+
info.parseTime = ms;
|
|
138
|
+
console.log(`[DEBUG] Parse time: ${ms}ms`);
|
|
139
|
+
},
|
|
140
|
+
validatePass(nodes, ms) {
|
|
141
|
+
info.nodesValidated = nodes;
|
|
142
|
+
info.validateTime = ms;
|
|
143
|
+
console.log(`[DEBUG] Validate pass: ${nodes} nodes validated (${ms}ms)`);
|
|
144
|
+
},
|
|
145
|
+
analyzePass(states, actions, views, ms) {
|
|
146
|
+
info.stateCount = states;
|
|
147
|
+
info.actionCount = actions;
|
|
148
|
+
info.viewNodeCount = views;
|
|
149
|
+
info.analyzeTime = ms;
|
|
150
|
+
console.log(`[DEBUG] Analyze pass: ${states} states, ${actions} actions, ${views} views (${ms}ms)`);
|
|
151
|
+
},
|
|
152
|
+
transformPass(outputSize, ms) {
|
|
153
|
+
info.outputSize = outputSize;
|
|
154
|
+
info.transformTime = ms;
|
|
155
|
+
console.log(`[DEBUG] Transform pass: output size ${outputSize} bytes (${ms}ms)`);
|
|
156
|
+
},
|
|
157
|
+
getInfo() {
|
|
158
|
+
return info;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function buildDebugInfo(inputFile, inputSize, parseTime, validateTime, analyzeTime, transformTime, nodesValidated, stateCount, actionCount, viewNodeCount, outputSize) {
|
|
163
|
+
return {
|
|
164
|
+
inputFile,
|
|
165
|
+
inputSize,
|
|
166
|
+
parseTime,
|
|
167
|
+
validateTime,
|
|
168
|
+
analyzeTime,
|
|
169
|
+
transformTime,
|
|
170
|
+
nodesValidated,
|
|
171
|
+
stateCount,
|
|
172
|
+
actionCount,
|
|
173
|
+
viewNodeCount,
|
|
174
|
+
outputSize
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function countAstNodes(ast) {
|
|
178
|
+
let count = 1;
|
|
179
|
+
count += Object.keys(ast.state).length;
|
|
180
|
+
for (const action of ast.actions) {
|
|
181
|
+
count += 1;
|
|
182
|
+
count += action.steps.length;
|
|
183
|
+
}
|
|
184
|
+
count += countViewNodesFromAst(ast.view);
|
|
185
|
+
if (ast.components) {
|
|
186
|
+
for (const key of Object.keys(ast.components)) {
|
|
187
|
+
const comp = ast.components[key];
|
|
188
|
+
if (comp) {
|
|
189
|
+
count += 1;
|
|
190
|
+
count += countViewNodesFromAst(comp.view);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return count;
|
|
195
|
+
}
|
|
196
|
+
function countViewNodesFromAst(node) {
|
|
197
|
+
let count = 1;
|
|
198
|
+
if ("children" in node && Array.isArray(node.children)) {
|
|
199
|
+
for (const child of node.children) {
|
|
200
|
+
count += countViewNodesFromAst(child);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if ("then" in node && node.then) {
|
|
204
|
+
count += countViewNodesFromAst(node.then);
|
|
205
|
+
}
|
|
206
|
+
if ("else" in node && node.else) {
|
|
207
|
+
count += countViewNodesFromAst(node.else);
|
|
208
|
+
}
|
|
209
|
+
if ("template" in node && node.template) {
|
|
210
|
+
count += countViewNodesFromAst(node.template);
|
|
211
|
+
}
|
|
212
|
+
return count;
|
|
213
|
+
}
|
|
10
214
|
function getOutputPath(inputPath, options) {
|
|
11
215
|
if (options.out) {
|
|
12
216
|
return options.out;
|
|
@@ -19,64 +223,849 @@ function getOutputPath(inputPath, options) {
|
|
|
19
223
|
}
|
|
20
224
|
return join(dir, `${base}.compiled.json`);
|
|
21
225
|
}
|
|
22
|
-
|
|
226
|
+
function formatColoredError(error, c) {
|
|
227
|
+
const lines = [];
|
|
228
|
+
const pathInfo = error.path ? ` at ${error.path}` : "";
|
|
229
|
+
lines.push(`Error [${c.red(error.code)}]: ${error.message}${pathInfo}`);
|
|
230
|
+
if (error.suggestion) {
|
|
231
|
+
lines.push(` ${c.yellow(`Did you mean: ${error.suggestion}`)}`);
|
|
232
|
+
}
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
function errorToJson(error) {
|
|
236
|
+
const result = {
|
|
237
|
+
code: error.code,
|
|
238
|
+
message: error.message
|
|
239
|
+
};
|
|
240
|
+
if (error.path !== void 0) {
|
|
241
|
+
result.path = error.path;
|
|
242
|
+
}
|
|
243
|
+
if (error.suggestion !== void 0) {
|
|
244
|
+
result.suggestion = error.suggestion;
|
|
245
|
+
}
|
|
246
|
+
if (error.context !== void 0) {
|
|
247
|
+
result.context = error.context;
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function outputJson(output) {
|
|
252
|
+
console.log(JSON.stringify(output));
|
|
253
|
+
}
|
|
254
|
+
function performCompilation(inputPath, options, c) {
|
|
255
|
+
const startTime = performance.now();
|
|
256
|
+
const verbose = createVerboseLogger(options.verbose === true, c);
|
|
257
|
+
const isJsonMode = options.json === true;
|
|
258
|
+
const debug = createDebugLogger(options.debug === true && !isJsonMode);
|
|
259
|
+
let inputSize;
|
|
23
260
|
try {
|
|
24
261
|
const stat = statSync(inputPath);
|
|
25
262
|
if (!stat.isFile()) {
|
|
26
|
-
|
|
27
|
-
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
errors: [new ConstelaError("SCHEMA_INVALID", `Input path is not a regular file: ${inputPath}`)],
|
|
266
|
+
duration: Math.round(performance.now() - startTime)
|
|
267
|
+
};
|
|
28
268
|
}
|
|
269
|
+
inputSize = stat.size;
|
|
29
270
|
} catch (err) {
|
|
30
271
|
const error = err;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
272
|
+
const message = error.code === "ENOENT" ? `File not found: ${inputPath}` : `Could not access file: ${inputPath} - ${error.message}`;
|
|
273
|
+
return {
|
|
274
|
+
ok: false,
|
|
275
|
+
errors: [new ConstelaError("SCHEMA_INVALID", message)],
|
|
276
|
+
duration: Math.round(performance.now() - startTime)
|
|
277
|
+
};
|
|
37
278
|
}
|
|
279
|
+
debug.inputFile(basename(inputPath), inputSize);
|
|
38
280
|
let fileContent;
|
|
39
281
|
try {
|
|
40
282
|
fileContent = readFileSync(inputPath, "utf-8");
|
|
41
283
|
} catch (err) {
|
|
42
284
|
const error = err;
|
|
43
|
-
|
|
44
|
-
|
|
285
|
+
return {
|
|
286
|
+
ok: false,
|
|
287
|
+
errors: [new ConstelaError("SCHEMA_INVALID", `Could not read file: ${inputPath} - ${error.message}`)],
|
|
288
|
+
duration: Math.round(performance.now() - startTime)
|
|
289
|
+
};
|
|
45
290
|
}
|
|
291
|
+
const parseStart = performance.now();
|
|
46
292
|
let inputJson;
|
|
47
293
|
try {
|
|
48
294
|
inputJson = JSON.parse(fileContent);
|
|
49
295
|
} catch (err) {
|
|
50
296
|
const error = err;
|
|
51
|
-
|
|
52
|
-
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
errors: [new ConstelaError("SCHEMA_INVALID", `Invalid JSON: ${error.message}`)],
|
|
300
|
+
duration: Math.round(performance.now() - startTime)
|
|
301
|
+
};
|
|
53
302
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
303
|
+
const parseDuration = Math.round(performance.now() - parseStart);
|
|
304
|
+
debug.parseTime(parseDuration);
|
|
305
|
+
const phase1Start = performance.now();
|
|
306
|
+
const validateResult = validatePass(inputJson);
|
|
307
|
+
const phase1Duration = Math.round(performance.now() - phase1Start);
|
|
308
|
+
if (!validateResult.ok) {
|
|
309
|
+
verbose.phaseFail(1, 3, "Validating schema");
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
errors: [validateResult.error],
|
|
313
|
+
duration: Math.round(performance.now() - startTime)
|
|
314
|
+
};
|
|
61
315
|
}
|
|
316
|
+
verbose.phaseOk(1, 3, "Validating schema", phase1Duration);
|
|
317
|
+
const ast = validateResult.ast;
|
|
318
|
+
const nodesValidated = countAstNodes(ast);
|
|
319
|
+
debug.validatePass(nodesValidated, phase1Duration);
|
|
320
|
+
const phase2Start = performance.now();
|
|
321
|
+
const stateNames = Object.keys(ast.state);
|
|
322
|
+
const actionNames = ast.actions.map((a) => a.name);
|
|
323
|
+
verbose.detail(`Collecting state names: ${stateNames.join(", ")}`);
|
|
324
|
+
verbose.detail(`Collecting action names: ${actionNames.join(", ")}`);
|
|
325
|
+
verbose.detail("Validating view tree");
|
|
326
|
+
const analyzeResult = analyzePass(ast);
|
|
327
|
+
const phase2Duration = Math.round(performance.now() - phase2Start);
|
|
328
|
+
if (!analyzeResult.ok) {
|
|
329
|
+
verbose.phaseFail(2, 3, "Analyzing semantics");
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
errors: analyzeResult.errors,
|
|
333
|
+
duration: Math.round(performance.now() - startTime)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
verbose.phaseOk(2, 3, "Analyzing semantics", phase2Duration);
|
|
337
|
+
const phase3Start = performance.now();
|
|
338
|
+
const program2 = transformPass(analyzeResult.ast, analyzeResult.context);
|
|
339
|
+
const phase3Duration = Math.round(performance.now() - phase3Start);
|
|
340
|
+
verbose.phaseOk(3, 3, "Transforming AST", phase3Duration);
|
|
341
|
+
const viewNodeCount = countViewNodes(program2.view);
|
|
342
|
+
debug.analyzePass(stateNames.length, actionNames.length, viewNodeCount, phase2Duration);
|
|
62
343
|
const outputPath = getOutputPath(inputPath, options);
|
|
63
344
|
const outputDir = dirname(outputPath);
|
|
64
345
|
try {
|
|
65
346
|
mkdirSync(outputDir, { recursive: true });
|
|
66
347
|
} catch (err) {
|
|
67
348
|
const error = err;
|
|
68
|
-
|
|
69
|
-
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
errors: [new ConstelaError("SCHEMA_INVALID", `Could not create output directory: ${outputDir} - ${error.message}`)],
|
|
352
|
+
duration: Math.round(performance.now() - startTime)
|
|
353
|
+
};
|
|
70
354
|
}
|
|
71
|
-
const outputContent = options.pretty ? JSON.stringify(
|
|
355
|
+
const outputContent = options.pretty ? JSON.stringify(program2, null, 2) : JSON.stringify(program2);
|
|
72
356
|
try {
|
|
73
357
|
writeFileSync(outputPath, outputContent, "utf-8");
|
|
74
358
|
} catch (err) {
|
|
75
359
|
const error = err;
|
|
76
|
-
|
|
360
|
+
return {
|
|
361
|
+
ok: false,
|
|
362
|
+
errors: [new ConstelaError("SCHEMA_INVALID", `Could not write output file: ${outputPath} - ${error.message}`)],
|
|
363
|
+
duration: Math.round(performance.now() - startTime)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const outputSize = outputContent.length;
|
|
367
|
+
debug.transformPass(outputSize, phase3Duration);
|
|
368
|
+
const totalDuration = Math.round(performance.now() - startTime);
|
|
369
|
+
verbose.summary(stateNames.length, actionNames.length, viewNodeCount, totalDuration);
|
|
370
|
+
const result = {
|
|
371
|
+
ok: true,
|
|
372
|
+
outputPath,
|
|
373
|
+
duration: totalDuration
|
|
374
|
+
};
|
|
375
|
+
if (options.debug) {
|
|
376
|
+
result.debugInfo = buildDebugInfo(
|
|
377
|
+
basename(inputPath),
|
|
378
|
+
inputSize,
|
|
379
|
+
parseDuration,
|
|
380
|
+
phase1Duration,
|
|
381
|
+
phase2Duration,
|
|
382
|
+
phase3Duration,
|
|
383
|
+
nodesValidated,
|
|
384
|
+
stateNames.length,
|
|
385
|
+
actionNames.length,
|
|
386
|
+
viewNodeCount,
|
|
387
|
+
outputSize
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
function outputResult(inputPath, result, options, c) {
|
|
393
|
+
const isJsonMode = options.json === true;
|
|
394
|
+
if (result.ok) {
|
|
395
|
+
if (isJsonMode) {
|
|
396
|
+
const output = {
|
|
397
|
+
success: true,
|
|
398
|
+
inputFile: basename(inputPath),
|
|
399
|
+
outputFile: basename(result.outputPath),
|
|
400
|
+
diagnostics: { duration: result.duration }
|
|
401
|
+
};
|
|
402
|
+
if (result.debugInfo) {
|
|
403
|
+
output.debug = result.debugInfo;
|
|
404
|
+
}
|
|
405
|
+
outputJson(output);
|
|
406
|
+
} else if (!options.verbose) {
|
|
407
|
+
console.log(`${c.green("\u2713")} Compiled ${inputPath} \u2192 ${result.outputPath}`);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
if (isJsonMode) {
|
|
411
|
+
const output = {
|
|
412
|
+
success: false,
|
|
413
|
+
errors: result.errors.map(errorToJson),
|
|
414
|
+
diagnostics: { duration: result.duration }
|
|
415
|
+
};
|
|
416
|
+
outputJson(output);
|
|
417
|
+
} else {
|
|
418
|
+
for (const error of result.errors) {
|
|
419
|
+
console.error(formatColoredError(error, c));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function compileCommand(inputPath, options) {
|
|
425
|
+
const isJsonMode = options.json === true;
|
|
426
|
+
const c = isJsonMode ? { red: (s) => s, green: (s) => s, yellow: (s) => s, reset: "" } : getColors();
|
|
427
|
+
const result = performCompilation(inputPath, options, c);
|
|
428
|
+
outputResult(inputPath, result, options, c);
|
|
429
|
+
if (!options.watch) {
|
|
430
|
+
if (!result.ok) {
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
console.log("[Watching for changes...]");
|
|
436
|
+
const watcher = fsWatch(inputPath, (eventType) => {
|
|
437
|
+
if (eventType === "change") {
|
|
438
|
+
console.log(`[File changed: ${inputPath}]`);
|
|
439
|
+
const watchResult = performCompilation(inputPath, options, c);
|
|
440
|
+
outputResult(inputPath, watchResult, options, c);
|
|
441
|
+
console.log("[Watching for changes...]");
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
process.on("SIGINT", () => {
|
|
445
|
+
watcher.close();
|
|
446
|
+
console.log("\n[Watch mode stopped]");
|
|
447
|
+
process.exit(0);
|
|
448
|
+
});
|
|
449
|
+
await new Promise(() => {
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/commands/validate.ts
|
|
454
|
+
import { validatePass as validatePass2, analyzePass as analyzePass2 } from "@constela/compiler";
|
|
455
|
+
import { ConstelaError as ConstelaError2 } from "@constela/core";
|
|
456
|
+
import { readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
457
|
+
import { join as join2, basename as basename2 } from "path";
|
|
458
|
+
function shouldUseColors2() {
|
|
459
|
+
if (process.env["NO_COLOR"] !== void 0) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
if (process.env["FORCE_COLOR"] === "1") {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
return process.stdout.isTTY === true;
|
|
466
|
+
}
|
|
467
|
+
var colors2 = {
|
|
468
|
+
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
469
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
470
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
471
|
+
reset: "\x1B[0m"
|
|
472
|
+
};
|
|
473
|
+
function getColors2() {
|
|
474
|
+
if (shouldUseColors2()) {
|
|
475
|
+
return colors2;
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
red: (s) => s,
|
|
479
|
+
green: (s) => s,
|
|
480
|
+
yellow: (s) => s,
|
|
481
|
+
reset: ""
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function formatColoredError2(error, c) {
|
|
485
|
+
const lines = [];
|
|
486
|
+
const pathInfo = error.path ? ` at ${error.path}` : "";
|
|
487
|
+
lines.push(`Error [${c.red(error.code)}]: ${error.message}${pathInfo}`);
|
|
488
|
+
if (error.suggestion) {
|
|
489
|
+
lines.push(` ${c.yellow(`Suggestion: ${error.suggestion}`)}`);
|
|
490
|
+
}
|
|
491
|
+
return lines.join("\n");
|
|
492
|
+
}
|
|
493
|
+
function errorToJson2(error) {
|
|
494
|
+
const result = {
|
|
495
|
+
code: error.code,
|
|
496
|
+
message: error.message
|
|
497
|
+
};
|
|
498
|
+
if (error.path !== void 0) {
|
|
499
|
+
result.path = error.path;
|
|
500
|
+
}
|
|
501
|
+
if (error.suggestion !== void 0) {
|
|
502
|
+
result.suggestion = error.suggestion;
|
|
503
|
+
}
|
|
504
|
+
if (error.context !== void 0) {
|
|
505
|
+
result.context = error.context;
|
|
506
|
+
}
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
function outputJson2(output) {
|
|
510
|
+
console.log(JSON.stringify(output));
|
|
511
|
+
}
|
|
512
|
+
function findJsonFiles(dir) {
|
|
513
|
+
const files = [];
|
|
514
|
+
const entries = readdirSync(dir);
|
|
515
|
+
for (const entry of entries) {
|
|
516
|
+
const fullPath = join2(dir, entry);
|
|
517
|
+
const stat = statSync2(fullPath);
|
|
518
|
+
if (stat.isDirectory()) {
|
|
519
|
+
files.push(...findJsonFiles(fullPath));
|
|
520
|
+
} else if (stat.isFile() && entry.endsWith(".json")) {
|
|
521
|
+
files.push(fullPath);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return files;
|
|
525
|
+
}
|
|
526
|
+
function validateSingleFile(filePath) {
|
|
527
|
+
let fileContent;
|
|
528
|
+
try {
|
|
529
|
+
fileContent = readFileSync2(filePath, "utf-8");
|
|
530
|
+
} catch (err) {
|
|
531
|
+
const error = err;
|
|
532
|
+
if (error.code === "ENOENT") {
|
|
533
|
+
return {
|
|
534
|
+
valid: false,
|
|
535
|
+
errors: [new ConstelaError2("SCHEMA_INVALID", `File not found: ${filePath}`)]
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
valid: false,
|
|
540
|
+
errors: [new ConstelaError2("SCHEMA_INVALID", `Could not read file: ${filePath} - ${error.message}`)]
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
let inputJson;
|
|
544
|
+
try {
|
|
545
|
+
inputJson = JSON.parse(fileContent);
|
|
546
|
+
} catch (err) {
|
|
547
|
+
const error = err;
|
|
548
|
+
return {
|
|
549
|
+
valid: false,
|
|
550
|
+
errors: [new ConstelaError2("SCHEMA_INVALID", `Invalid JSON: ${error.message}`)]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const validateResult = validatePass2(inputJson);
|
|
554
|
+
if (!validateResult.ok) {
|
|
555
|
+
return {
|
|
556
|
+
valid: false,
|
|
557
|
+
errors: [validateResult.error]
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const analyzeResult = analyzePass2(validateResult.ast);
|
|
561
|
+
if (!analyzeResult.ok) {
|
|
562
|
+
return {
|
|
563
|
+
valid: false,
|
|
564
|
+
errors: analyzeResult.errors
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
valid: true,
|
|
569
|
+
errors: []
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function validateCommand(input, options) {
|
|
573
|
+
const startTime = performance.now();
|
|
574
|
+
const isJsonMode = options.json === true;
|
|
575
|
+
const isAllMode = options.all === true;
|
|
576
|
+
const c = isJsonMode ? { red: (s) => s, green: (s) => s, yellow: (s) => s, reset: "" } : getColors2();
|
|
577
|
+
function exitWithResult(success, output) {
|
|
578
|
+
if (isJsonMode && output) {
|
|
579
|
+
outputJson2(output);
|
|
580
|
+
}
|
|
581
|
+
process.exit(success ? 0 : 1);
|
|
582
|
+
}
|
|
583
|
+
if (isAllMode) {
|
|
584
|
+
const dirPath = input ?? ".";
|
|
585
|
+
try {
|
|
586
|
+
const stat = statSync2(dirPath);
|
|
587
|
+
if (!stat.isDirectory()) {
|
|
588
|
+
const duration3 = Math.round(performance.now() - startTime);
|
|
589
|
+
if (isJsonMode) {
|
|
590
|
+
exitWithResult(false, {
|
|
591
|
+
success: false,
|
|
592
|
+
errors: [{ code: "SCHEMA_INVALID", message: `Not a directory: ${dirPath}` }],
|
|
593
|
+
diagnostics: { duration: duration3 }
|
|
594
|
+
});
|
|
595
|
+
} else {
|
|
596
|
+
console.error(`Error: Not a directory: ${dirPath}`);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
} catch (err) {
|
|
601
|
+
const duration3 = Math.round(performance.now() - startTime);
|
|
602
|
+
if (isJsonMode) {
|
|
603
|
+
exitWithResult(false, {
|
|
604
|
+
success: false,
|
|
605
|
+
errors: [{ code: "SCHEMA_INVALID", message: `Directory not found: ${dirPath}` }],
|
|
606
|
+
diagnostics: { duration: duration3 }
|
|
607
|
+
});
|
|
608
|
+
} else {
|
|
609
|
+
console.error(`Error: Directory not found: ${dirPath}`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const jsonFiles = findJsonFiles(dirPath);
|
|
614
|
+
const validatedCount = jsonFiles.length;
|
|
615
|
+
const fileErrors = [];
|
|
616
|
+
const validFiles = [];
|
|
617
|
+
for (const filePath of jsonFiles) {
|
|
618
|
+
const result2 = validateSingleFile(filePath);
|
|
619
|
+
const fileName2 = basename2(filePath);
|
|
620
|
+
if (result2.valid) {
|
|
621
|
+
validFiles.push(fileName2);
|
|
622
|
+
} else {
|
|
623
|
+
fileErrors.push({
|
|
624
|
+
file: fileName2,
|
|
625
|
+
errors: result2.errors.map(errorToJson2)
|
|
626
|
+
});
|
|
627
|
+
if (!isJsonMode) {
|
|
628
|
+
console.error(`
|
|
629
|
+
${c.red("Error")} in ${fileName2}:`);
|
|
630
|
+
for (const error of result2.errors) {
|
|
631
|
+
console.error(formatColoredError2(error, c));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const duration2 = Math.round(performance.now() - startTime);
|
|
637
|
+
const hasErrors = fileErrors.length > 0;
|
|
638
|
+
if (isJsonMode) {
|
|
639
|
+
if (hasErrors) {
|
|
640
|
+
const output = {
|
|
641
|
+
success: false,
|
|
642
|
+
files: fileErrors,
|
|
643
|
+
validatedCount,
|
|
644
|
+
diagnostics: { duration: duration2 }
|
|
645
|
+
};
|
|
646
|
+
exitWithResult(false, output);
|
|
647
|
+
} else {
|
|
648
|
+
const output = {
|
|
649
|
+
success: true,
|
|
650
|
+
files: validFiles,
|
|
651
|
+
validatedCount,
|
|
652
|
+
diagnostics: { duration: duration2 }
|
|
653
|
+
};
|
|
654
|
+
exitWithResult(true, output);
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
if (hasErrors) {
|
|
658
|
+
console.log(`
|
|
659
|
+
Validated ${validatedCount} files with errors`);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
} else {
|
|
662
|
+
console.log(`${c.green("OK")} Validated ${validatedCount} files successfully`);
|
|
663
|
+
process.exit(0);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (!input) {
|
|
668
|
+
const duration2 = Math.round(performance.now() - startTime);
|
|
669
|
+
if (isJsonMode) {
|
|
670
|
+
exitWithResult(false, {
|
|
671
|
+
success: false,
|
|
672
|
+
errors: [{ code: "SCHEMA_INVALID", message: "No input file specified" }],
|
|
673
|
+
diagnostics: { duration: duration2 }
|
|
674
|
+
});
|
|
675
|
+
} else {
|
|
676
|
+
console.error("Error: No input file specified");
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const result = validateSingleFile(input);
|
|
681
|
+
const duration = Math.round(performance.now() - startTime);
|
|
682
|
+
const fileName = basename2(input);
|
|
683
|
+
if (result.valid) {
|
|
684
|
+
if (isJsonMode) {
|
|
685
|
+
const output = {
|
|
686
|
+
success: true,
|
|
687
|
+
file: fileName,
|
|
688
|
+
validatedCount: 1,
|
|
689
|
+
diagnostics: { duration }
|
|
690
|
+
};
|
|
691
|
+
exitWithResult(true, output);
|
|
692
|
+
} else {
|
|
693
|
+
console.log(`${c.green("OK")} ${input} is valid`);
|
|
694
|
+
process.exit(0);
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
if (isJsonMode) {
|
|
698
|
+
const output = {
|
|
699
|
+
success: false,
|
|
700
|
+
file: fileName,
|
|
701
|
+
errors: result.errors.map(errorToJson2),
|
|
702
|
+
diagnostics: { duration }
|
|
703
|
+
};
|
|
704
|
+
exitWithResult(false, output);
|
|
705
|
+
} else {
|
|
706
|
+
for (const error of result.errors) {
|
|
707
|
+
console.error(formatColoredError2(error, c));
|
|
708
|
+
}
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/commands/inspect.ts
|
|
715
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
716
|
+
function shouldUseColors3() {
|
|
717
|
+
if (process.env["NO_COLOR"] !== void 0) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
if (process.env["FORCE_COLOR"] === "1") {
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
return process.stdout.isTTY === true;
|
|
724
|
+
}
|
|
725
|
+
var colors3 = {
|
|
726
|
+
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
727
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
728
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
729
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
730
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
731
|
+
reset: "\x1B[0m"
|
|
732
|
+
};
|
|
733
|
+
function getColors3() {
|
|
734
|
+
if (shouldUseColors3()) {
|
|
735
|
+
return colors3;
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
red: (s) => s,
|
|
739
|
+
green: (s) => s,
|
|
740
|
+
yellow: (s) => s,
|
|
741
|
+
cyan: (s) => s,
|
|
742
|
+
dim: (s) => s,
|
|
743
|
+
reset: ""
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function summarizeStep(step) {
|
|
747
|
+
switch (step.do) {
|
|
748
|
+
case "set":
|
|
749
|
+
return `set ${step.target}`;
|
|
750
|
+
case "update":
|
|
751
|
+
return `${step.operation} ${step.target}`;
|
|
752
|
+
case "fetch":
|
|
753
|
+
return "fetch";
|
|
754
|
+
case "storage":
|
|
755
|
+
return `storage ${step.operation}`;
|
|
756
|
+
case "clipboard":
|
|
757
|
+
return `clipboard ${step.operation}`;
|
|
758
|
+
case "navigate":
|
|
759
|
+
return "navigate";
|
|
760
|
+
case "import":
|
|
761
|
+
return `import ${step.module}`;
|
|
762
|
+
case "call":
|
|
763
|
+
return "call";
|
|
764
|
+
case "subscribe":
|
|
765
|
+
return `subscribe ${step.event}`;
|
|
766
|
+
case "dispose":
|
|
767
|
+
return "dispose";
|
|
768
|
+
case "dom":
|
|
769
|
+
return `dom ${step.operation}`;
|
|
770
|
+
default:
|
|
771
|
+
return "unknown";
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function summarizeAction(action) {
|
|
775
|
+
if (action.steps.length === 0) {
|
|
776
|
+
return "(empty)";
|
|
777
|
+
}
|
|
778
|
+
if (action.steps.length === 1) {
|
|
779
|
+
return summarizeStep(action.steps[0]);
|
|
780
|
+
}
|
|
781
|
+
return `${summarizeStep(action.steps[0])} + ${action.steps.length - 1} more`;
|
|
782
|
+
}
|
|
783
|
+
function viewNodeToInfo(node) {
|
|
784
|
+
const info = { kind: node.kind };
|
|
785
|
+
switch (node.kind) {
|
|
786
|
+
case "element": {
|
|
787
|
+
info.tag = node.tag;
|
|
788
|
+
if (node.props) {
|
|
789
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
790
|
+
if (key === "onClick" && typeof value === "object" && value !== null && "action" in value) {
|
|
791
|
+
info.event = `onClick=${value.action}`;
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (node.children && node.children.length > 0) {
|
|
797
|
+
info.children = node.children.map(viewNodeToInfo);
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
case "text": {
|
|
802
|
+
const val = node.value;
|
|
803
|
+
if (val.expr === "state") {
|
|
804
|
+
info.text = `state.${val.name}`;
|
|
805
|
+
} else if (val.expr === "lit") {
|
|
806
|
+
info.text = String(val.value);
|
|
807
|
+
} else if (val.expr === "param") {
|
|
808
|
+
info.text = `param.${val.name}`;
|
|
809
|
+
} else {
|
|
810
|
+
info.text = `(${val.expr})`;
|
|
811
|
+
}
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
case "if": {
|
|
815
|
+
info.children = [viewNodeToInfo(node.then)];
|
|
816
|
+
if (node.else) {
|
|
817
|
+
info.children.push(viewNodeToInfo(node.else));
|
|
818
|
+
}
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "each": {
|
|
822
|
+
info.children = [viewNodeToInfo(node.body)];
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
case "component": {
|
|
826
|
+
info.tag = node.name;
|
|
827
|
+
if (node.children && node.children.length > 0) {
|
|
828
|
+
info.children = node.children.map(viewNodeToInfo);
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
default:
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
return info;
|
|
836
|
+
}
|
|
837
|
+
function formatStateSection(state, c) {
|
|
838
|
+
const lines = [];
|
|
839
|
+
const fieldCount = Object.keys(state).length;
|
|
840
|
+
lines.push(`${c.cyan("State")} (${fieldCount} fields):`);
|
|
841
|
+
for (const [name, field] of Object.entries(state)) {
|
|
842
|
+
const initialStr = JSON.stringify(field.initial);
|
|
843
|
+
lines.push(` ${name}: ${c.yellow(field.type)} = ${c.dim(initialStr)}`);
|
|
844
|
+
}
|
|
845
|
+
return lines;
|
|
846
|
+
}
|
|
847
|
+
function formatActionsSection(actions, c) {
|
|
848
|
+
const lines = [];
|
|
849
|
+
lines.push(`${c.cyan("Actions")} (${actions.length}):`);
|
|
850
|
+
for (const action of actions) {
|
|
851
|
+
const summary = summarizeAction(action);
|
|
852
|
+
lines.push(` ${action.name}: ${c.dim(summary)}`);
|
|
853
|
+
}
|
|
854
|
+
return lines;
|
|
855
|
+
}
|
|
856
|
+
function normalizeComponents(components) {
|
|
857
|
+
if (Array.isArray(components)) {
|
|
858
|
+
return components.map((comp) => ({
|
|
859
|
+
name: comp.name,
|
|
860
|
+
params: Array.isArray(comp.params) ? comp.params : Object.entries(comp.params ?? {}).map(([pName, pDef]) => ({
|
|
861
|
+
name: pName,
|
|
862
|
+
type: pDef.type
|
|
863
|
+
}))
|
|
864
|
+
}));
|
|
865
|
+
}
|
|
866
|
+
return Object.entries(components).map(
|
|
867
|
+
([name, def]) => ({
|
|
868
|
+
name,
|
|
869
|
+
params: Array.isArray(def.params) ? def.params : Object.entries(def.params ?? {}).map(([pName, pDef]) => ({
|
|
870
|
+
name: pName,
|
|
871
|
+
type: pDef.type
|
|
872
|
+
}))
|
|
873
|
+
})
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
function formatComponentsSection(components, c) {
|
|
877
|
+
const lines = [];
|
|
878
|
+
const normalized = normalizeComponents(components);
|
|
879
|
+
const componentCount = normalized.length;
|
|
880
|
+
lines.push(`${c.cyan("Components")} (${componentCount}):`);
|
|
881
|
+
for (const comp of normalized) {
|
|
882
|
+
const paramList = comp.params.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
883
|
+
lines.push(` ${comp.name}: params(${c.dim(paramList)})`);
|
|
884
|
+
}
|
|
885
|
+
return lines;
|
|
886
|
+
}
|
|
887
|
+
function formatStylesSection(styles, c) {
|
|
888
|
+
const lines = [];
|
|
889
|
+
const styleCount = Object.keys(styles).length;
|
|
890
|
+
lines.push(`${c.cyan("Styles")} (${styleCount}):`);
|
|
891
|
+
for (const [name, preset] of Object.entries(styles)) {
|
|
892
|
+
const variantCount = preset.variants ? Object.keys(preset.variants).length : 0;
|
|
893
|
+
const desc = variantCount > 0 ? `base + ${variantCount} variants` : "base only";
|
|
894
|
+
lines.push(` ${name}: ${c.dim(desc)}`);
|
|
895
|
+
}
|
|
896
|
+
return lines;
|
|
897
|
+
}
|
|
898
|
+
function formatViewTree(node, c, indent = 0) {
|
|
899
|
+
const lines = [];
|
|
900
|
+
const prefix = " ".repeat(indent);
|
|
901
|
+
switch (node.kind) {
|
|
902
|
+
case "element": {
|
|
903
|
+
let line = `${prefix}element<${c.green(node.tag)}>`;
|
|
904
|
+
if (node.props) {
|
|
905
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
906
|
+
if (key === "onClick" && typeof value === "object" && value !== null && "action" in value) {
|
|
907
|
+
line += ` ${c.dim(`onClick=${value.action}`)}`;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
lines.push(line);
|
|
912
|
+
if (node.children) {
|
|
913
|
+
for (const child of node.children) {
|
|
914
|
+
lines.push(...formatViewTree(child, c, indent + 1));
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
case "text": {
|
|
920
|
+
const val = node.value;
|
|
921
|
+
let textDesc;
|
|
922
|
+
if (val.expr === "state") {
|
|
923
|
+
textDesc = `state.${val.name}`;
|
|
924
|
+
} else if (val.expr === "lit") {
|
|
925
|
+
textDesc = JSON.stringify(val.value);
|
|
926
|
+
} else if (val.expr === "param") {
|
|
927
|
+
textDesc = `param.${val.name}`;
|
|
928
|
+
} else {
|
|
929
|
+
textDesc = `(${val.expr})`;
|
|
930
|
+
}
|
|
931
|
+
lines.push(`${prefix}text: ${c.dim(textDesc)}`);
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
case "if": {
|
|
935
|
+
lines.push(`${prefix}if:`);
|
|
936
|
+
lines.push(`${prefix} then:`);
|
|
937
|
+
lines.push(...formatViewTree(node.then, c, indent + 2));
|
|
938
|
+
if (node.else) {
|
|
939
|
+
lines.push(`${prefix} else:`);
|
|
940
|
+
lines.push(...formatViewTree(node.else, c, indent + 2));
|
|
941
|
+
}
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
case "each": {
|
|
945
|
+
lines.push(`${prefix}each (as ${node.as}):`);
|
|
946
|
+
lines.push(...formatViewTree(node.body, c, indent + 1));
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
case "component": {
|
|
950
|
+
lines.push(`${prefix}component<${c.green(node.name)}>`);
|
|
951
|
+
if (node.children) {
|
|
952
|
+
for (const child of node.children) {
|
|
953
|
+
lines.push(...formatViewTree(child, c, indent + 1));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
case "slot": {
|
|
959
|
+
const slotName = node.name ? `(${node.name})` : "";
|
|
960
|
+
lines.push(`${prefix}slot${slotName}`);
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "markdown": {
|
|
964
|
+
lines.push(`${prefix}markdown`);
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
case "code": {
|
|
968
|
+
lines.push(`${prefix}code`);
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return lines;
|
|
973
|
+
}
|
|
974
|
+
function formatViewSection(view, c) {
|
|
975
|
+
const lines = [];
|
|
976
|
+
lines.push(`${c.cyan("View Tree")}:`);
|
|
977
|
+
lines.push(...formatViewTree(view, c, 1));
|
|
978
|
+
return lines;
|
|
979
|
+
}
|
|
980
|
+
async function inspectCommand(input, options) {
|
|
981
|
+
const isJsonMode = options.json === true;
|
|
982
|
+
const c = isJsonMode ? {
|
|
983
|
+
red: (s) => s,
|
|
984
|
+
green: (s) => s,
|
|
985
|
+
yellow: (s) => s,
|
|
986
|
+
cyan: (s) => s,
|
|
987
|
+
dim: (s) => s,
|
|
988
|
+
reset: ""
|
|
989
|
+
} : getColors3();
|
|
990
|
+
const showAll = !options.state && !options.actions && !options.components && !options.view;
|
|
991
|
+
const showState = showAll || options.state === true;
|
|
992
|
+
const showActions = showAll || options.actions === true;
|
|
993
|
+
const showComponents = showAll || options.components === true;
|
|
994
|
+
const showView = showAll || options.view === true;
|
|
995
|
+
const showStyles = showAll;
|
|
996
|
+
let fileContent;
|
|
997
|
+
try {
|
|
998
|
+
fileContent = readFileSync3(input, "utf-8");
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
const error = err;
|
|
1001
|
+
if (error.code === "ENOENT") {
|
|
1002
|
+
console.error(`Error: File not found: ${input}`);
|
|
1003
|
+
} else {
|
|
1004
|
+
console.error(`Error: Could not read file: ${input} - ${error.message}`);
|
|
1005
|
+
}
|
|
77
1006
|
process.exit(1);
|
|
78
1007
|
}
|
|
79
|
-
|
|
1008
|
+
let program2;
|
|
1009
|
+
try {
|
|
1010
|
+
program2 = JSON.parse(fileContent);
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
const error = err;
|
|
1013
|
+
console.error(`Error: Invalid JSON syntax: ${error.message}`);
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
if (isJsonMode) {
|
|
1017
|
+
const output = {};
|
|
1018
|
+
if (showState && program2.state) {
|
|
1019
|
+
output.state = {};
|
|
1020
|
+
for (const [name, field] of Object.entries(program2.state)) {
|
|
1021
|
+
output.state[name] = {
|
|
1022
|
+
type: field.type,
|
|
1023
|
+
initial: field.initial
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (showActions && program2.actions) {
|
|
1028
|
+
output.actions = program2.actions.map((action) => ({
|
|
1029
|
+
name: action.name,
|
|
1030
|
+
stepSummary: summarizeAction(action)
|
|
1031
|
+
}));
|
|
1032
|
+
}
|
|
1033
|
+
if (showComponents && program2.components) {
|
|
1034
|
+
output.components = normalizeComponents(program2.components);
|
|
1035
|
+
}
|
|
1036
|
+
if (showStyles && program2.styles) {
|
|
1037
|
+
output.styles = program2.styles;
|
|
1038
|
+
}
|
|
1039
|
+
if (showView && program2.view) {
|
|
1040
|
+
output.viewTree = viewNodeToInfo(program2.view);
|
|
1041
|
+
}
|
|
1042
|
+
console.log(JSON.stringify(output));
|
|
1043
|
+
} else {
|
|
1044
|
+
const sections = [];
|
|
1045
|
+
if (showState && program2.state) {
|
|
1046
|
+
sections.push(formatStateSection(program2.state, c));
|
|
1047
|
+
}
|
|
1048
|
+
if (showActions && program2.actions) {
|
|
1049
|
+
sections.push(formatActionsSection(program2.actions, c));
|
|
1050
|
+
}
|
|
1051
|
+
if (showComponents && program2.components) {
|
|
1052
|
+
sections.push(formatComponentsSection(program2.components, c));
|
|
1053
|
+
}
|
|
1054
|
+
if (showStyles && program2.styles) {
|
|
1055
|
+
sections.push(formatStylesSection(program2.styles, c));
|
|
1056
|
+
}
|
|
1057
|
+
if (showView && program2.view) {
|
|
1058
|
+
sections.push(formatViewSection(program2.view, c));
|
|
1059
|
+
}
|
|
1060
|
+
for (let i = 0; i < sections.length; i++) {
|
|
1061
|
+
for (const line of sections[i]) {
|
|
1062
|
+
console.log(line);
|
|
1063
|
+
}
|
|
1064
|
+
if (i < sections.length - 1) {
|
|
1065
|
+
console.log("");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
80
1069
|
}
|
|
81
1070
|
|
|
82
1071
|
// src/commands/dev.ts
|
|
@@ -185,7 +1174,9 @@ async function startCommand(options) {
|
|
|
185
1174
|
// src/index.ts
|
|
186
1175
|
var program = new Command();
|
|
187
1176
|
program.name("constela").description("Constela UI framework CLI").version("0.1.0");
|
|
188
|
-
program.command("compile <input>").description("Compile a Constela DSL file").option("-o, --out <path>", "Output file path").option("--pretty", "Pretty-print JSON output").action(compileCommand);
|
|
1177
|
+
program.command("compile <input>").description("Compile a Constela DSL file").option("-o, --out <path>", "Output file path").option("--pretty", "Pretty-print JSON output").option("--json", "Output results as JSON").option("-w, --watch", "Watch input file for changes and recompile").option("-v, --verbose", "Show detailed progress during compilation").option("--debug", "Show internal debug information").action(compileCommand);
|
|
1178
|
+
program.command("validate [input]").description("Validate Constela JSON files without compilation").option("-a, --all", "Validate all JSON files in directory recursively").option("--json", "Output results as JSON").action(validateCommand);
|
|
1179
|
+
program.command("inspect <input>").description("Inspect Constela program structure").option("--state", "Show only state").option("--actions", "Show only actions").option("--components", "Show only components").option("--view", "Show only view tree").option("--json", "Output as JSON").action(inspectCommand);
|
|
189
1180
|
program.command("dev").description("Start development server").option("-p, --port <number>", "Port number (default: 3000)").option("--host <string>", "Host address").option("--routesDir <path>", "Routes directory").option("--publicDir <path>", "Public directory").option("--layoutsDir <path>", "Layouts directory").action(devCommand);
|
|
190
1181
|
program.command("build").description("Build for production").option("-o, --outDir <path>", "Output directory (default: dist)").option("--routesDir <path>", "Routes directory").option("--publicDir <path>", "Public directory").option("--layoutsDir <path>", "Layouts directory").action(buildCommand);
|
|
191
1182
|
program.command("start").description("Start production server").option("-p, --port <number>", "Port number (default: 3000)").action(startCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI tools for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"commander": "^12.0.0",
|
|
22
|
-
"@constela/
|
|
23
|
-
"@constela/compiler": "0.
|
|
24
|
-
"@constela/
|
|
22
|
+
"@constela/start": "1.2.24",
|
|
23
|
+
"@constela/compiler": "0.8.0",
|
|
24
|
+
"@constela/core": "0.8.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^20.10.0",
|