@principal-ai/principal-view-cli 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +50 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +274 -0
- package/dist/commands/hooks.d.ts +9 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +295 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +290 -0
- package/dist/commands/lint.d.ts +6 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +375 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +80 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +333 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +389 -0
- package/dist/index.cjs +17286 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/package.json +57 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate command - Validate .canvas configuration files
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve, relative } from 'node:path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { globby } from 'globby';
|
|
9
|
+
import yaml from 'js-yaml';
|
|
10
|
+
/**
|
|
11
|
+
* Load the library.yaml file from the .principal-views directory
|
|
12
|
+
*/
|
|
13
|
+
function loadLibrary(principalViewsDir) {
|
|
14
|
+
const libraryFiles = ['library.yaml', 'library.yml', 'library.json'];
|
|
15
|
+
for (const fileName of libraryFiles) {
|
|
16
|
+
const libraryPath = resolve(principalViewsDir, fileName);
|
|
17
|
+
if (existsSync(libraryPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const content = readFileSync(libraryPath, 'utf8');
|
|
20
|
+
const library = fileName.endsWith('.json')
|
|
21
|
+
? JSON.parse(content)
|
|
22
|
+
: yaml.load(content);
|
|
23
|
+
if (library && typeof library === 'object') {
|
|
24
|
+
return {
|
|
25
|
+
nodeComponents: library.nodeComponents || {},
|
|
26
|
+
edgeComponents: library.edgeComponents || {},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Library exists but failed to parse - return empty to avoid false positives
|
|
32
|
+
return { nodeComponents: {}, edgeComponents: {} };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Standard JSON Canvas node types that don't require pv metadata
|
|
40
|
+
*/
|
|
41
|
+
const STANDARD_CANVAS_TYPES = ['text', 'group', 'file', 'link'];
|
|
42
|
+
/**
|
|
43
|
+
* Valid node shapes for pv.shape
|
|
44
|
+
*/
|
|
45
|
+
const VALID_NODE_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'];
|
|
46
|
+
/**
|
|
47
|
+
* Validate an ExtendedCanvas object with strict validation
|
|
48
|
+
*
|
|
49
|
+
* Strict validation ensures:
|
|
50
|
+
* - All required fields are present
|
|
51
|
+
* - Custom node types have proper pv metadata
|
|
52
|
+
* - Edge types reference defined types in pv.edgeTypes or library.edgeComponents
|
|
53
|
+
* - Node types reference defined types in pv.nodeTypes or library.nodeComponents
|
|
54
|
+
* - Canvas has pv extension with name and version
|
|
55
|
+
*/
|
|
56
|
+
function validateCanvas(canvas, filePath, library) {
|
|
57
|
+
const issues = [];
|
|
58
|
+
if (!canvas || typeof canvas !== 'object') {
|
|
59
|
+
issues.push({ type: 'error', message: 'Canvas must be an object' });
|
|
60
|
+
return issues;
|
|
61
|
+
}
|
|
62
|
+
const c = canvas;
|
|
63
|
+
// Collect library-defined types
|
|
64
|
+
const libraryNodeTypes = library ? Object.keys(library.nodeComponents) : [];
|
|
65
|
+
const libraryEdgeTypes = library ? Object.keys(library.edgeComponents) : [];
|
|
66
|
+
// Check pv extension (REQUIRED for strict validation)
|
|
67
|
+
let canvasEdgeTypes = [];
|
|
68
|
+
let canvasNodeTypes = [];
|
|
69
|
+
if (c.pv === undefined) {
|
|
70
|
+
issues.push({
|
|
71
|
+
type: 'error',
|
|
72
|
+
message: 'Canvas must have a "pv" extension with name and version',
|
|
73
|
+
path: 'pv',
|
|
74
|
+
suggestion: 'Add: "pv": { "name": "My Graph", "version": "1.0.0" }',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (typeof c.pv !== 'object') {
|
|
78
|
+
issues.push({ type: 'error', message: '"pv" extension must be an object' });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const pv = c.pv;
|
|
82
|
+
if (typeof pv.version !== 'string' || !pv.version) {
|
|
83
|
+
issues.push({
|
|
84
|
+
type: 'error',
|
|
85
|
+
message: 'pv.version is required',
|
|
86
|
+
path: 'pv.version',
|
|
87
|
+
suggestion: 'Add: "version": "1.0.0"',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (typeof pv.name !== 'string' || !pv.name) {
|
|
91
|
+
issues.push({
|
|
92
|
+
type: 'error',
|
|
93
|
+
message: 'pv.name is required',
|
|
94
|
+
path: 'pv.name',
|
|
95
|
+
suggestion: 'Add: "name": "My Graph"',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Collect defined edge types for later validation
|
|
99
|
+
if (pv.edgeTypes && typeof pv.edgeTypes === 'object') {
|
|
100
|
+
canvasEdgeTypes = Object.keys(pv.edgeTypes);
|
|
101
|
+
}
|
|
102
|
+
// Collect defined node types for later validation
|
|
103
|
+
if (pv.nodeTypes && typeof pv.nodeTypes === 'object') {
|
|
104
|
+
canvasNodeTypes = Object.keys(pv.nodeTypes);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Combined types from canvas + library
|
|
108
|
+
const allDefinedNodeTypes = [...new Set([...canvasNodeTypes, ...libraryNodeTypes])];
|
|
109
|
+
const allDefinedEdgeTypes = [...new Set([...canvasEdgeTypes, ...libraryEdgeTypes])];
|
|
110
|
+
// Check nodes
|
|
111
|
+
if (!Array.isArray(c.nodes)) {
|
|
112
|
+
issues.push({ type: 'error', message: 'Canvas must have a "nodes" array' });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
c.nodes.forEach((node, index) => {
|
|
116
|
+
if (!node || typeof node !== 'object') {
|
|
117
|
+
issues.push({ type: 'error', message: `Node at index ${index} must be an object`, path: `nodes[${index}]` });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const n = node;
|
|
121
|
+
if (typeof n.id !== 'string' || !n.id) {
|
|
122
|
+
issues.push({ type: 'error', message: `Node at index ${index} must have a string "id"`, path: `nodes[${index}].id` });
|
|
123
|
+
}
|
|
124
|
+
if (typeof n.type !== 'string') {
|
|
125
|
+
issues.push({ type: 'error', message: `Node "${n.id || index}" must have a string "type"`, path: `nodes[${index}].type` });
|
|
126
|
+
}
|
|
127
|
+
if (typeof n.x !== 'number') {
|
|
128
|
+
issues.push({ type: 'error', message: `Node "${n.id || index}" must have a numeric "x" position`, path: `nodes[${index}].x` });
|
|
129
|
+
}
|
|
130
|
+
if (typeof n.y !== 'number') {
|
|
131
|
+
issues.push({ type: 'error', message: `Node "${n.id || index}" must have a numeric "y" position`, path: `nodes[${index}].y` });
|
|
132
|
+
}
|
|
133
|
+
// Width and height are now REQUIRED (was warning)
|
|
134
|
+
if (typeof n.width !== 'number') {
|
|
135
|
+
issues.push({ type: 'error', message: `Node "${n.id || index}" must have a numeric "width"`, path: `nodes[${index}].width` });
|
|
136
|
+
}
|
|
137
|
+
if (typeof n.height !== 'number') {
|
|
138
|
+
issues.push({ type: 'error', message: `Node "${n.id || index}" must have a numeric "height"`, path: `nodes[${index}].height` });
|
|
139
|
+
}
|
|
140
|
+
// Validate node type - must be standard canvas type OR have pv metadata
|
|
141
|
+
const nodeType = n.type;
|
|
142
|
+
const isStandardType = STANDARD_CANVAS_TYPES.includes(nodeType);
|
|
143
|
+
if (!isStandardType) {
|
|
144
|
+
// Custom type - must have pv.nodeType with shape
|
|
145
|
+
if (!n.pv || typeof n.pv !== 'object') {
|
|
146
|
+
issues.push({
|
|
147
|
+
type: 'error',
|
|
148
|
+
message: `Node "${n.id || index}" uses custom type "${nodeType}" but has no "pv" extension`,
|
|
149
|
+
path: `nodes[${index}].pv`,
|
|
150
|
+
suggestion: `Use a standard type (${STANDARD_CANVAS_TYPES.join(', ')}) or add pv.nodeType and pv.shape`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const nodePv = n.pv;
|
|
155
|
+
if (typeof nodePv.nodeType !== 'string' || !nodePv.nodeType) {
|
|
156
|
+
issues.push({
|
|
157
|
+
type: 'error',
|
|
158
|
+
message: `Node "${n.id || index}" with custom type must have "pv.nodeType"`,
|
|
159
|
+
path: `nodes[${index}].pv.nodeType`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (typeof nodePv.shape !== 'string' || !VALID_NODE_SHAPES.includes(nodePv.shape)) {
|
|
163
|
+
issues.push({
|
|
164
|
+
type: 'error',
|
|
165
|
+
message: `Node "${n.id || index}" must have a valid "pv.shape"`,
|
|
166
|
+
path: `nodes[${index}].pv.shape`,
|
|
167
|
+
suggestion: `Valid shapes: ${VALID_NODE_SHAPES.join(', ')}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Validate pv.nodeType references a defined nodeType (for any node with pv.nodeType)
|
|
173
|
+
if (n.pv && typeof n.pv === 'object') {
|
|
174
|
+
const nodePv = n.pv;
|
|
175
|
+
if (typeof nodePv.nodeType === 'string' && nodePv.nodeType) {
|
|
176
|
+
if (allDefinedNodeTypes.length === 0) {
|
|
177
|
+
issues.push({
|
|
178
|
+
type: 'error',
|
|
179
|
+
message: `Node "${n.id || index}" uses nodeType "${nodePv.nodeType}" but no node types are defined`,
|
|
180
|
+
path: `nodes[${index}].pv.nodeType`,
|
|
181
|
+
suggestion: 'Define node types in canvas pv.nodeTypes or library.yaml nodeComponents',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (!allDefinedNodeTypes.includes(nodePv.nodeType)) {
|
|
185
|
+
// Build a helpful suggestion showing where types can be defined
|
|
186
|
+
const sources = [];
|
|
187
|
+
if (canvasNodeTypes.length > 0) {
|
|
188
|
+
sources.push(`canvas pv.nodeTypes: ${canvasNodeTypes.join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
if (libraryNodeTypes.length > 0) {
|
|
191
|
+
sources.push(`library.yaml nodeComponents: ${libraryNodeTypes.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
const suggestion = sources.length > 0
|
|
194
|
+
? `Available types from ${sources.join(' | ')}`
|
|
195
|
+
: 'Define node types in canvas pv.nodeTypes or library.yaml nodeComponents';
|
|
196
|
+
issues.push({
|
|
197
|
+
type: 'error',
|
|
198
|
+
message: `Node "${n.id || index}" uses undefined nodeType "${nodePv.nodeType}"`,
|
|
199
|
+
path: `nodes[${index}].pv.nodeType`,
|
|
200
|
+
suggestion,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// Check edges (optional but validated strictly if present)
|
|
208
|
+
if (c.edges !== undefined && !Array.isArray(c.edges)) {
|
|
209
|
+
issues.push({ type: 'error', message: '"edges" must be an array if present' });
|
|
210
|
+
}
|
|
211
|
+
else if (Array.isArray(c.edges)) {
|
|
212
|
+
const nodeIds = new Set(c.nodes?.map(n => n.id) || []);
|
|
213
|
+
c.edges.forEach((edge, index) => {
|
|
214
|
+
if (!edge || typeof edge !== 'object') {
|
|
215
|
+
issues.push({ type: 'error', message: `Edge at index ${index} must be an object`, path: `edges[${index}]` });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const e = edge;
|
|
219
|
+
if (typeof e.id !== 'string' || !e.id) {
|
|
220
|
+
issues.push({ type: 'error', message: `Edge at index ${index} must have a string "id"`, path: `edges[${index}].id` });
|
|
221
|
+
}
|
|
222
|
+
if (typeof e.fromNode !== 'string') {
|
|
223
|
+
issues.push({ type: 'error', message: `Edge "${e.id || index}" must have a string "fromNode"`, path: `edges[${index}].fromNode` });
|
|
224
|
+
}
|
|
225
|
+
else if (!nodeIds.has(e.fromNode)) {
|
|
226
|
+
issues.push({ type: 'error', message: `Edge "${e.id || index}" references unknown node "${e.fromNode}"`, path: `edges[${index}].fromNode` });
|
|
227
|
+
}
|
|
228
|
+
if (typeof e.toNode !== 'string') {
|
|
229
|
+
issues.push({ type: 'error', message: `Edge "${e.id || index}" must have a string "toNode"`, path: `edges[${index}].toNode` });
|
|
230
|
+
}
|
|
231
|
+
else if (!nodeIds.has(e.toNode)) {
|
|
232
|
+
issues.push({ type: 'error', message: `Edge "${e.id || index}" references unknown node "${e.toNode}"`, path: `edges[${index}].toNode` });
|
|
233
|
+
}
|
|
234
|
+
// Validate edge type if pv.edgeType is specified
|
|
235
|
+
if (e.pv && typeof e.pv === 'object') {
|
|
236
|
+
const edgePv = e.pv;
|
|
237
|
+
if (edgePv.edgeType && typeof edgePv.edgeType === 'string') {
|
|
238
|
+
if (allDefinedEdgeTypes.length === 0) {
|
|
239
|
+
issues.push({
|
|
240
|
+
type: 'error',
|
|
241
|
+
message: `Edge "${e.id || index}" uses edgeType "${edgePv.edgeType}" but no edge types are defined`,
|
|
242
|
+
path: `edges[${index}].pv.edgeType`,
|
|
243
|
+
suggestion: 'Define edge types in canvas pv.edgeTypes or library.yaml edgeComponents',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else if (!allDefinedEdgeTypes.includes(edgePv.edgeType)) {
|
|
247
|
+
// Build a helpful suggestion showing where types can be defined
|
|
248
|
+
const sources = [];
|
|
249
|
+
if (canvasEdgeTypes.length > 0) {
|
|
250
|
+
sources.push(`canvas pv.edgeTypes: ${canvasEdgeTypes.join(', ')}`);
|
|
251
|
+
}
|
|
252
|
+
if (libraryEdgeTypes.length > 0) {
|
|
253
|
+
sources.push(`library.yaml edgeComponents: ${libraryEdgeTypes.join(', ')}`);
|
|
254
|
+
}
|
|
255
|
+
const suggestion = sources.length > 0
|
|
256
|
+
? `Available types from ${sources.join(' | ')}`
|
|
257
|
+
: 'Define edge types in canvas pv.edgeTypes or library.yaml edgeComponents';
|
|
258
|
+
issues.push({
|
|
259
|
+
type: 'error',
|
|
260
|
+
message: `Edge "${e.id || index}" uses undefined edgeType "${edgePv.edgeType}"`,
|
|
261
|
+
path: `edges[${index}].pv.edgeType`,
|
|
262
|
+
suggestion,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return issues;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Validate a single .canvas file
|
|
273
|
+
*/
|
|
274
|
+
function validateFile(filePath, library) {
|
|
275
|
+
const absolutePath = resolve(filePath);
|
|
276
|
+
const relativePath = relative(process.cwd(), absolutePath);
|
|
277
|
+
if (!existsSync(absolutePath)) {
|
|
278
|
+
return {
|
|
279
|
+
file: relativePath,
|
|
280
|
+
isValid: false,
|
|
281
|
+
issues: [{ type: 'error', message: `File not found: ${filePath}` }],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const content = readFileSync(absolutePath, 'utf8');
|
|
286
|
+
const canvas = JSON.parse(content);
|
|
287
|
+
const issues = validateCanvas(canvas, relativePath, library);
|
|
288
|
+
const hasErrors = issues.some(i => i.type === 'error');
|
|
289
|
+
return {
|
|
290
|
+
file: relativePath,
|
|
291
|
+
isValid: !hasErrors,
|
|
292
|
+
issues,
|
|
293
|
+
canvas: hasErrors ? undefined : canvas,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
return {
|
|
298
|
+
file: relativePath,
|
|
299
|
+
isValid: false,
|
|
300
|
+
issues: [{ type: 'error', message: `Failed to parse JSON: ${error.message}` }],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export function createValidateCommand() {
|
|
305
|
+
const command = new Command('validate');
|
|
306
|
+
command
|
|
307
|
+
.description('Validate .canvas configuration files')
|
|
308
|
+
.argument('[files...]', 'Files or glob patterns to validate (defaults to .principal-views/*.canvas)')
|
|
309
|
+
.option('-q, --quiet', 'Only output errors')
|
|
310
|
+
.option('--json', 'Output results as JSON')
|
|
311
|
+
.action(async (files, options) => {
|
|
312
|
+
try {
|
|
313
|
+
// Default to .principal-views/*.canvas if no files specified
|
|
314
|
+
const patterns = files.length > 0 ? files : ['.principal-views/*.canvas'];
|
|
315
|
+
// Find all matching files
|
|
316
|
+
const matchedFiles = await globby(patterns, {
|
|
317
|
+
expandDirectories: false,
|
|
318
|
+
});
|
|
319
|
+
if (matchedFiles.length === 0) {
|
|
320
|
+
if (options.json) {
|
|
321
|
+
console.log(JSON.stringify({ files: [], summary: { total: 0, valid: 0, invalid: 0 } }));
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
console.log(chalk.yellow('No .canvas files found matching the specified patterns.'));
|
|
325
|
+
console.log(chalk.dim(`Patterns searched: ${patterns.join(', ')}`));
|
|
326
|
+
console.log(chalk.dim('\nTo create a new .principal-views folder, run: privu init'));
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Load library from .principal-views directory (used for type validation)
|
|
331
|
+
const principalViewsDir = resolve(process.cwd(), '.principal-views');
|
|
332
|
+
const library = loadLibrary(principalViewsDir);
|
|
333
|
+
// Validate all files
|
|
334
|
+
const results = matchedFiles.map(f => validateFile(f, library));
|
|
335
|
+
const validCount = results.filter(r => r.isValid).length;
|
|
336
|
+
const invalidCount = results.length - validCount;
|
|
337
|
+
// Output results
|
|
338
|
+
if (options.json) {
|
|
339
|
+
console.log(JSON.stringify({
|
|
340
|
+
files: results,
|
|
341
|
+
summary: { total: results.length, valid: validCount, invalid: invalidCount },
|
|
342
|
+
}, null, 2));
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
if (!options.quiet) {
|
|
346
|
+
console.log(chalk.bold(`\nValidating ${results.length} canvas file(s)...\n`));
|
|
347
|
+
}
|
|
348
|
+
for (const result of results) {
|
|
349
|
+
if (result.isValid) {
|
|
350
|
+
if (!options.quiet) {
|
|
351
|
+
console.log(chalk.green(`✓ ${result.file}`));
|
|
352
|
+
const warnings = result.issues.filter(i => i.type === 'warning');
|
|
353
|
+
if (warnings.length > 0) {
|
|
354
|
+
warnings.forEach(w => {
|
|
355
|
+
console.log(chalk.yellow(` ⚠ ${w.message}`));
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
console.log(chalk.red(`✗ ${result.file}`));
|
|
362
|
+
result.issues.forEach(issue => {
|
|
363
|
+
const icon = issue.type === 'error' ? '✗' : '⚠';
|
|
364
|
+
const color = issue.type === 'error' ? chalk.red : chalk.yellow;
|
|
365
|
+
console.log(color(` ${icon} ${issue.message}`));
|
|
366
|
+
if (issue.suggestion) {
|
|
367
|
+
console.log(chalk.dim(` → ${issue.suggestion}`));
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Summary
|
|
373
|
+
console.log('');
|
|
374
|
+
if (invalidCount === 0) {
|
|
375
|
+
console.log(chalk.green(`✓ All ${validCount} file(s) are valid`));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
console.log(chalk.red(`✗ ${invalidCount} of ${results.length} file(s) failed validation`));
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
console.error(chalk.red('Error:'), error.message);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return command;
|
|
389
|
+
}
|