@devfellowship/components 3.2.3 → 3.3.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/cli.js +132 -10
- package/dist/index.cjs +523 -0
- package/dist/index.d.cts +1061 -1
- package/dist/index.d.ts +1061 -1
- package/dist/index.js +467 -0
- package/package.json +1 -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.
|
|
500
|
-
|
|
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
|
-
|
|
503
|
-
|
|
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
|
-
|
|
632
|
+
console.log(chalk2.green("OK"), path, "conforms to schema v1.");
|
|
633
|
+
process.exit(0);
|
|
512
634
|
});
|
|
513
635
|
}
|
|
514
636
|
|