@devfellowship/components 3.2.2 → 3.2.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +132 -10
  2. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -464,11 +464,121 @@ var SCHEMA_V1 = {
464
464
  }
465
465
  };
466
466
 
467
+ // node_modules/@devfellowship/ux-paths-spec/dist/index.js
468
+ function stepScreenId(step) {
469
+ return typeof step === "string" ? step : step.screen;
470
+ }
471
+ function flowScreenIds(flow) {
472
+ return [flow.start, ...flow.steps.map(stepScreenId)];
473
+ }
474
+ function danglingScreenIds(doc) {
475
+ const declared = new Set((doc.screens ?? []).map((screen) => screen.id));
476
+ const dangling = /* @__PURE__ */ new Set();
477
+ for (const flow of doc.flows ?? []) {
478
+ for (const id of flowScreenIds(flow)) {
479
+ if (!declared.has(id))
480
+ dangling.add(id);
481
+ }
482
+ }
483
+ return [...dangling];
484
+ }
485
+
467
486
  // src/cli/ux-paths/lib/load-schema.ts
468
487
  async function loadSchemaV1() {
469
488
  return SCHEMA_V1;
470
489
  }
471
490
 
491
+ // src/cli/ux-paths/lib/structural-checks.ts
492
+ function screensOf(doc) {
493
+ const screens = doc?.screens;
494
+ return Array.isArray(screens) ? screens : [];
495
+ }
496
+ function flowsOf(doc) {
497
+ const flows = doc?.flows;
498
+ return Array.isArray(flows) ? flows : [];
499
+ }
500
+ function checkUniqueIds(doc) {
501
+ const seen = /* @__PURE__ */ new Set();
502
+ const duplicates = /* @__PURE__ */ new Set();
503
+ for (const screen of screensOf(doc)) {
504
+ const id = screen?.id;
505
+ if (typeof id !== "string") continue;
506
+ if (seen.has(id)) duplicates.add(id);
507
+ else seen.add(id);
508
+ }
509
+ if (duplicates.size === 0) return null;
510
+ return {
511
+ rule: "unique-ids",
512
+ message: `${duplicates.size} duplicate screen id${duplicates.size === 1 ? "" : "s"}`,
513
+ detail: [...duplicates].sort().map((id) => ` - "${id}" is declared more than once`)
514
+ };
515
+ }
516
+ function checkWholeFlows(doc) {
517
+ const normalised = {
518
+ screens: screensOf(doc),
519
+ flows: flowsOf(doc).map((flow) => ({
520
+ ...flow,
521
+ steps: Array.isArray(flow?.steps) ? flow.steps : []
522
+ }))
523
+ };
524
+ const dangling = danglingScreenIds(normalised);
525
+ if (dangling.length === 0) return null;
526
+ const flows = flowsOf(doc);
527
+ const where = /* @__PURE__ */ new Map();
528
+ flows.forEach((flow, i) => {
529
+ const f = flow;
530
+ const label = typeof f?.name === "string" ? f.name : `flows[${i}]`;
531
+ const referenced = [
532
+ f?.start,
533
+ ...(Array.isArray(f?.steps) ? f.steps : []).map(
534
+ (step) => typeof step === "string" ? step : step?.screen
535
+ )
536
+ ];
537
+ for (const id of referenced) {
538
+ if (typeof id !== "string" || !dangling.includes(id)) continue;
539
+ if (!where.has(id)) where.set(id, /* @__PURE__ */ new Set());
540
+ where.get(id)?.add(label);
541
+ }
542
+ });
543
+ return {
544
+ rule: "whole-flows",
545
+ message: `${dangling.length} screen id${dangling.length === 1 ? "" : "s"} referenced by a flow that no screen declares`,
546
+ detail: dangling.slice().sort().map((id) => {
547
+ const labels = [...where.get(id) ?? []].sort().join(", ");
548
+ return ` - "${id}"${labels ? ` (referenced by: ${labels})` : ""}`;
549
+ })
550
+ };
551
+ }
552
+ function checkActionTargets(doc) {
553
+ const screens = screensOf(doc);
554
+ const declared = new Set(
555
+ screens.map((s) => s?.id).filter((id) => typeof id === "string")
556
+ );
557
+ const dangling = /* @__PURE__ */ new Map();
558
+ for (const screen of screens) {
559
+ const actions = Array.isArray(screen?.actions) ? screen.actions : [];
560
+ for (const action of actions) {
561
+ const target = action?.next_screen;
562
+ if (typeof target !== "string" || target.trim() === "") continue;
563
+ if (declared.has(target)) continue;
564
+ const site = `${typeof screen?.id === "string" ? screen.id : "<screen with no id>"}.${typeof action?.id === "string" ? action.id : "<action with no id>"}`;
565
+ if (!dangling.has(target)) dangling.set(target, /* @__PURE__ */ new Set());
566
+ dangling.get(target)?.add(site);
567
+ }
568
+ }
569
+ if (dangling.size === 0) return null;
570
+ return {
571
+ rule: "action-targets",
572
+ message: `${dangling.size} action target${dangling.size === 1 ? "" : "s"} that no screen declares`,
573
+ detail: [...dangling.keys()].sort().map((id) => ` - "${id}" (targeted by: ${[...dangling.get(id) ?? []].sort().join(", ")})`)
574
+ };
575
+ }
576
+ function checkStructure(doc) {
577
+ return [checkUniqueIds(doc), checkWholeFlows(doc), checkActionTargets(doc)].filter(
578
+ (p) => p !== null
579
+ );
580
+ }
581
+
472
582
  // src/cli/ux-paths/commands/validate.ts
