@jasonbelmonti/markdown-engine 3.6.0 → 3.8.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 (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +40 -11
  3. package/dist/cli/declarative-validation.d.ts +3 -9
  4. package/dist/cli/declarative-validation.d.ts.map +1 -1
  5. package/dist/cli/declarative-validation.js +6 -16
  6. package/dist/cli/declarative-validation.js.map +1 -1
  7. package/dist/cli/run.d.ts.map +1 -1
  8. package/dist/cli/run.js +2 -0
  9. package/dist/cli/run.js.map +1 -1
  10. package/dist/cli/validate-args.d.ts +3 -1
  11. package/dist/cli/validate-args.d.ts.map +1 -1
  12. package/dist/cli/validate-args.js +45 -92
  13. package/dist/cli/validate-args.js.map +1 -1
  14. package/dist/cli/validation-output.d.ts +16 -0
  15. package/dist/cli/validation-output.d.ts.map +1 -0
  16. package/dist/cli/validation-output.js +26 -0
  17. package/dist/cli/validation-output.js.map +1 -0
  18. package/dist/cli/validation-report.d.ts +8 -0
  19. package/dist/cli/validation-report.d.ts.map +1 -0
  20. package/dist/cli/validation-report.js +24 -0
  21. package/dist/cli/validation-report.js.map +1 -0
  22. package/dist/cli/validation-summary.d.ts +47 -0
  23. package/dist/cli/validation-summary.d.ts.map +1 -0
  24. package/dist/cli/validation-summary.js +55 -0
  25. package/dist/cli/validation-summary.js.map +1 -0
  26. package/dist/declarative-validation/assertions/evaluator.d.ts.map +1 -1
  27. package/dist/declarative-validation/assertions/evaluator.js +3 -0
  28. package/dist/declarative-validation/assertions/evaluator.js.map +1 -1
  29. package/dist/declarative-validation/assertions/table-rows-complete.d.ts +4 -0
  30. package/dist/declarative-validation/assertions/table-rows-complete.d.ts.map +1 -0
  31. package/dist/declarative-validation/assertions/table-rows-complete.js +40 -0
  32. package/dist/declarative-validation/assertions/table-rows-complete.js.map +1 -0
  33. package/dist/declarative-validation/compiler/assertion-builders.d.ts.map +1 -1
  34. package/dist/declarative-validation/compiler/assertion-builders.js +2 -0
  35. package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -1
  36. package/dist/declarative-validation/compiler/assertions.d.ts.map +1 -1
  37. package/dist/declarative-validation/compiler/assertions.js +1 -0
  38. package/dist/declarative-validation/compiler/assertions.js.map +1 -1
  39. package/dist/declarative-validation/compiler/compatibility.d.ts.map +1 -1
  40. package/dist/declarative-validation/compiler/compatibility.js +1 -0
  41. package/dist/declarative-validation/compiler/compatibility.js.map +1 -1
  42. package/dist/declarative-validation/compiler/plan.d.ts +2 -0
  43. package/dist/declarative-validation/compiler/plan.d.ts.map +1 -1
  44. package/dist/declarative-validation/compiler/table-rows-complete.d.ts +3 -0
  45. package/dist/declarative-validation/compiler/table-rows-complete.d.ts.map +1 -0
  46. package/dist/declarative-validation/compiler/table-rows-complete.js +17 -0
  47. package/dist/declarative-validation/compiler/table-rows-complete.js.map +1 -0
  48. package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -1
  49. package/dist/declarative-validation/profile/assertion-schema.js +14 -0
  50. package/dist/declarative-validation/profile/assertion-schema.js.map +1 -1
  51. package/dist/declarative-validation/profile/index.d.ts +1 -0
  52. package/dist/declarative-validation/profile/index.d.ts.map +1 -1
  53. package/dist/internal/package-version.d.ts +1 -1
  54. package/dist/internal/package-version.js +1 -1
  55. package/dist-bundled/markdown-engine-cli.mjs +266 -149
  56. package/docs/contracts/declarative-validation.md +123 -7
  57. package/fixtures/declarative-validation/examples/table-rows-complete/README.md +65 -0
  58. package/fixtures/declarative-validation/examples/table-rows-complete/blockquote.md +3 -0
  59. package/fixtures/declarative-validation/examples/table-rows-complete/complete.md +3 -0
  60. package/fixtures/declarative-validation/examples/table-rows-complete/consumer-missing.md +50 -0
  61. package/fixtures/declarative-validation/examples/table-rows-complete/consumer-repaired.md +50 -0
  62. package/fixtures/declarative-validation/examples/table-rows-complete/excess.md +3 -0
  63. package/fixtures/declarative-validation/examples/table-rows-complete/explicit-empty.md +3 -0
  64. package/fixtures/declarative-validation/examples/table-rows-complete/header-only.md +2 -0
  65. package/fixtures/declarative-validation/examples/table-rows-complete/legacy.yaml +10 -0
  66. package/fixtures/declarative-validation/examples/table-rows-complete/missing.md +3 -0
  67. package/fixtures/declarative-validation/examples/table-rows-complete/multiple.md +5 -0
  68. package/fixtures/declarative-validation/examples/table-rows-complete/oracle.json +85 -0
  69. package/fixtures/declarative-validation/examples/table-rows-complete/profile.yaml +11 -0
  70. package/fixtures/declarative-validation/examples/table-rows-complete/shape.yaml +8 -0
  71. package/fixtures/declarative-validation/examples/table-rows-complete/syntax.md +3 -0
  72. package/package.json +2 -2
  73. package/scripts/install-markdown-engine-cli.sh +2 -2
@@ -7531,14 +7531,14 @@ function lists(document3, query = {}) {
7531
7531
  });
