@principal-ai/principal-view-cli 0.1.18 → 0.1.20

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 CHANGED
@@ -37,6 +37,7 @@ vv validate --json # Output as JSON
37
37
  ```
38
38
 
39
39
  **Validation checks:**
40
+
40
41
  - Required `vv` extension with name and version
41
42
  - All nodes have required fields (id, type, x, y, width, height)
42
43
  - Custom node types must have `vv.nodeType` and valid `vv.shape`
@@ -96,18 +97,22 @@ Canvas files follow the [JSON Canvas](https://jsoncanvas.org/) specification wit
96
97
  ### Node Types
97
98
 
98
99
  **Standard types** (no additional metadata required):
100
+
99
101
  - `text` - Text content
100
102
  - `group` - Container for other nodes
101
103
  - `file` - File reference
102
104
  - `link` - URL link
103
105
 
104
106
  **Custom types** require `vv` extension:
107
+
105
108
  ```json
106
109
  {
107
110
  "id": "node-1",
108
111
  "type": "custom",
109
- "x": 0, "y": 0,
110
- "width": 200, "height": 100,
112
+ "x": 0,
113
+ "y": 0,
114
+ "width": 200,
115
+ "height": 100,
111
116
  "vv": {
112
117
  "nodeType": "service",
113
118
  "shape": "rectangle"
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,mBAAmB,IAAI,OAAO,CA2C7C"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,mBAAmB,IAAI,OAAO,CAkD7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+KpC,wBAAgB,mBAAmB,IAAI,OAAO,CAgK7C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmLpC,wBAAgB,mBAAmB,IAAI,OAAO,CAsK7C"}
@@ -184,21 +184,21 @@ export function createDoctorCommand() {
184
184
  // Filter issues based on options
185
185
  const filterIssues = (issues) => {
186
186
  if (options.errorsOnly) {
187
- return issues.filter(i => i.type === 'error');
187
+ return issues.filter((i) => i.type === 'error');
188
188
  }
189
189
  if (options.quiet) {
190
- return issues.filter(i => i.type === 'error' || i.type === 'warning');
190
+ return issues.filter((i) => i.type === 'error' || i.type === 'warning');
191
191
  }
192
192
  return issues;
193
193
  };
194
194
  // Count issues
195
- const allIssues = results.flatMap(r => filterIssues(r.issues));
196
- const errorCount = allIssues.filter(i => i.type === 'error').length;
197
- const warningCount = allIssues.filter(i => i.type === 'warning').length;
195
+ const allIssues = results.flatMap((r) => filterIssues(r.issues));
196
+ const errorCount = allIssues.filter((i) => i.type === 'error').length;
197
+ const warningCount = allIssues.filter((i) => i.type === 'warning').length;
198
198
  // Output results
199
199
  if (options.json) {
200
200
  console.log(JSON.stringify({
201
- results: results.map(r => ({
201
+ results: results.map((r) => ({
202
202
  ...r,
203
203
  issues: filterIssues(r.issues),
204
204
  })),
@@ -221,7 +221,7 @@ export function createDoctorCommand() {
221
221
  continue;
222
222
  }
223
223
  if (issues.length > 0) {
224
- const hasErrors = issues.some(i => i.type === 'error');
224
+ const hasErrors = issues.some((i) => i.type === 'error');
225
225
  const icon = hasErrors ? chalk.red('✗') : chalk.yellow('⚠');
226
226
  console.log(`${icon} ${result.configFile}` + chalk.dim(` (${result.configName})`));
227
227
  for (const issue of issues) {
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0NpC,wBAAgB,kBAAkB,IAAI,OAAO,CA6F5C"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4NpC,wBAAgB,kBAAkB,IAAI,OAAO,CA+F5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkIpC,wBAAgB,iBAAiB,IAAI,OAAO,CAoK3C"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkIpC,wBAAgB,iBAAiB,IAAI,OAAO,CA6L3C"}
@@ -198,12 +198,14 @@ edgeComponents: {}
198
198
  if (existsSync(preCommitFile)) {
199
199
  // Check if our hook is already in the file
200
200
  const existingContent = readFileSync(preCommitFile, 'utf8');
201
- if (existingContent.includes('principal-view-cli lint') || existingContent.includes('privu lint')) {
201
+ if (existingContent.includes('principal-view-cli lint') ||
202
+ existingContent.includes('privu lint')) {
202
203
  console.log(chalk.yellow(`Husky pre-commit hook already includes principal view linting`));
203
204
  }
204
205
  else {
205
206
  // Append our lint command to existing pre-commit
206
- const updatedContent = existingContent.trimEnd() + '\n\n# Run principal view linting\nnpx @principal-ai/principal-view-cli lint --quiet\n';
207
+ const updatedContent = existingContent.trimEnd() +
208
+ '\n\n# Run principal view linting\nnpx @principal-ai/principal-view-cli lint --quiet\n';
207
209
  writeFileSync(preCommitFile, updatedContent);
208
210
  console.log(chalk.green(`Updated Husky pre-commit hook with principal view linting`));
209
211
  huskySetup = true;
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8PpC,wBAAgB,iBAAiB,IAAI,OAAO,CA8K3C"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0PpC,wBAAgB,iBAAiB,IAAI,OAAO,CA8L3C"}
@@ -14,11 +14,7 @@ import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefault
14
14
  /**
15
15
  * Config file names in resolution order
16
16
  */
17
- const CONFIG_FILE_NAMES = [
18
- '.privurc.yaml',
19
- '.privurc.yml',
20
- '.privurc.json',
21
- ];
17
+ const CONFIG_FILE_NAMES = ['.privurc.yaml', '.privurc.yml', '.privurc.json'];
22
18
  /**
23
19
  * Find and load privurc config file
24
20
  */
@@ -277,7 +273,11 @@ export function createLintCommand() {
277
273
  patterns = privuConfig.include;
278
274
  }
279
275
  else {
280
- patterns = ['.principal-views/**/*.yaml', '.principal-views/**/*.yml', '.principal-views/**/*.json'];
276
+ patterns = [
277
+ '.principal-views/**/*.yaml',
278
+ '.principal-views/**/*.yml',
279
+ '.principal-views/**/*.json',
280
+ ];
281
281
  }
282
282
  // Find matching files
283
283
  const matchedFiles = await globby(patterns, {
@@ -294,7 +294,10 @@ export function createLintCommand() {
294
294
  });
295
295
  if (configFiles.length === 0) {
296
296
  if (options.json) {
297
- console.log(JSON.stringify({ files: [], summary: { totalFiles: 0, totalErrors: 0, totalWarnings: 0, totalFixable: 0 } }));
297
+ console.log(JSON.stringify({
298
+ files: [],
299
+ summary: { totalFiles: 0, totalErrors: 0, totalWarnings: 0, totalFixable: 0 },
300
+ }));
298
301
  }
299
302
  else {
300
303
  console.log(chalk.yellow('No configuration files found matching the specified patterns.'));
@@ -323,14 +326,16 @@ export function createLintCommand() {
323
326
  if (!loaded) {
324
327
  // File couldn't be loaded - report as error
325
328
  results.set(relativePath, {
326
- violations: [{
329
+ violations: [
330
+ {
327
331
  ruleId: 'parse-error',
328
332
  severity: 'error',
329
333
  file: relativePath,
330
334
  message: `Could not parse file: ${filePath}`,
331
335
  impact: 'File cannot be validated',
332
336
  fixable: false,
333
- }],
337
+ },
338
+ ],
334
339
  errorCount: 1,
335
340
  warningCount: 0,
336
341
  fixableCount: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/commands/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2TpC,wBAAgB,mBAAmB,IAAI,OAAO,CA0B7C"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/commands/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2UpC,wBAAgB,mBAAmB,IAAI,OAAO,CA0B7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgpBpC,wBAAgB,qBAAqB,IAAI,OAAO,CAyG/C"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA26BpC,wBAAgB,qBAAqB,IAAI,OAAO,CAsH/C"}
@@ -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
- edgeComponents: library.edgeComponents || {},
23
+ nodeComponents: library.nodeComponents ||
24
+ {},
25
+ edgeComponents: library.edgeComponents ||
26
+ {},
27
27
  raw: library,
28
28
  path: libraryPath,
29
29
  };
@@ -129,12 +129,27 @@ const VALID_NODE_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'
129
129
  const ALLOWED_CANVAS_FIELDS = {
130
130
  root: ['nodes', 'edges', 'pv'],
131
131
  pv: ['version', 'name', 'description', 'nodeTypes', 'edgeTypes', 'pathConfig', 'display'],
132
- pvPathConfig: ['projectRoot', 'captureSource', 'enableActionPatterns', 'logLevel', 'ignoreUnsourced'],
132
+ pvPathConfig: [
133
+ 'projectRoot',
134
+ 'captureSource',
135
+ 'enableActionPatterns',
136
+ 'logLevel',
137
+ 'ignoreUnsourced',
138
+ ],
133
139
  pvDisplay: ['layout', 'theme', 'animations'],
134
140
  pvDisplayTheme: ['primary', 'success', 'warning', 'danger', 'info'],
135
141
  pvDisplayAnimations: ['enabled', 'speed'],
136
142
  pvNodeType: ['label', 'description', 'color', 'icon', 'shape'],
137
- pvEdgeType: ['label', 'style', 'color', 'width', 'directed', 'animation', 'labelConfig', 'activatedBy'],
143
+ pvEdgeType: [
144
+ 'label',
145
+ 'style',
146
+ 'color',
147
+ 'width',
148
+ 'directed',
149
+ 'animation',
150
+ 'labelConfig',
151
+ 'activatedBy',
152
+ ],
138
153
  pvEdgeTypeAnimation: ['type', 'duration', 'color'],
139
154
  pvEdgeTypeLabelConfig: ['field', 'position'],
140
155
  // Base node fields from JSON Canvas spec
@@ -145,13 +160,36 @@ const ALLOWED_CANVAS_FIELDS = {
145
160
  nodeLink: ['url'],
146
161
  nodeGroup: ['label', 'background', 'backgroundStyle'],
147
162
  // Node pv extension
148
- nodePv: ['nodeType', 'description', 'shape', 'icon', 'fill', 'stroke', 'states', 'sources', 'actions', 'dataSchema', 'layout'],
163
+ nodePv: [
164
+ 'nodeType',
165
+ 'description',
166
+ 'shape',
167
+ 'icon',
168
+ 'fill',
169
+ 'stroke',
170
+ 'states',
171
+ 'sources',
172
+ 'actions',
173
+ 'dataSchema',
174
+ 'layout',
175
+ ],
149
176
  nodePvState: ['color', 'icon', 'label'],
150
177
  nodePvAction: ['pattern', 'event', 'state', 'metadata', 'triggerEdges'],
151
178
  nodePvDataSchemaField: ['type', 'required', 'displayInLabel'],
152
179
  nodePvLayout: ['layer', 'cluster'],
153
180
  // Edge fields
154
- edge: ['id', 'fromNode', 'toNode', 'fromSide', 'toSide', 'fromEnd', 'toEnd', 'color', 'label', 'pv'],
181
+ edge: [
182
+ 'id',
183
+ 'fromNode',
184
+ 'toNode',
185
+ 'fromSide',
186
+ 'toSide',
187
+ 'fromEnd',
188
+ 'toEnd',
189
+ 'color',
190
+ 'label',
191
+ 'pv',
192
+ ],
155
193
  edgePv: ['edgeType', 'style', 'width', 'animation', 'activatedBy'],
156
194
  edgePvAnimation: ['type', 'duration', 'color'],
157
195
  edgePvActivatedBy: ['action', 'animation', 'direction', 'duration'],
@@ -161,13 +199,35 @@ const ALLOWED_CANVAS_FIELDS = {
161
199
  */
162
200
  const ALLOWED_LIBRARY_FIELDS = {
163
201
  root: ['version', 'name', 'description', 'nodeComponents', 'edgeComponents', 'connectionRules'],
164
- nodeComponent: ['description', 'tags', 'defaultLabel', 'shape', 'icon', 'color', 'size', 'states', 'sources', 'actions', 'dataSchema', 'layout'],
202
+ nodeComponent: [
203
+ 'description',
204
+ 'tags',
205
+ 'defaultLabel',
206
+ 'shape',
207
+ 'icon',
208
+ 'color',
209
+ 'size',
210
+ 'states',
211
+ 'sources',
212
+ 'actions',
213
+ 'dataSchema',
214
+ 'layout',
215
+ ],
165
216
  nodeComponentSize: ['width', 'height'],
166
217
  nodeComponentState: ['color', 'icon', 'label'],
167
218
  nodeComponentAction: ['pattern', 'event', 'state', 'metadata', 'triggerEdges'],
168
219
  nodeComponentDataSchemaField: ['type', 'required', 'displayInLabel', 'label', 'displayInInfo'],
169
220
  nodeComponentLayout: ['layer', 'cluster'],
170
- edgeComponent: ['description', 'tags', 'style', 'color', 'width', 'directed', 'animation', 'label'],
221
+ edgeComponent: [
222
+ 'description',
223
+ 'tags',
224
+ 'style',
225
+ 'color',
226
+ 'width',
227
+ 'directed',
228
+ 'animation',
229
+ 'label',
230
+ ],
171
231
  edgeComponentAnimation: ['type', 'duration', 'color'],
172
232
  edgeComponentLabel: ['field', 'position'],
173
233
  connectionRule: ['from', 'to', 'via', 'constraints'],
@@ -323,7 +383,11 @@ function validateCanvas(canvas, filePath, library) {
323
383
  else {
324
384
  c.nodes.forEach((node, index) => {
325
385
  if (!node || typeof node !== 'object') {
326
- issues.push({ type: 'error', message: `Node at index ${index} must be an object`, path: `nodes[${index}]` });
386
+ issues.push({
387
+ type: 'error',
388
+ message: `Node at index ${index} must be an object`,
389
+ path: `nodes[${index}]`,
390
+ });
327
391
  return;
328
392
  }
329
393
  const n = node;
@@ -347,23 +411,72 @@ function validateCanvas(canvas, filePath, library) {
347
411
  // Custom types can have any base fields
348
412
  checkUnknownFields(n, allowedNodeFields, nodePath, issues);
349
413
  if (typeof n.id !== 'string' || !n.id) {
350
- issues.push({ type: 'error', message: `Node at index ${index} must have a string "id"`, path: `${nodePath}.id` });
414
+ issues.push({
415
+ type: 'error',
416
+ message: `Node at index ${index} must have a string "id"`,
417
+ path: `${nodePath}.id`,
418
+ });
351
419
  }
352
420
  if (typeof n.type !== 'string') {
353
- issues.push({ type: 'error', message: `Node "${nodeLabel}" must have a string "type"`, path: `${nodePath}.type` });
421
+ issues.push({
422
+ type: 'error',
423
+ message: `Node "${nodeLabel}" must have a string "type"`,
424
+ path: `${nodePath}.type`,
425
+ });
354
426
  }
355
427
  if (typeof n.x !== 'number') {
356
- issues.push({ type: 'error', message: `Node "${nodeLabel}" must have a numeric "x" position`, path: `${nodePath}.x` });
428
+ issues.push({
429
+ type: 'error',
430
+ message: `Node "${nodeLabel}" must have a numeric "x" position`,
431
+ path: `${nodePath}.x`,
432
+ });
357
433
  }
358
434
  if (typeof n.y !== 'number') {
359
- issues.push({ type: 'error', message: `Node "${nodeLabel}" must have a numeric "y" position`, path: `${nodePath}.y` });
435
+ issues.push({
436
+ type: 'error',
437
+ message: `Node "${nodeLabel}" must have a numeric "y" position`,
438
+ path: `${nodePath}.y`,
439
+ });
360
440
  }
361
441
  // Width and height are now REQUIRED (was warning)
362
442
  if (typeof n.width !== 'number') {
363
- issues.push({ type: 'error', message: `Node "${nodeLabel}" must have a numeric "width"`, path: `${nodePath}.width` });
443
+ issues.push({
444
+ type: 'error',
445
+ message: `Node "${nodeLabel}" must have a numeric "width"`,
446
+ path: `${nodePath}.width`,
447
+ });
364
448
  }
365
449
  if (typeof n.height !== 'number') {
366
- issues.push({ type: 'error', message: `Node "${nodeLabel}" must have a numeric "height"`, path: `${nodePath}.height` });
450
+ issues.push({
451
+ type: 'error',
452
+ message: `Node "${nodeLabel}" must have a numeric "height"`,
453
+ path: `${nodePath}.height`,
454
+ });
455
+ }
456
+ // Validate required fields for standard canvas types
457
+ if (nodeType === 'text' && (typeof n.text !== 'string' || !n.text)) {
458
+ issues.push({
459
+ type: 'error',
460
+ message: `Node "${nodeLabel}" has type "text" but is missing required "text" field`,
461
+ path: `${nodePath}.text`,
462
+ suggestion: 'Add a "text" field with markdown content, or change the node type',
463
+ });
464
+ }
465
+ if (nodeType === 'file' && (typeof n.file !== 'string' || !n.file)) {
466
+ issues.push({
467
+ type: 'error',
468
+ message: `Node "${nodeLabel}" has type "file" but is missing required "file" field`,
469
+ path: `${nodePath}.file`,
470
+ suggestion: 'Add a "file" field with a file path, or change the node type',
471
+ });
472
+ }
473
+ if (nodeType === 'link' && (typeof n.url !== 'string' || !n.url)) {
474
+ issues.push({
475
+ type: 'error',
476
+ message: `Node "${nodeLabel}" has type "link" but is missing required "url" field`,
477
+ path: `${nodePath}.url`,
478
+ suggestion: 'Add a "url" field with a URL, or change the node type',
479
+ });
367
480
  }
368
481
  // Validate node type - must be standard canvas type OR have pv metadata
369
482
  const isStandardType = STANDARD_CANVAS_TYPES.includes(nodeType);
@@ -386,7 +499,8 @@ function validateCanvas(canvas, filePath, library) {
386
499
  path: `nodes[${index}].pv.nodeType`,
387
500
  });
388
501
  }
389
- if (typeof nodePv.shape !== 'string' || !VALID_NODE_SHAPES.includes(nodePv.shape)) {
502
+ if (typeof nodePv.shape !== 'string' ||
503
+ !VALID_NODE_SHAPES.includes(nodePv.shape)) {
390
504
  issues.push({
391
505
  type: 'error',
392
506
  message: `Node "${n.id || index}" must have a valid "pv.shape"`,
@@ -464,10 +578,14 @@ function validateCanvas(canvas, filePath, library) {
464
578
  issues.push({ type: 'error', message: '"edges" must be an array if present' });
465
579
  }
466
580
  else if (Array.isArray(c.edges)) {
467
- const nodeIds = new Set(c.nodes?.map(n => n.id) || []);
581
+ const nodeIds = new Set(c.nodes?.map((n) => n.id) || []);
468
582
  c.edges.forEach((edge, index) => {
469
583
  if (!edge || typeof edge !== 'object') {
470
- issues.push({ type: 'error', message: `Edge at index ${index} must be an object`, path: `edges[${index}]` });
584
+ issues.push({
585
+ type: 'error',
586
+ message: `Edge at index ${index} must be an object`,
587
+ path: `edges[${index}]`,
588
+ });
471
589
  return;
472
590
  }
473
591
  const e = edge;
@@ -476,19 +594,39 @@ function validateCanvas(canvas, filePath, library) {
476
594
  // Check unknown fields on edge
477
595
  checkUnknownFields(e, ALLOWED_CANVAS_FIELDS.edge, edgePath, issues);
478
596
  if (typeof e.id !== 'string' || !e.id) {
479
- issues.push({ type: 'error', message: `Edge at index ${index} must have a string "id"`, path: `${edgePath}.id` });
597
+ issues.push({
598
+ type: 'error',
599
+ message: `Edge at index ${index} must have a string "id"`,
600
+ path: `${edgePath}.id`,
601
+ });
480
602
  }
481
603
  if (typeof e.fromNode !== 'string') {
482
- issues.push({ type: 'error', message: `Edge "${edgeLabel}" must have a string "fromNode"`, path: `${edgePath}.fromNode` });
604
+ issues.push({
605
+ type: 'error',
606
+ message: `Edge "${edgeLabel}" must have a string "fromNode"`,
607
+ path: `${edgePath}.fromNode`,
608
+ });
483
609
  }
484
610
  else if (!nodeIds.has(e.fromNode)) {
485
- issues.push({ type: 'error', message: `Edge "${edgeLabel}" references unknown node "${e.fromNode}"`, path: `${edgePath}.fromNode` });
611
+ issues.push({
612
+ type: 'error',
613
+ message: `Edge "${edgeLabel}" references unknown node "${e.fromNode}"`,
614
+ path: `${edgePath}.fromNode`,
615
+ });
486
616
  }
487
617
  if (typeof e.toNode !== 'string') {
488
- issues.push({ type: 'error', message: `Edge "${edgeLabel}" must have a string "toNode"`, path: `${edgePath}.toNode` });
618
+ issues.push({
619
+ type: 'error',
620
+ message: `Edge "${edgeLabel}" must have a string "toNode"`,
621
+ path: `${edgePath}.toNode`,
622
+ });
489
623
  }
490
624
  else if (!nodeIds.has(e.toNode)) {
491
- issues.push({ type: 'error', message: `Edge "${edgeLabel}" references unknown node "${e.toNode}"`, path: `${edgePath}.toNode` });
625
+ issues.push({
626
+ type: 'error',
627
+ message: `Edge "${edgeLabel}" references unknown node "${e.toNode}"`,
628
+ path: `${edgePath}.toNode`,
629
+ });
492
630
  }
493
631
  // Validate edge pv extension fields
494
632
  if (e.pv && typeof e.pv === 'object') {
@@ -558,7 +696,7 @@ function validateFile(filePath, library) {
558
696
  const content = readFileSync(absolutePath, 'utf8');
559
697
  const canvas = JSON.parse(content);
560
698
  const issues = validateCanvas(canvas, relativePath, library);
561
- const hasErrors = issues.some(i => i.type === 'error');
699
+ const hasErrors = issues.some((i) => i.type === 'error');
562
700
  return {
563
701
  file: relativePath,
564
702
  isValid: !hasErrors,
@@ -607,7 +745,7 @@ export function createValidateCommand() {
607
745
  let libraryResult = null;
608
746
  if (library && Object.keys(library.raw).length > 0) {
609
747
  const libraryIssues = validateLibrary(library);
610
- const libraryHasErrors = libraryIssues.some(i => i.type === 'error');
748
+ const libraryHasErrors = libraryIssues.some((i) => i.type === 'error');
611
749
  libraryResult = {
612
750
  file: relative(process.cwd(), library.path),
613
751
  isValid: !libraryHasErrors,
@@ -615,10 +753,10 @@ export function createValidateCommand() {
615
753
  };
616
754
  }
617
755
  // Validate all canvas files
618
- const results = matchedFiles.map(f => validateFile(f, library));
756
+ const results = matchedFiles.map((f) => validateFile(f, library));
619
757
  // Combine results
620
758
  const allResults = libraryResult ? [libraryResult, ...results] : results;
621
- const validCount = allResults.filter(r => r.isValid).length;
759
+ const validCount = allResults.filter((r) => r.isValid).length;
622
760
  const invalidCount = allResults.length - validCount;
623
761
  // Output results
624
762
  if (options.json) {
@@ -629,16 +767,18 @@ export function createValidateCommand() {
629
767
  }
630
768
  else {
631
769
  if (!options.quiet) {
632
- const fileCount = libraryResult ? `${results.length} canvas file(s) + library` : `${results.length} canvas file(s)`;
770
+ const fileCount = libraryResult
771
+ ? `${results.length} canvas file(s) + library`
772
+ : `${results.length} canvas file(s)`;
633
773
  console.log(chalk.bold(`\nValidating ${fileCount}...\n`));
634
774
  }
635
775
  for (const result of allResults) {
636
776
  if (result.isValid) {
637
777
  if (!options.quiet) {
638
778
  console.log(chalk.green(`✓ ${result.file}`));
639
- const warnings = result.issues.filter(i => i.type === 'warning');
779
+ const warnings = result.issues.filter((i) => i.type === 'warning');
640
780
  if (warnings.length > 0) {
641
- warnings.forEach(w => {
781
+ warnings.forEach((w) => {
642
782
  console.log(chalk.yellow(` ⚠ ${w.message}`));
643
783
  });
644
784
  }
@@ -646,7 +786,7 @@ export function createValidateCommand() {
646
786
  }
647
787
  else {
648
788
  console.log(chalk.red(`✗ ${result.file}`));
649
- result.issues.forEach(issue => {
789
+ result.issues.forEach((issue) => {
650
790
  const icon = issue.type === 'error' ? '✗' : '⚠';
651
791
  const color = issue.type === 'error' ? chalk.red : chalk.yellow;
652
792
  console.log(color(` ${icon} ${issue.message}`));