@principal-ai/principal-view-cli 0.1.19 → 0.1.21
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 +7 -2
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +7 -7
- package/dist/commands/hooks.d.ts.map +1 -1
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +4 -2
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +14 -9
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +5 -2
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +270 -34
- package/dist/index.cjs +523 -101
- package/dist/index.cjs.map +3 -3
- package/package.json +1 -1
|
@@ -17,13 +17,13 @@ function loadLibrary(principalViewsDir) {
|
|
|
17
17
|
if (existsSync(libraryPath)) {
|
|
18
18
|
try {
|
|
19
19
|
const content = readFileSync(libraryPath, 'utf8');
|
|
20
|
-
const library = fileName.endsWith('.json')
|
|
21
|
-
? JSON.parse(content)
|
|
22
|
-
: yaml.load(content);
|
|
20
|
+
const library = fileName.endsWith('.json') ? JSON.parse(content) : yaml.load(content);
|
|
23
21
|
if (library && typeof library === 'object') {
|
|
24
22
|
return {
|
|
25
|
-
nodeComponents: library.nodeComponents ||
|
|
26
|
-
|
|
23
|
+
nodeComponents: library.nodeComponents ||
|
|
24
|
+
{},
|
|
25
|
+
edgeComponents: library.edgeComponents ||
|
|
26
|
+
{},
|
|
27
27
|
raw: library,
|
|
28
28
|
path: libraryPath,
|
|
29
29
|
};
|
|
@@ -51,6 +51,8 @@ function validateLibrary(library) {
|
|
|
51
51
|
if (compDef && typeof compDef === 'object') {
|
|
52
52
|
const comp = compDef;
|
|
53
53
|
checkUnknownFields(comp, ALLOWED_LIBRARY_FIELDS.nodeComponent, `nodeComponents.${compId}`, issues);
|
|
54
|
+
// Validate icon name format (must be PascalCase for Lucide icons)
|
|
55
|
+
validateIconName(comp.icon, `nodeComponents.${compId}.icon`, issues);
|
|
54
56
|
// Check nested fields
|
|
55
57
|
if (comp.size && typeof comp.size === 'object') {
|
|
56
58
|
checkUnknownFields(comp.size, ALLOWED_LIBRARY_FIELDS.nodeComponentSize, `nodeComponents.${compId}.size`, issues);
|
|
@@ -59,6 +61,9 @@ function validateLibrary(library) {
|
|
|
59
61
|
for (const [stateId, stateDef] of Object.entries(comp.states)) {
|
|
60
62
|
if (stateDef && typeof stateDef === 'object') {
|
|
61
63
|
checkUnknownFields(stateDef, ALLOWED_LIBRARY_FIELDS.nodeComponentState, `nodeComponents.${compId}.states.${stateId}`, issues);
|
|
64
|
+
// Validate state icon name format
|
|
65
|
+
const state = stateDef;
|
|
66
|
+
validateIconName(state.icon, `nodeComponents.${compId}.states.${stateId}.icon`, issues);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
}
|
|
@@ -121,6 +126,55 @@ const STANDARD_CANVAS_TYPES = ['text', 'group', 'file', 'link'];
|
|
|
121
126
|
*/
|
|
122
127
|
const VALID_NODE_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'];
|
|
123
128
|
// ============================================================================
|
|
129
|
+
// Icon Validation
|
|
130
|
+
// ============================================================================
|
|
131
|
+
/**
|
|
132
|
+
* Convert kebab-case to PascalCase
|
|
133
|
+
* e.g., "file-text" -> "FileText", "alert-circle" -> "AlertCircle"
|
|
134
|
+
*/
|
|
135
|
+
function kebabToPascalCase(str) {
|
|
136
|
+
return str
|
|
137
|
+
.split('-')
|
|
138
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
139
|
+
.join('');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if a string looks like kebab-case (has hyphens and lowercase)
|
|
143
|
+
*/
|
|
144
|
+
function isKebabCase(str) {
|
|
145
|
+
return str.includes('-') && str === str.toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Validate an icon name and return issues if invalid
|
|
149
|
+
* Icons should be in PascalCase (e.g., "FileText", "Database", "AlertCircle")
|
|
150
|
+
*/
|
|
151
|
+
function validateIconName(iconValue, path, issues) {
|
|
152
|
+
if (typeof iconValue !== 'string' || !iconValue) {
|
|
153
|
+
return; // No icon specified, that's fine
|
|
154
|
+
}
|
|
155
|
+
// Check if it looks like kebab-case
|
|
156
|
+
if (isKebabCase(iconValue)) {
|
|
157
|
+
const suggested = kebabToPascalCase(iconValue);
|
|
158
|
+
issues.push({
|
|
159
|
+
type: 'error',
|
|
160
|
+
message: `Invalid icon name "${iconValue}" - icons must be in PascalCase`,
|
|
161
|
+
path,
|
|
162
|
+
suggestion: `Use "${suggested}" instead of "${iconValue}"`,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Check if first character is lowercase (common mistake)
|
|
167
|
+
if (iconValue[0] === iconValue[0].toLowerCase() && iconValue[0] !== iconValue[0].toUpperCase()) {
|
|
168
|
+
const suggested = iconValue.charAt(0).toUpperCase() + iconValue.slice(1);
|
|
169
|
+
issues.push({
|
|
170
|
+
type: 'error',
|
|
171
|
+
message: `Invalid icon name "${iconValue}" - icons must start with uppercase`,
|
|
172
|
+
path,
|
|
173
|
+
suggestion: `Use "${suggested}" instead of "${iconValue}"`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ============================================================================
|
|
124
178
|
// Allowed Fields Definitions
|
|
125
179
|
// ============================================================================
|
|
126
180
|
/**
|
|
@@ -129,12 +183,27 @@ const VALID_NODE_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'
|
|
|
129
183
|
const ALLOWED_CANVAS_FIELDS = {
|
|
130
184
|
root: ['nodes', 'edges', 'pv'],
|
|
131
185
|
pv: ['version', 'name', 'description', 'nodeTypes', 'edgeTypes', 'pathConfig', 'display'],
|
|
132
|
-
pvPathConfig: [
|
|
186
|
+
pvPathConfig: [
|
|
187
|
+
'projectRoot',
|
|
188
|
+
'captureSource',
|
|
189
|
+
'enableActionPatterns',
|
|
190
|
+
'logLevel',
|
|
191
|
+
'ignoreUnsourced',
|
|
192
|
+
],
|
|
133
193
|
pvDisplay: ['layout', 'theme', 'animations'],
|
|
134
194
|
pvDisplayTheme: ['primary', 'success', 'warning', 'danger', 'info'],
|
|
135
195
|
pvDisplayAnimations: ['enabled', 'speed'],
|
|
136
196
|
pvNodeType: ['label', 'description', 'color', 'icon', 'shape'],
|
|
137
|
-
pvEdgeType: [
|
|
197
|
+
pvEdgeType: [
|
|
198
|
+
'label',
|
|
199
|
+
'style',
|
|
200
|
+
'color',
|
|
201
|
+
'width',
|
|
202
|
+
'directed',
|
|
203
|
+
'animation',
|
|
204
|
+
'labelConfig',
|
|
205
|
+
'activatedBy',
|
|
206
|
+
],
|
|
138
207
|
pvEdgeTypeAnimation: ['type', 'duration', 'color'],
|
|
139
208
|
pvEdgeTypeLabelConfig: ['field', 'position'],
|
|
140
209
|
// Base node fields from JSON Canvas spec
|
|
@@ -145,13 +214,36 @@ const ALLOWED_CANVAS_FIELDS = {
|
|
|
145
214
|
nodeLink: ['url'],
|
|
146
215
|
nodeGroup: ['label', 'background', 'backgroundStyle'],
|
|
147
216
|
// Node pv extension
|
|
148
|
-
nodePv: [
|
|
217
|
+
nodePv: [
|
|
218
|
+
'nodeType',
|
|
219
|
+
'description',
|
|
220
|
+
'shape',
|
|
221
|
+
'icon',
|
|
222
|
+
'fill',
|
|
223
|
+
'stroke',
|
|
224
|
+
'states',
|
|
225
|
+
'sources',
|
|
226
|
+
'actions',
|
|
227
|
+
'dataSchema',
|
|
228
|
+
'layout',
|
|
229
|
+
],
|
|
149
230
|
nodePvState: ['color', 'icon', 'label'],
|
|
150
231
|
nodePvAction: ['pattern', 'event', 'state', 'metadata', 'triggerEdges'],
|
|
151
232
|
nodePvDataSchemaField: ['type', 'required', 'displayInLabel'],
|
|
152
233
|
nodePvLayout: ['layer', 'cluster'],
|
|
153
234
|
// Edge fields
|
|
154
|
-
edge: [
|
|
235
|
+
edge: [
|
|
236
|
+
'id',
|
|
237
|
+
'fromNode',
|
|
238
|
+
'toNode',
|
|
239
|
+
'fromSide',
|
|
240
|
+
'toSide',
|
|
241
|
+
'fromEnd',
|
|
242
|
+
'toEnd',
|
|
243
|
+
'color',
|
|
244
|
+
'label',
|
|
245
|
+
'pv',
|
|
246
|
+
],
|
|
155
247
|
edgePv: ['edgeType', 'style', 'width', 'animation', 'activatedBy'],
|
|
156
248
|
edgePvAnimation: ['type', 'duration', 'color'],
|
|
157
249
|
edgePvActivatedBy: ['action', 'animation', 'direction', 'duration'],
|
|
@@ -161,13 +253,35 @@ const ALLOWED_CANVAS_FIELDS = {
|
|
|
161
253
|
*/
|
|
162
254
|
const ALLOWED_LIBRARY_FIELDS = {
|
|
163
255
|
root: ['version', 'name', 'description', 'nodeComponents', 'edgeComponents', 'connectionRules'],
|
|
164
|
-
nodeComponent: [
|
|
256
|
+
nodeComponent: [
|
|
257
|
+
'description',
|
|
258
|
+
'tags',
|
|
259
|
+
'defaultLabel',
|
|
260
|
+
'shape',
|
|
261
|
+
'icon',
|
|
262
|
+
'color',
|
|
263
|
+
'size',
|
|
264
|
+
'states',
|
|
265
|
+
'sources',
|
|
266
|
+
'actions',
|
|
267
|
+
'dataSchema',
|
|
268
|
+
'layout',
|
|
269
|
+
],
|
|
165
270
|
nodeComponentSize: ['width', 'height'],
|
|
166
271
|
nodeComponentState: ['color', 'icon', 'label'],
|
|
167
272
|
nodeComponentAction: ['pattern', 'event', 'state', 'metadata', 'triggerEdges'],
|
|
168
273
|
nodeComponentDataSchemaField: ['type', 'required', 'displayInLabel', 'label', 'displayInInfo'],
|
|
169
274
|
nodeComponentLayout: ['layer', 'cluster'],
|
|
170
|
-
edgeComponent: [
|
|
275
|
+
edgeComponent: [
|
|
276
|
+
'description',
|
|
277
|
+
'tags',
|
|
278
|
+
'style',
|
|
279
|
+
'color',
|
|
280
|
+
'width',
|
|
281
|
+
'directed',
|
|
282
|
+
'animation',
|
|
283
|
+
'label',
|
|
284
|
+
],
|
|
171
285
|
edgeComponentAnimation: ['type', 'duration', 'color'],
|
|
172
286
|
edgeComponentLabel: ['field', 'position'],
|
|
173
287
|
connectionRule: ['from', 'to', 'via', 'constraints'],
|
|
@@ -293,6 +407,9 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
293
407
|
for (const [typeId, typeDef] of Object.entries(pv.nodeTypes)) {
|
|
294
408
|
if (typeDef && typeof typeDef === 'object') {
|
|
295
409
|
checkUnknownFields(typeDef, ALLOWED_CANVAS_FIELDS.pvNodeType, `pv.nodeTypes.${typeId}`, issues);
|
|
410
|
+
// Validate icon name format
|
|
411
|
+
const nodeType = typeDef;
|
|
412
|
+
validateIconName(nodeType.icon, `pv.nodeTypes.${typeId}.icon`, issues);
|
|
296
413
|
}
|
|
297
414
|
}
|
|
298
415
|
}
|
|
@@ -323,7 +440,11 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
323
440
|
else {
|
|
324
441
|
c.nodes.forEach((node, index) => {
|
|
325
442
|
if (!node || typeof node !== 'object') {
|
|
326
|
-
issues.push({
|
|
443
|
+
issues.push({
|
|
444
|
+
type: 'error',
|
|
445
|
+
message: `Node at index ${index} must be an object`,
|
|
446
|
+
path: `nodes[${index}]`,
|
|
447
|
+
});
|
|
327
448
|
return;
|
|
328
449
|
}
|
|
329
450
|
const n = node;
|
|
@@ -347,23 +468,72 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
347
468
|
// Custom types can have any base fields
|
|
348
469
|
checkUnknownFields(n, allowedNodeFields, nodePath, issues);
|
|
349
470
|
if (typeof n.id !== 'string' || !n.id) {
|
|
350
|
-
issues.push({
|
|
471
|
+
issues.push({
|
|
472
|
+
type: 'error',
|
|
473
|
+
message: `Node at index ${index} must have a string "id"`,
|
|
474
|
+
path: `${nodePath}.id`,
|
|
475
|
+
});
|
|
351
476
|
}
|
|
352
477
|
if (typeof n.type !== 'string') {
|
|
353
|
-
issues.push({
|
|
478
|
+
issues.push({
|
|
479
|
+
type: 'error',
|
|
480
|
+
message: `Node "${nodeLabel}" must have a string "type"`,
|
|
481
|
+
path: `${nodePath}.type`,
|
|
482
|
+
});
|
|
354
483
|
}
|
|
355
484
|
if (typeof n.x !== 'number') {
|
|
356
|
-
issues.push({
|
|
485
|
+
issues.push({
|
|
486
|
+
type: 'error',
|
|
487
|
+
message: `Node "${nodeLabel}" must have a numeric "x" position`,
|
|
488
|
+
path: `${nodePath}.x`,
|
|
489
|
+
});
|
|
357
490
|
}
|
|
358
491
|
if (typeof n.y !== 'number') {
|
|
359
|
-
issues.push({
|
|
492
|
+
issues.push({
|
|
493
|
+
type: 'error',
|
|
494
|
+
message: `Node "${nodeLabel}" must have a numeric "y" position`,
|
|
495
|
+
path: `${nodePath}.y`,
|
|
496
|
+
});
|
|
360
497
|
}
|
|
361
498
|
// Width and height are now REQUIRED (was warning)
|
|
362
499
|
if (typeof n.width !== 'number') {
|
|
363
|
-
issues.push({
|
|
500
|
+
issues.push({
|
|
501
|
+
type: 'error',
|
|
502
|
+
message: `Node "${nodeLabel}" must have a numeric "width"`,
|
|
503
|
+
path: `${nodePath}.width`,
|
|
504
|
+
});
|
|
364
505
|
}
|
|
365
506
|
if (typeof n.height !== 'number') {
|
|
366
|
-
issues.push({
|
|
507
|
+
issues.push({
|
|
508
|
+
type: 'error',
|
|
509
|
+
message: `Node "${nodeLabel}" must have a numeric "height"`,
|
|
510
|
+
path: `${nodePath}.height`,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
// Validate required fields for standard canvas types
|
|
514
|
+
if (nodeType === 'text' && (typeof n.text !== 'string' || !n.text)) {
|
|
515
|
+
issues.push({
|
|
516
|
+
type: 'error',
|
|
517
|
+
message: `Node "${nodeLabel}" has type "text" but is missing required "text" field`,
|
|
518
|
+
path: `${nodePath}.text`,
|
|
519
|
+
suggestion: 'Add a "text" field with markdown content, or change the node type',
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (nodeType === 'file' && (typeof n.file !== 'string' || !n.file)) {
|
|
523
|
+
issues.push({
|
|
524
|
+
type: 'error',
|
|
525
|
+
message: `Node "${nodeLabel}" has type "file" but is missing required "file" field`,
|
|
526
|
+
path: `${nodePath}.file`,
|
|
527
|
+
suggestion: 'Add a "file" field with a file path, or change the node type',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (nodeType === 'link' && (typeof n.url !== 'string' || !n.url)) {
|
|
531
|
+
issues.push({
|
|
532
|
+
type: 'error',
|
|
533
|
+
message: `Node "${nodeLabel}" has type "link" but is missing required "url" field`,
|
|
534
|
+
path: `${nodePath}.url`,
|
|
535
|
+
suggestion: 'Add a "url" field with a URL, or change the node type',
|
|
536
|
+
});
|
|
367
537
|
}
|
|
368
538
|
// Validate node type - must be standard canvas type OR have pv metadata
|
|
369
539
|
const isStandardType = STANDARD_CANVAS_TYPES.includes(nodeType);
|
|
@@ -386,7 +556,8 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
386
556
|
path: `nodes[${index}].pv.nodeType`,
|
|
387
557
|
});
|
|
388
558
|
}
|
|
389
|
-
if (typeof nodePv.shape !== 'string' ||
|
|
559
|
+
if (typeof nodePv.shape !== 'string' ||
|
|
560
|
+
!VALID_NODE_SHAPES.includes(nodePv.shape)) {
|
|
390
561
|
issues.push({
|
|
391
562
|
type: 'error',
|
|
392
563
|
message: `Node "${n.id || index}" must have a valid "pv.shape"`,
|
|
@@ -401,11 +572,16 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
401
572
|
const nodePv = n.pv;
|
|
402
573
|
// Check unknown fields in node pv extension
|
|
403
574
|
checkUnknownFields(nodePv, ALLOWED_CANVAS_FIELDS.nodePv, `${nodePath}.pv`, issues);
|
|
575
|
+
// Validate icon name format (must be PascalCase for Lucide icons)
|
|
576
|
+
validateIconName(nodePv.icon, `${nodePath}.pv.icon`, issues);
|
|
404
577
|
// Check nested pv fields
|
|
405
578
|
if (nodePv.states && typeof nodePv.states === 'object') {
|
|
406
579
|
for (const [stateId, stateDef] of Object.entries(nodePv.states)) {
|
|
407
580
|
if (stateDef && typeof stateDef === 'object') {
|
|
408
581
|
checkUnknownFields(stateDef, ALLOWED_CANVAS_FIELDS.nodePvState, `${nodePath}.pv.states.${stateId}`, issues);
|
|
582
|
+
// Validate state icon name format
|
|
583
|
+
const state = stateDef;
|
|
584
|
+
validateIconName(state.icon, `${nodePath}.pv.states.${stateId}.icon`, issues);
|
|
409
585
|
}
|
|
410
586
|
}
|
|
411
587
|
}
|
|
@@ -464,10 +640,14 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
464
640
|
issues.push({ type: 'error', message: '"edges" must be an array if present' });
|
|
465
641
|
}
|
|
466
642
|
else if (Array.isArray(c.edges)) {
|
|
467
|
-
const nodeIds = new Set(c.nodes?.map(n => n.id) || []);
|
|
643
|
+
const nodeIds = new Set(c.nodes?.map((n) => n.id) || []);
|
|
468
644
|
c.edges.forEach((edge, index) => {
|
|
469
645
|
if (!edge || typeof edge !== 'object') {
|
|
470
|
-
issues.push({
|
|
646
|
+
issues.push({
|
|
647
|
+
type: 'error',
|
|
648
|
+
message: `Edge at index ${index} must be an object`,
|
|
649
|
+
path: `edges[${index}]`,
|
|
650
|
+
});
|
|
471
651
|
return;
|
|
472
652
|
}
|
|
473
653
|
const e = edge;
|
|
@@ -476,19 +656,73 @@ function validateCanvas(canvas, filePath, library) {
|
|
|
476
656
|
// Check unknown fields on edge
|
|
477
657
|
checkUnknownFields(e, ALLOWED_CANVAS_FIELDS.edge, edgePath, issues);
|
|
478
658
|
if (typeof e.id !== 'string' || !e.id) {
|
|
479
|
-
issues.push({
|
|
659
|
+
issues.push({
|
|
660
|
+
type: 'error',
|
|
661
|
+
message: `Edge at index ${index} must have a string "id"`,
|
|
662
|
+
path: `${edgePath}.id`,
|
|
663
|
+
});
|
|
480
664
|
}
|
|
481
665
|
if (typeof e.fromNode !== 'string') {
|
|
482
|
-
issues.push({
|
|
666
|
+
issues.push({
|
|
667
|
+
type: 'error',
|
|
668
|
+
message: `Edge "${edgeLabel}" must have a string "fromNode"`,
|
|
669
|
+
path: `${edgePath}.fromNode`,
|
|
670
|
+
});
|
|
483
671
|
}
|
|
484
672
|
else if (!nodeIds.has(e.fromNode)) {
|
|
485
|
-
issues.push({
|
|
673
|
+
issues.push({
|
|
674
|
+
type: 'error',
|
|
675
|
+
message: `Edge "${edgeLabel}" references unknown node "${e.fromNode}"`,
|
|
676
|
+
path: `${edgePath}.fromNode`,
|
|
677
|
+
});
|
|
486
678
|
}
|
|
487
679
|
if (typeof e.toNode !== 'string') {
|
|
488
|
-
issues.push({
|
|
680
|
+
issues.push({
|
|
681
|
+
type: 'error',
|
|
682
|
+
message: `Edge "${edgeLabel}" must have a string "toNode"`,
|
|
683
|
+
path: `${edgePath}.toNode`,
|
|
684
|
+
});
|
|
489
685
|
}
|
|
490
686
|
else if (!nodeIds.has(e.toNode)) {
|
|
491
|
-
issues.push({
|
|
687
|
+
issues.push({
|
|
688
|
+
type: 'error',
|
|
689
|
+
message: `Edge "${edgeLabel}" references unknown node "${e.toNode}"`,
|
|
690
|
+
path: `${edgePath}.toNode`,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
// Validate fromSide and toSide are present and valid
|
|
694
|
+
const VALID_SIDES = ['top', 'right', 'bottom', 'left'];
|
|
695
|
+
if (typeof e.fromSide !== 'string') {
|
|
696
|
+
issues.push({
|
|
697
|
+
type: 'error',
|
|
698
|
+
message: `Edge "${edgeLabel}" must have a "fromSide" field`,
|
|
699
|
+
path: `${edgePath}.fromSide`,
|
|
700
|
+
suggestion: `Specify which side of the source node the edge starts from: ${VALID_SIDES.join(', ')}`,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
else if (!VALID_SIDES.includes(e.fromSide)) {
|
|
704
|
+
issues.push({
|
|
705
|
+
type: 'error',
|
|
706
|
+
message: `Edge "${edgeLabel}" has invalid fromSide "${e.fromSide}"`,
|
|
707
|
+
path: `${edgePath}.fromSide`,
|
|
708
|
+
suggestion: `Valid values: ${VALID_SIDES.join(', ')}`,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (typeof e.toSide !== 'string') {
|
|
712
|
+
issues.push({
|
|
713
|
+
type: 'error',
|
|
714
|
+
message: `Edge "${edgeLabel}" must have a "toSide" field`,
|
|
715
|
+
path: `${edgePath}.toSide`,
|
|
716
|
+
suggestion: `Specify which side of the target node the edge connects to: ${VALID_SIDES.join(', ')}`,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
else if (!VALID_SIDES.includes(e.toSide)) {
|
|
720
|
+
issues.push({
|
|
721
|
+
type: 'error',
|
|
722
|
+
message: `Edge "${edgeLabel}" has invalid toSide "${e.toSide}"`,
|
|
723
|
+
path: `${edgePath}.toSide`,
|
|
724
|
+
suggestion: `Valid values: ${VALID_SIDES.join(', ')}`,
|
|
725
|
+
});
|
|
492
726
|
}
|
|
493
727
|
// Validate edge pv extension fields
|
|
494
728
|
if (e.pv && typeof e.pv === 'object') {
|
|
@@ -558,7 +792,7 @@ function validateFile(filePath, library) {
|
|
|
558
792
|
const content = readFileSync(absolutePath, 'utf8');
|
|
559
793
|
const canvas = JSON.parse(content);
|
|
560
794
|
const issues = validateCanvas(canvas, relativePath, library);
|
|
561
|
-
const hasErrors = issues.some(i => i.type === 'error');
|
|
795
|
+
const hasErrors = issues.some((i) => i.type === 'error');
|
|
562
796
|
return {
|
|
563
797
|
file: relativePath,
|
|
564
798
|
isValid: !hasErrors,
|
|
@@ -607,7 +841,7 @@ export function createValidateCommand() {
|
|
|
607
841
|
let libraryResult = null;
|
|
608
842
|
if (library && Object.keys(library.raw).length > 0) {
|
|
609
843
|
const libraryIssues = validateLibrary(library);
|
|
610
|
-
const libraryHasErrors = libraryIssues.some(i => i.type === 'error');
|
|
844
|
+
const libraryHasErrors = libraryIssues.some((i) => i.type === 'error');
|
|
611
845
|
libraryResult = {
|
|
612
846
|
file: relative(process.cwd(), library.path),
|
|
613
847
|
isValid: !libraryHasErrors,
|
|
@@ -615,10 +849,10 @@ export function createValidateCommand() {
|
|
|
615
849
|
};
|
|
616
850
|
}
|
|
617
851
|
// Validate all canvas files
|
|
618
|
-
const results = matchedFiles.map(f => validateFile(f, library));
|
|
852
|
+
const results = matchedFiles.map((f) => validateFile(f, library));
|
|
619
853
|
// Combine results
|
|
620
854
|
const allResults = libraryResult ? [libraryResult, ...results] : results;
|
|
621
|
-
const validCount = allResults.filter(r => r.isValid).length;
|
|
855
|
+
const validCount = allResults.filter((r) => r.isValid).length;
|
|
622
856
|
const invalidCount = allResults.length - validCount;
|
|
623
857
|
// Output results
|
|
624
858
|
if (options.json) {
|
|
@@ -629,16 +863,18 @@ export function createValidateCommand() {
|
|
|
629
863
|
}
|
|
630
864
|
else {
|
|
631
865
|
if (!options.quiet) {
|
|
632
|
-
const fileCount = libraryResult
|
|
866
|
+
const fileCount = libraryResult
|
|
867
|
+
? `${results.length} canvas file(s) + library`
|
|
868
|
+
: `${results.length} canvas file(s)`;
|
|
633
869
|
console.log(chalk.bold(`\nValidating ${fileCount}...\n`));
|
|
634
870
|
}
|
|
635
871
|
for (const result of allResults) {
|
|
636
872
|
if (result.isValid) {
|
|
637
873
|
if (!options.quiet) {
|
|
638
874
|
console.log(chalk.green(`✓ ${result.file}`));
|
|
639
|
-
const warnings = result.issues.filter(i => i.type === 'warning');
|
|
875
|
+
const warnings = result.issues.filter((i) => i.type === 'warning');
|
|
640
876
|
if (warnings.length > 0) {
|
|
641
|
-
warnings.forEach(w => {
|
|
877
|
+
warnings.forEach((w) => {
|
|
642
878
|
console.log(chalk.yellow(` ⚠ ${w.message}`));
|
|
643
879
|
});
|
|
644
880
|
}
|
|
@@ -646,7 +882,7 @@ export function createValidateCommand() {
|
|
|
646
882
|
}
|
|
647
883
|
else {
|
|
648
884
|
console.log(chalk.red(`✗ ${result.file}`));
|
|
649
|
-
result.issues.forEach(issue => {
|
|
885
|
+
result.issues.forEach((issue) => {
|
|
650
886
|
const icon = issue.type === 'error' ? '✗' : '⚠';
|
|
651
887
|
const color = issue.type === 'error' ? chalk.red : chalk.yellow;
|
|
652
888
|
console.log(color(` ${icon} ${issue.message}`));
|