@novedu/cli 0.2.0 → 0.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.
Files changed (2) hide show
  1. package/dist/main.js +46 -3
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -40,6 +40,26 @@ function warning(code, message, extra = {}) {
40
40
  ...extra
41
41
  };
42
42
  }
43
+ /**
44
+ * Flattens a treeified Zod error into `path: message` lines so a generic
45
+ * "Document does not match the expected structure" becomes actionable — e.g.
46
+ * `Unrecognized key: "nae"` and `name: Invalid input: expected string`.
47
+ * Framework-agnostic: shared by the web UI (`ErrorList`), the share-tutor
48
+ * action, and the CLI formatter, so a schema error reads the same everywhere.
49
+ */
50
+ function formatZodIssues(zodIssues) {
51
+ const out = [];
52
+ const walk = (node, path) => {
53
+ if (!node || typeof node !== "object") return;
54
+ if (Array.isArray(node.errors)) for (const message of node.errors) out.push(path.length ? `${path.join(".")}: ${message}` : message);
55
+ if (node.properties) for (const [key, child] of Object.entries(node.properties)) walk(child, [...path, key]);
56
+ if (Array.isArray(node.items)) node.items.forEach((child, index) => {
57
+ walk(child, [...path, String(index)]);
58
+ });
59
+ };
60
+ walk(zodIssues, []);
61
+ return out;
62
+ }
43
63
  //#endregion
44
64
  //#region ../lib/tutors/consistency.ts
45
65
  /** Compare a supplied value against its declared property type. Returns null when it matches. */
@@ -635,6 +655,20 @@ function context(item) {
635
655
  function renderWarnings(warnings) {
636
656
  return warnings.map((w) => ` ${yellow("⚠")} ${yellow(w.code)} ${w.message}${context(w)}`);
637
657
  }
658
+ /**
659
+ * Render each error as a line, with any flattened Zod schema-issue detail
660
+ * indented beneath it — so a generic "Document does not match the expected
661
+ * structure" is followed by the actual field paths (e.g. `Unrecognized key:
662
+ * "nae"`), matching what the web UI shows.
663
+ */
664
+ function renderErrors(errors) {
665
+ const lines = [];
666
+ for (const e of errors) {
667
+ lines.push(` ${red("✗")} ${red(e.code)} ${e.message}${context(e)}`);
668
+ if (e.zodIssues) for (const issue of formatZodIssues(e.zodIssues)) lines.push(` ${dim(issue)}`);
669
+ }
670
+ return lines;
671
+ }
638
672
  function formatResult(result, source) {
639
673
  const lines = [];
640
674
  if (result.ok) {
@@ -652,7 +686,7 @@ function formatResult(result, source) {
652
686
  lines.push(red(`✘ Invalid tutor`) + dim(` — ${source}`));
653
687
  lines.push("");
654
688
  lines.push(red(`${result.errors.length} error(s):`));
655
- for (const e of result.errors) lines.push(` ${red("✗")} ${red(e.code)} ${e.message}${context(e)}`);
689
+ lines.push(...renderErrors(result.errors));
656
690
  if (result.warnings.length) {
657
691
  lines.push("");
658
692
  lines.push(yellow(`${result.warnings.length} warning(s):`));
@@ -677,7 +711,7 @@ function formatFragmentResult(result, source) {
677
711
  lines.push(red(`✘ Invalid fragment file`) + dim(` — ${source}`));
678
712
  lines.push("");
679
713
  lines.push(red(`${result.errors.length} error(s):`));
680
- for (const e of result.errors) lines.push(` ${red("✗")} ${red(e.code)} ${e.message}${context(e)}`);
714
+ lines.push(...renderErrors(result.errors));
681
715
  if (result.warnings.length) {
682
716
  lines.push("");
683
717
  lines.push(yellow(`${result.warnings.length} warning(s):`));
@@ -723,7 +757,16 @@ function runValidate(pathOrUrl, kind) {
723
757
  }));
724
758
  }
725
759
  function registerValidate(program) {
726
- program.command("validate").description("Validate a tutor YAML (default) or a fragment library by local path or public http(s) URL").argument("<pathOrUrl>", "path to a tutor or fragment YAML file, or a public http(s) URL").option("--kind <kind>", "what the file is: 'tutor' (default) or 'fragment'", "tutor").option("--json", "print the raw validation result as JSON").action(async (pathOrUrl, options) => {
760
+ program.command("validate").description("Validate a tutor YAML (default) or a fragment library by local path or public http(s) URL").argument("<pathOrUrl>", "path to a tutor or fragment YAML file, or a public http(s) URL").option("--kind <kind>", "what the file is: 'tutor' (default) or 'fragment'", "tutor").option("--json", "print the raw validation result as JSON").addHelpText("after", `
761
+ Examples:
762
+ # Validate a tutor (also strict-renders every fragment in every referenced library)
763
+ $ novedu-cli validate ./tutors/my-tutor.yaml
764
+
765
+ # Validate a fragment library on its own
766
+ $ novedu-cli validate ./tutors/my-fragments.yaml --kind fragment
767
+
768
+ # Machine-readable output for CI
769
+ $ novedu-cli validate https://example.com/tutor.yaml --json`).action(async (pathOrUrl, options) => {
727
770
  if (options.kind !== void 0 && options.kind !== "tutor" && options.kind !== "fragment") {
728
771
  console.error(`Invalid --kind "${options.kind}": expected "tutor" or "fragment".`);
729
772
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novedu/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Command-line companion for the Novedu chat app. Validates tutor YAML definitions and fragment libraries (more commands to follow).",
5
5
  "type": "module",
6
6
  "repository": {