473
583
  function registerValidate(program2) {
474
584
  program2.command("validate [path]").description("Validate a flows.json against the DFL UX Paths v1 schema.").action(async (maybePath) => {
@@ -495,20 +605,32 @@ function registerValidate(program2) {
495
605
  }
496
606
  const validate = ajv.compile(schema);
497
607
  const ok = validate(doc);
498
- if (ok) {
499
- console.log(chalk2.green("OK"), path, "conforms to schema v1.");
500
- process.exit(0);
608
+ if (!ok) {
609
+ console.error(chalk2.red("FAIL"), path);
610
+ for (const err of validate.errors ?? []) {
611
+ console.error(
612
+ chalk2.yellow(" -"),
613
+ err.instancePath || "<root>",
614
+ err.message,
615
+ err.params ? JSON.stringify(err.params) : ""
616
+ );
617
+ }
618
+ process.exit(1);
501
619
  }
502
- console.error(chalk2.red("FAIL"), path);
503
- for (const err of validate.errors ?? []) {
620
+ const problems = checkStructure(doc);
621
+ if (problems.length > 0) {
622
+ console.error(chalk2.red("FAIL"), path);
504
623
  console.error(
505
- chalk2.yellow(" -"),
506
- err.instancePath || "<root>",
507
- err.message,
508
- err.params ? JSON.stringify(err.params) : ""
624
+ chalk2.yellow(" the document satisfies schema v1 but is not internally consistent:")
509
625
  );
626
+ for (const problem of problems) {
627
+ console.error(chalk2.yellow(` [${problem.rule}]`), problem.message);
628
+ for (const line of problem.detail) console.error(chalk2.dim(` ${line}`));
629
+ }
630
+ process.exit(1);
510
631
  }
511
- process.exit(1);
632
+ console.log(chalk2.green("OK"), path, "conforms to schema v1.");
633
+ process.exit(0);
512
634
  });
513
635
  }
514
636
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devfellowship/components",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
4
4
  "description": "DFL Design System — UI components, hooks, utils and providers",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -93,6 +93,7 @@
93
93
  "@changesets/changelog-github": "^0.7.0",
94
94
  "@changesets/cli": "^2.31.0",
95
95
  "@dagrejs/dagre": "^3.1.1",
96
+ "@devfellowship/ux-paths-capture": "^0.1.1",
96
97
  "@storybook/addon-a11y": "^9.1.20",
97
98
  "@storybook/addon-themes": "^9.1.20",
98
99
  "@storybook/react": "^9.1.20",