7532
7532
  }
7533
7533
  function links(document3, query = {}) {
7534
- return (document3.links ?? []).filter((link2) => {
7535
- if (query.targetId !== void 0 && link2.target.id !== query.targetId) {
7534
+ return (document3.links ?? []).filter((link3) => {
7535
+ if (query.targetId !== void 0 && link3.target.id !== query.targetId) {
7536
7536
  return false;
7537
7537
  }
7538
- if (query.url !== void 0 && link2.url !== query.url) {
7538
+ if (query.url !== void 0 && link3.url !== query.url) {
7539
7539
  return false;
7540
7540
  }
7541
- return query.text === void 0 || link2.text === query.text;
7541
+ return query.text === void 0 || link3.text === query.text;
7542
7542
  });
7543
7543
  }
7544
7544
  function linkReferences(document3, query = {}) {
@@ -9003,11 +9003,11 @@ function linkTargets(document3, selector) {
9003
9003
  return documentQueries.links(document3, {
9004
9004
  ...selector.text !== void 0 ? { text: selector.text } : {},
9005
9005
  ...selector.url !== void 0 ? { url: selector.url } : {}
9006
- }).filter((link2) => targetMatchesSection(document3, link2.target.id, selector.section)).map((link2) => ({
9006
+ }).filter((link3) => targetMatchesSection(document3, link3.target.id, selector.section)).map((link3) => ({
9007
9007
  kind: "link",
9008
- link: link2,
9009
- text: link2.text,
9010
- ...sourceFromTarget(document3, link2.target)
9008
+ link: link3,
9009
+ text: link3.text,
9010
+ ...sourceFromTarget(document3, link3.target)
9011
9011
  }));
9012
9012
  }
9013
9013
  function listTargets(document3, selector) {
@@ -9751,6 +9751,49 @@ function targetColumnSource(assertion) {
9751
9751
  };
9752
9752
  }
9753
9753
 
9754
+ // src/declarative-validation/assertions/table-rows-complete.ts
9755
+ function evaluateTableRowsComplete(context) {
9756
+ if (context.selection.targets.length === 0) {
9757
+ return [emptySelectionDiagnostic(context.rule, context.assertionIndex)];
9758
+ }
9759
+ return context.selection.targets.flatMap(
9760
+ (target, targetOrder) => target.kind === "table" ? incompleteRows(target.table, context, targetOrder) : []
9761
+ );
9762
+ }
9763
+ function incompleteRows(table, context, targetOrder) {
9764
+ const expected = columnIndexes(table.cells.filter((cell) => cell.header));
9765
+ const rows = /* @__PURE__ */ new Map();
9766
+ for (const cell of table.cells) {
9767
+ if (!cell.header) {
9768
+ const cells = rows.get(cell.rowIndex) ?? [];
9769
+ cells.push(cell);
9770
+ rows.set(cell.rowIndex, cells);
9771
+ }
9772
+ }
9773
+ return [...rows].sort(([left], [right]) => left - right).flatMap(([rowIndex, cells]) => {
9774
+ const actual = columnIndexes(cells);
9775
+ if (expected.length === actual.length && expected.every((column, i) => column === actual[i])) {
9776
+ return [];
9777
+ }
9778
+ const sourceRange = [...cells].sort((left, right) => left.columnIndex - right.columnIndex).find((cell) => cell.sourceRange !== void 0)?.sourceRange;
9779
+ return [validationDiagnostic(
9780
+ "profile.validation.assertionFailed",
9781
+ `Selected table "${table.target.id}" body row ${rowIndex} must have column positions ${JSON.stringify(expected)}; found ${JSON.stringify(actual)}.`,
9782
+ context.rule,
9783
+ {
9784
+ assertionIndex: context.assertionIndex,
9785
+ targetOrder,
9786
+ targetKey: `table:${table.target.id}:row:${rowIndex}`,
9787
+ diagnosticOrder: rowIndex,
9788
+ ...sourceRange === void 0 ? {} : { sourceRange }
9789
+ }
9790
+ )];
9791
+ });
9792
+ }
9793
+ function columnIndexes(cells) {
9794
+ return cells.map((cell) => cell.columnIndex).sort((left, right) => left - right);
9795
+ }
9796
+
9754
9797
  // src/declarative-validation/assertions/table-columns-exact.ts
9755
9798
  function evaluateTableColumnsExact(assertion, context) {
9756
9799
  if (context.selection.targets.length === 0) {
@@ -10137,6 +10180,8 @@ function evaluateAssertion(assertion, context) {
10137
10180
  return evaluateFrontmatterRequired(assertion, context);
10138
10181
  case "tableColumnsRequired":
10139
10182
  return evaluateTableColumnsRequired(assertion, context);
10183
+ case "tableRowsComplete":
10184
+ return evaluateTableRowsComplete(context);
10140
10185
  case "tableColumnsExact":
10141
10186
  return evaluateTableColumnsExact(assertion, context);
10142
10187
  case "ids":
@@ -11568,6 +11613,7 @@ function selectorAssertionCompatibilityError(assertionName, selector) {
11568
11613
  return selector.target === "document" ? void 0 : selectorCompatibilityMessage(assertionName, "document");
11569
11614
  case "tableColumnsRequired":
11570
11615
  case "tableColumnsExact":
11616
+ case "tableRowsComplete":
11571
11617
  return selector.target === "table" ? void 0 : selectorCompatibilityMessage(assertionName, "table");
11572
11618
  case "ids":
11573
11619
  case "text":
@@ -11689,12 +11735,29 @@ function hasLengthBound(bounds) {
11689
11735
  return bounds.min !== void 0 || bounds.max !== void 0;
11690
11736
  }
11691
11737
 
11738
+ // src/declarative-validation/compiler/table-rows-complete.ts
11739
+ var buildTableRowsCompleteAssertion = (assertion, selector, ruleId, syntaxVersion, diagnostics) => {
11740
+ if (assertion.tableRowsComplete === void 0 || syntaxVersion !== PROFILE_SYNTAX_VERSION_V2) {
11741
+ return void 0;
11742
+ }
11743
+ if (assertion.tableRowsComplete !== true) {
11744
+ diagnostics.push(compileDiagnostic(
11745
+ "profile.config.invalidShape",
11746
+ "tableRowsComplete must be true.",
11747
+ ruleId
11748
+ ));
11749
+ return void 0;
11750
+ }
11751
+ return pushCompatibilityDiagnostic("tableRowsComplete", selector, ruleId, diagnostics) ? { kind: "tableRowsComplete" } : void 0;
11752
+ };
11753
+
11692
11754
  // src/declarative-validation/compiler/assertion-builders.ts
11693
11755
  var ASSERTION_BUILDERS = [
11694
11756
  buildExistsAssertion,
11695
11757
  buildSectionsRequiredAssertion,
11696
11758
  buildTableColumnsRequiredAssertion,
11697
11759
  buildTableColumnsExactAssertion,
11760
+ buildTableRowsCompleteAssertion,
11698
11761
  buildIdsAssertion,
11699
11762
  buildReferencesAssertion,
11700
11763
  buildTableColumnCoverageAssertion,
@@ -12122,6 +12185,7 @@ var ASSERTION_KEYS_V2 = [
12122
12185
  "selectionCount",
12123
12186
  "sourceLength",
12124
12187
  "tableColumnsExact",
12188
+ "tableRowsComplete",
12125
12189
  "tableColumnCoverage",
12126
12190
  "frontmatterShape",
12127
12191
  "textFormat"
@@ -12893,6 +12957,7 @@ var SUPPORTED_ASSERTION_KEYS_V2 = [
12893
12957
  "selectionCount",
12894
12958
  "sourceLength",
12895
12959
  "tableColumnsExact",
12960
+ "tableRowsComplete",
12896
12961
  "tableColumnCoverage",
12897
12962
  "frontmatterShape",
12898
12963
  "textFormat"
@@ -12919,6 +12984,7 @@ function assertionFromValue(value, syntaxVersion, diagnostics) {
12919
12984
  ...supportsV2AssertionSurface(syntaxVersion) ? selectionCountFromValue(value.selectionCount, diagnostics) : {},
12920
12985
  ...supportsV2AssertionSurface(syntaxVersion) ? sourceLengthFromValue(value.sourceLength, diagnostics) : {},
12921
12986
  ...supportsV2AssertionSurface(syntaxVersion) ? tableColumnsExactFromValue(value.tableColumnsExact, diagnostics) : {},
12987
+ ...supportsV2AssertionSurface(syntaxVersion) ? tableRowsCompleteFromValue(value.tableRowsComplete, diagnostics) : {},
12922
12988
  ...frontmatterRequiredFromValue(value.frontmatterRequired, diagnostics),
12923
12989
  ...supportsV2AssertionSurface(syntaxVersion) ? tableColumnCoverageFromValue(value.tableColumnCoverage, diagnostics) : {},
12924
12990
  ...supportsV2AssertionSurface(syntaxVersion) ? frontmatterShapeFromValue2(value.frontmatterShape, diagnostics) : {},
@@ -12974,6 +13040,16 @@ function existsFromValue(value, diagnostics) {
12974
13040
  }
12975
13041
  return { exists: true };
12976
13042
  }
13043
+ function tableRowsCompleteFromValue(value, diagnostics) {
13044
+ if (value === void 0) {
13045
+ return {};
13046
+ }
13047
+ if (value !== true) {
13048
+ diagnostics.push(invalidShape("tableRowsComplete must be true."));
13049
+ return {};
13050
+ }
13051
+ return { tableRowsComplete: true };
13052
+ }
12977
13053
  function sectionsRequiredFromValue(value, diagnostics) {
12978
13054
  if (value === void 0) {
12979
13055
  return {};
@@ -13926,7 +14002,7 @@ function cloneSourceRange2(sourceRange) {
13926
14002
  import { createHash } from "node:crypto";
13927
14003
 
13928
14004
  // src/internal/package-version.ts
13929
- var MARKDOWN_ENGINE_PACKAGE_VERSION = "3.6.0";
14005
+ var MARKDOWN_ENGINE_PACKAGE_VERSION = "3.8.0";
13930
14006
 
13931
14007
  // src/internal/stable-json.ts
13932
14008
  function normalizeStableJsonValue(value) {
@@ -19740,10 +19816,10 @@ function resolveAll(constructs2, events, context) {
19740
19816
  const called = [];
19741
19817
  let index2 = -1;
19742
19818
  while (++index2 < constructs2.length) {
19743
- const resolve2 = constructs2[index2].resolveAll;
19744
- if (resolve2 && !called.includes(resolve2)) {
19745
- events = resolve2(events, context);
19746
- called.push(resolve2);
19819
+ const resolve3 = constructs2[index2].resolveAll;
19820
+ if (resolve3 && !called.includes(resolve3)) {
19821
+ events = resolve3(events, context);
19822
+ called.push(resolve3);
19747
19823
  }
19748
19824
  }
19749
19825
  return events;
@@ -24607,7 +24683,7 @@ function compiler(options) {
24607
24683
  transforms: [],
24608
24684
  canContainEols: ["emphasis", "fragment", "heading", "paragraph", "strong"],
24609
24685
  enter: {
24610
- autolink: opener(link2),
24686
+ autolink: opener(link3),
24611
24687
  autolinkProtocol: onenterdata,
24612
24688
  autolinkEmail: onenterdata,
24613
24689
  atxHeading: opener(heading2),
@@ -24635,7 +24711,7 @@ function compiler(options) {
24635
24711
  htmlTextData: onenterdata,
24636
24712
  image: opener(image2),
24637
24713
  label: buffer,
24638
- link: opener(link2),
24714
+ link: opener(link3),
24639
24715
  listItem: opener(listItem2),
24640
24716
  listItemValue: onenterlistitemvalue,
24641
24717
  listOrdered: opener(list3, onenterlistordered),
@@ -25187,7 +25263,7 @@ function compiler(options) {
25187
25263
  alt: null
25188
25264
  };
25189
25265
  }
25190
- function link2() {
25266
+ function link3() {
25191
25267
  return {
25192
25268
  type: "link",
25193
25269
  title: null,
@@ -25688,9 +25764,9 @@ var VFile = class {
25688
25764
  * @returns {undefined}
25689
25765
  * Nothing.
25690
25766
  */
25691
- set dirname(dirname) {
25767
+ set dirname(dirname2) {
25692
25768
  assertPath(this.basename, "dirname");
25693
- this.path = default2.join(dirname || "", this.basename);
25769
+ this.path = default2.join(dirname2 || "", this.basename);
25694
25770
  }
25695
25771
  /**
25696
25772
  * Get the extname (including dot) (example: `'.js'`).
@@ -26289,7 +26365,7 @@ var Processor = class _Processor extends CallableInstance {
26289
26365
  assertParser("process", this.parser || this.Parser);
26290
26366
  assertCompiler("process", this.compiler || this.Compiler);
26291
26367
  return done ? executor(void 0, done) : new Promise(executor);
26292
- function executor(resolve2, reject) {
26368
+ function executor(resolve3, reject) {
26293
26369
  const realFile = vfile(file);
26294
26370
  const parseTree = (
26295
26371
  /** @type {HeadTree extends undefined ? Node : HeadTree} */
@@ -26320,8 +26396,8 @@ var Processor = class _Processor extends CallableInstance {
26320
26396
  function realDone(error, file2) {
26321
26397
  if (error || !file2) {
26322
26398
  reject(error);
26323
- } else if (resolve2) {
26324
- resolve2(file2);
26399
+ } else if (resolve3) {
26400
+ resolve3(file2);
26325
26401
  } else {
26326
26402
  ok(done, "`done` is defined if `resolve` is not");
26327
26403
  done(void 0, file2);
@@ -26423,7 +26499,7 @@ var Processor = class _Processor extends CallableInstance {
26423
26499
  file = void 0;
26424
26500
  }
26425
26501
  return done ? executor(void 0, done) : new Promise(executor);
26426
- function executor(resolve2, reject) {
26502
+ function executor(resolve3, reject) {
26427
26503
  ok(
26428
26504
  typeof file !== "function",
26429
26505
  "`file` can\u2019t be a `done` anymore, we checked"
@@ -26437,8 +26513,8 @@ var Processor = class _Processor extends CallableInstance {
26437
26513
  );
26438
26514
  if (error) {
26439
26515
  reject(error);
26440
- } else if (resolve2) {
26441
- resolve2(resultingTree);
26516
+ } else if (resolve3) {
26517
+ resolve3(resultingTree);
26442
26518
  } else {
26443
26519
  ok(done, "`done` is defined if `resolve` is not");
26444
26520
  done(void 0, resultingTree, file2);
@@ -27449,13 +27525,7 @@ function compatibilityModeForDocumentVersion(documentVersion) {
27449
27525
  }
27450
27526
 
27451
27527
  // src/cli/validate-args.ts
27452
- var fileFlag = "--file";
27453
- var fileAssignmentPrefix = `${fileFlag}=`;
27454
- var profileFlag = "--profile";
27455
- var profileAssignmentPrefix = `${profileFlag}=`;
27456
- var formatFlag = "--format";
27457
- var formatAssignmentPrefix = `${formatFlag}=`;
27458
- var validateCliUsage = `Usage: markdown-engine validate --file <markdown-file> --profile <profile-file> [--format json]
27528
+ var validateCliUsage = `Usage: markdown-engine validate --file <markdown-file> --profile <profile-file> [--format json] [--output full|summary] [--report-file <new-file>]
27459
27529
 
27460
27530
  Runs declarative validation for one Markdown file and one validation profile.
27461
27531
 
@@ -27463,117 +27533,76 @@ Options:
27463
27533
  --file <markdown-file> Markdown file to validate.
27464
27534
  --profile <profile-file> Declarative validation profile to apply.
27465
27535
  --format json Output JSON. This is the default and only supported format.
27536
+ --output full|summary Full JSON (default) or a compact validation summary.
27537
+ --report-file <new-file> Save full JSON; required for summary. Never overwrites.
27466
27538
  -h, --help Show this help message.
27467
27539
 
27540
+ Summary includes at most 10 top-level diagnostics, errors first, with explicit
27541
+ omission/truncation counts. The report's parent directory must already exist.
27542
+
27468
27543
  Exit status:
27469
27544
  0 Validation completed with no error diagnostics.
27470
27545
  1 Profile/config/compile or validation diagnostics failed.
27471
- 2 Usage, unsupported format, unknown args, or file read error.
27546
+ 2 Usage, unsupported format, unknown args, or file read/write error.
27472
27547
  `;
27473
27548
  function parseValidateCliArgs(args) {
27474
- const filePaths = [];
27475
- const profilePaths = [];
27476
- let format;
27549
+ const values = {
27550
+ "--file": [],
27551
+ "--profile": [],
27552
+ "--format": [],
27553
+ "--output": [],
27554
+ "--report-file": []
27555
+ };
27477
27556
  for (let index2 = 0; index2 < args.length; index2 += 1) {
27478
27557
  const arg = args[index2];
27479
27558
  if (arg === "-h" || arg === "--help") {
27480
27559
  return { kind: "help", usage: validateCliUsage };
27481
27560
  }
27482
- if (arg === fileFlag) {
27483
- const value = readRequiredFlagValue(arg, args, index2);
27484
- if (value.kind === "error") {
27485
- return validateError(value.message);
27486
- }
27487
- filePaths.push(value.value);
27488
- index2 += 1;
27489
- continue;
27490
- }
27491
- if (arg?.startsWith(fileAssignmentPrefix) === true) {
27492
- filePaths.push(arg.slice(fileAssignmentPrefix.length));
27493
- continue;
27494
- }
27495
- if (arg === profileFlag) {
27496
- const value = readRequiredFlagValue(arg, args, index2);
27497
- if (value.kind === "error") {
27498
- return validateError(value.message);
27499
- }
27500
- profilePaths.push(value.value);
27501
- index2 += 1;
27502
- continue;
27503
- }
27504
- if (arg?.startsWith(profileAssignmentPrefix) === true) {
27505
- profilePaths.push(arg.slice(profileAssignmentPrefix.length));
27506
- continue;
27507
- }
27508
- if (arg === formatFlag) {
27509
- if (format !== void 0) {
27510
- return validateError("Expected at most one --format selector.");
27511
- }
27512
- const value = readRequiredFlagValue(arg, args, index2);
27513
- if (value.kind === "error") {
27514
- return validateError(value.message);
27515
- }
27516
- const parsedFormat = parseDeclarativeOutputFormat(value.value);
27517
- if (parsedFormat.kind === "error") {
27518
- return validateError(parsedFormat.message);
27519
- }
27520
- format = parsedFormat.format;
27521
- index2 += 1;
27522
- continue;
27523
- }
27524
- if (arg?.startsWith(formatAssignmentPrefix) === true) {
27525
- if (format !== void 0) {
27526
- return validateError("Expected at most one --format selector.");
27527
- }
27528
- const parsedFormat = parseDeclarativeOutputFormat(
27529
- arg.slice(formatAssignmentPrefix.length)
27530
- );
27531
- if (parsedFormat.kind === "error") {
27532
- return validateError(parsedFormat.message);
27533
- }
27534
- format = parsedFormat.format;
27535
- continue;
27536
- }
27537
- return validateError(`Unknown argument: ${arg ?? ""}`);
27538
- }
27539
- if (filePaths.length === 0) {
27540
- return validateError("Expected exactly one --file target.");
27541
- }
27542
- if (filePaths.length > 1) {
27543
- return validateError("Expected one Markdown file target, received multiple.");
27544
- }
27545
- if (profilePaths.length === 0) {
27546
- return validateError("Expected exactly one --profile target.");
27547
- }
27548
- if (profilePaths.length > 1) {
27549
- return validateError("Expected one profile file target, received multiple.");
27550
- }
27551
- const [filePath] = filePaths;
27552
- const [profilePath] = profilePaths;
27553
- if (filePath === void 0 || filePath.trim() === "") {
27554
- return validateError("File path cannot be empty.");
27555
- }
27556
- if (profilePath === void 0 || profilePath.trim() === "") {
27557
- return validateError("Profile path cannot be empty.");
27561
+ const equals = arg.indexOf("=");
27562
+ const flag = equals === -1 ? arg : arg.slice(0, equals);
27563
+ if (!Object.hasOwn(values, flag)) return validateError(`Unknown argument: ${arg}`);
27564
+ const entries = values[flag];
27565
+ if (flag !== "--file" && flag !== "--profile" && entries.length > 0) {
27566
+ return validateError(`Expected at most one ${flag} selector.`);
27567
+ }
27568
+ const value = equals === -1 ? args[index2 + 1] : arg.slice(equals + 1);
27569
+ if (value === void 0 || equals === -1 && value.startsWith("-")) {
27570
+ return validateError(`Missing value for ${flag}.`);
27571
+ }
27572
+ if (flag === "--format" && value !== "json") {
27573
+ return validateError(`Unsupported validation output format: ${value}.`);
27574
+ }
27575
+ if (flag === "--output" && value !== "full" && value !== "summary") {
27576
+ return validateError(`Unsupported validation output mode: ${value}.`);
27577
+ }
27578
+ entries.push(value);
27579
+ if (equals === -1) index2 += 1;
27580
+ }
27581
+ const files = values["--file"];
27582
+ const profiles = values["--profile"];
27583
+ if (files.length === 0) return validateError("Expected exactly one --file target.");
27584
+ if (files.length > 1) return validateError("Expected one Markdown file target, received multiple.");
27585
+ if (profiles.length === 0) return validateError("Expected exactly one --profile target.");
27586
+ if (profiles.length > 1) return validateError("Expected one profile file target, received multiple.");
27587
+ const filePath = files[0];
27588
+ const profilePath = profiles[0];
27589
+ if (filePath.trim() === "") return validateError("File path cannot be empty.");
27590
+ if (profilePath.trim() === "") return validateError("Profile path cannot be empty.");
27591
+ const output = values["--output"][0] ?? "full";
27592
+ const reportFile = values["--report-file"][0];
27593
+ if (reportFile !== void 0 && reportFile.trim() === "") {
27594
+ return validateError("Report file path cannot be empty.");
27595
+ }
27596
+ if (output === "summary" && reportFile === void 0) {
27597
+ return validateError("--output summary requires --report-file to retain the full result.");
27558
27598
  }
27559
27599
  return {
27560
27600
  kind: "validate",
27561
27601
  filePath,
27562
- format: format ?? "json",
27563
- profilePath
27564
- };
27565
- }
27566
- function readRequiredFlagValue(arg, args, index2) {
27567
- const value = args[index2 + 1];
27568
- if (value === void 0 || value.startsWith("-")) {
27569
- return { kind: "error", message: `Missing value for ${arg}.` };
27570
- }
27571
- return { kind: "ok", value };
27572
- }
27573
- function parseDeclarativeOutputFormat(value) {
27574
- return value === "json" ? { kind: "ok", format: value } : {
27575
- kind: "error",
27576
- message: `Unsupported validation output format: ${value}.`
27602
+ profilePath,
27603
+ format: "json",
27604
+ output,
27605
+ ...reportFile !== void 0 ? { reportFile } : {}
27577
27606
  };
27578
27607
  }
27579
27608
  function validateError(message) {
@@ -27585,8 +27614,8 @@ var documentVersionFlag = "--document-version";
27585
27614
  var documentVersionAssignmentPrefix = `${documentVersionFlag}=`;
27586
27615
  var duplicateDocumentVersionMessage = "Expected at most one --document-version selector.";
27587
27616
  var validateCommand = "validate";
27588
- var fileFlag2 = "--file";
27589
- var fileAssignmentPrefix2 = `${fileFlag2}=`;
27617
+ var fileFlag = "--file";
27618
+ var fileAssignmentPrefix = `${fileFlag}=`;
27590
27619
  var pathFlag = "--path";
27591
27620
  var pathAssignmentPrefix = `${pathFlag}=`;
27592
27621
  var normalizeCliUsage = `Usage: markdown-engine [--document-version <version>] (--file <markdown-file> | --path <markdown-file>)
@@ -27616,7 +27645,7 @@ function parseNormalizeCliArgs(args) {
27616
27645
  if (arg === "-h" || arg === "--help") {
27617
27646
  return { kind: "help", usage: cliUsage };
27618
27647
  }
27619
- if (arg === fileFlag2 || arg === pathFlag) {
27648
+ if (arg === fileFlag || arg === pathFlag) {
27620
27649
  const value = args[index2 + 1];
27621
27650
  if (value === void 0 || value.startsWith("-")) {
27622
27651
  return normalizeError(`Missing value for ${arg}.`);
@@ -27643,8 +27672,8 @@ function parseNormalizeCliArgs(args) {
27643
27672
  }
27644
27673
  continue;
27645
27674
  }
27646
- if (arg?.startsWith(fileAssignmentPrefix2) === true) {
27647
- targets.push(arg.slice(fileAssignmentPrefix2.length));
27675
+ if (arg?.startsWith(fileAssignmentPrefix) === true) {
27676
+ targets.push(arg.slice(fileAssignmentPrefix.length));
27648
27677
  continue;
27649
27678
  }
27650
27679
  if (arg?.startsWith(pathAssignmentPrefix) === true) {
@@ -27731,6 +27760,99 @@ function normalizeMarkdown(input) {
27731
27760
  });
27732
27761
  }
27733
27762
 
27763
+ // src/cli/validation-report.ts
27764
+ import { createHash as createHash2 } from "node:crypto";
27765
+ import { link as link2, mkdtemp, rm, writeFile } from "node:fs/promises";
27766
+ import { dirname, join, resolve as resolve2 } from "node:path";
27767
+ async function saveValidationReport(cwd, reportFile, content3) {
27768
+ const path2 = resolve2(cwd, reportFile);
27769
+ const temporaryDirectory = await mkdtemp(join(dirname(path2), ".markdown-engine-report-"));
27770
+ try {
27771
+ const temporaryFile = join(temporaryDirectory, "report.json");
27772
+ await writeFile(temporaryFile, content3, { encoding: "utf8", mode: 384, flag: "wx" });
27773
+ await link2(temporaryFile, path2);
27774
+ } finally {
27775
+ await rm(temporaryDirectory, { recursive: true, force: true });
27776
+ }
27777
+ return {
27778
+ path: path2,
27779
+ sha256: createHash2("sha256").update(content3, "utf8").digest("hex"),
27780
+ bytes: Buffer.byteLength(content3, "utf8")
27781
+ };
27782
+ }
27783
+
27784
+ // src/cli/validation-summary.ts
27785
+ var diagnosticLimit = 10;
27786
+ var severityOrder = { error: 0, warning: 1, info: 2 };
27787
+ function createValidationSummary(result, exitCode, report) {
27788
+ const diagnostics = [...result.diagnostics].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]).slice(0, diagnosticLimit).map(compactDiagnostic);
27789
+ const counts = { total: result.diagnostics.length, error: 0, warning: 0, info: 0 };
27790
+ for (const diagnostic of result.diagnostics) counts[diagnostic.severity] += 1;
27791
+ const evidence = result.evidence;
27792
+ return {
27793
+ schemaVersion: "markdown-engine.validation-summary.v1",
27794
+ valid: result.valid,
27795
+ stage: "stage" in result ? result.stage : "validation",
27796
+ exitCode,
27797
+ engineVersion: MARKDOWN_ENGINE_PACKAGE_VERSION,
27798
+ runtimeVersion: process.version,
27799
+ ...result.profile !== void 0 ? { profile: result.profile } : {},
27800
+ ...evidence !== void 0 ? {
27801
+ evidence: {
27802
+ inputHash: evidence.inputHash,
27803
+ profileHash: evidence.profileHash,
27804
+ engineVersion: evidence.engineVersion,
27805
+ runtimeVersion: evidence.runtimeVersion,
27806
+ ...evidence.sourceLength !== void 0 ? { sourceLength: evidence.sourceLength } : {}
27807
+ }
27808
+ } : {},
27809
+ diagnosticCounts: counts,
27810
+ diagnostics,
27811
+ diagnosticsOmitted: counts.total - diagnostics.length,
27812
+ diagnosticsTruncated: diagnostics.filter((d) => d.truncatedFields.length > 0).length,
27813
+ report
27814
+ };
27815
+ }
27816
+ function compactDiagnostic(diagnostic) {
27817
+ const truncatedFields = [];
27818
+ const bound = (field, value, max) => {
27819
+ if (value.length <= max) return value;
27820
+ truncatedFields.push(field);
27821
+ return `${value.slice(0, max - 1)}\u2026`;
27822
+ };
27823
+ return {
27824
+ code: bound("code", diagnostic.code, 128),
27825
+ message: bound("message", diagnostic.message, 512),
27826
+ severity: diagnostic.severity,
27827
+ ...diagnostic.ruleId !== void 0 ? { ruleId: bound("ruleId", diagnostic.ruleId, 128) } : {},
27828
+ ...diagnostic.sourceRange !== void 0 ? { sourceRange: diagnostic.sourceRange } : {},
27829
+ truncatedFields
27830
+ };
27831
+ }
27832
+
27833
+ // src/cli/validation-output.ts
27834
+ async function outputValidationResult(result, exitCode, options) {
27835
+ const output = JSON.stringify(normalizeStableJsonValue(result), null, 2) ?? "null";
27836
+ try {
27837
+ const report = options.reportFile === void 0 ? void 0 : await saveValidationReport(options.cwd, options.reportFile, `${output}
27838
+ `);
27839
+ if (options.output === "summary") {
27840
+ if (report === void 0) throw new Error("--output summary requires --report-file.");
27841
+ return {
27842
+ kind: "output",
27843
+ exitCode,
27844
+ output: JSON.stringify(normalizeStableJsonValue(createValidationSummary(result, exitCode, report)))
27845
+ };
27846
+ }
27847
+ return { kind: "output", exitCode, output };
27848
+ } catch (error) {
27849
+ return {
27850
+ kind: "fileError",
27851
+ message: `Unable to save validation report "${options.reportFile ?? ""}": ${error instanceof Error ? error.message : String(error)}`
27852
+ };
27853
+ }
27854
+ }
27855
+
27734
27856
  // src/cli/declarative-validation.ts
27735
27857
  async function runDeclarativeValidationCli(input) {
27736
27858
  const profileText = await readValidationFile(input.profilePath, input.cwd);
@@ -27741,13 +27863,14 @@ async function runDeclarativeValidationCli(input) {
27741
27863
  path: input.profilePath
27742
27864
  });
27743
27865
  if (profileResult.profile === void 0 || hasErrorDiagnostic(profileResult.diagnostics)) {
27744
- return outputResult(profileStageResult(profileResult.diagnostics), 1);
27866
+ return outputValidationResult(profileStageResult(profileResult.diagnostics), 1, input);
27745
27867
  }
27746
27868
  const compileDiagnostics = compileProfileForCli(profileResult.profile);
27747
27869
  if (hasErrorDiagnostic(compileDiagnostics)) {
27748
- return outputResult(
27870
+ return outputValidationResult(
27749
27871
  profileStageResult([...profileResult.diagnostics, ...compileDiagnostics]),
27750
- 1
27872
+ 1,
27873
+ input
27751
27874
  );
27752
27875
  }
27753
27876
  const markdown = await readValidationFile(input.filePath, input.cwd);
@@ -27764,7 +27887,7 @@ async function runDeclarativeValidationCli(input) {
27764
27887
  ...normalizeResult.diagnostics
27765
27888
  ];
27766
27889
  if (hasErrorDiagnostic(normalizeResult.diagnostics)) {
27767
- return outputResult(
27890
+ return outputValidationResult(
27768
27891
  documentDiagnosticsResult(
27769
27892
  normalizeResult.document,
27770
27893
  profileResult.profile,
@@ -27777,7 +27900,8 @@ async function runDeclarativeValidationCli(input) {
27777
27900
  )
27778
27901
  ]
27779
27902
  ),
27780
- 1
27903
+ 1,
27904
+ input
27781
27905
  );
27782
27906
  }
27783
27907
  const validationResult = validateWithProfile(
@@ -27789,9 +27913,10 @@ async function runDeclarativeValidationCli(input) {
27789
27913
  validationResult,
27790
27914
  documentDiagnostics
27791
27915
  );
27792
- return outputResult(
27916
+ return outputValidationResult(
27793
27917
  result,
27794
- hasErrorDiagnostic(result.diagnostics) ? 1 : 0
27918
+ hasErrorDiagnostic(result.diagnostics) ? 1 : 0,
27919
+ input
27795
27920
  );
27796
27921
  }
27797
27922
  function compileProfileForCli(profile) {
@@ -27844,16 +27969,6 @@ function mergeDocumentDiagnostics(validationResult, documentDiagnostics) {
27844
27969
  } : {}
27845
27970
  };
27846
27971
  }
27847
- function outputResult(result, exitCode) {
27848
- return {
27849
- kind: "output",
27850
- exitCode,
27851
- output: serializeDeclarativeValidationCliJsonResult(result)
27852
- };
27853
- }
27854
- function serializeDeclarativeValidationCliJsonResult(result) {
27855
- return JSON.stringify(normalizeStableJsonValue(result), null, 2) ?? "null";
27856
- }
27857
27972
  async function readValidationFile(path2, cwd) {
27858
27973
  try {
27859
27974
  return {
@@ -27886,7 +28001,9 @@ ${argsResult.usage}`);
27886
28001
  cwd: input.cwd,
27887
28002
  filePath: argsResult.filePath,
27888
28003
  format: argsResult.format,
27889
- profilePath: argsResult.profilePath
28004
+ profilePath: argsResult.profilePath,
28005
+ output: argsResult.output,
28006
+ ...argsResult.reportFile !== void 0 ? { reportFile: argsResult.reportFile } : {}
27890
28007
  });
27891
28008
  if (validationResult.kind === "fileError") {
27892
28009
  input.stderr.write(`${validationResult.message}