repo-contract 0.2.0 → 0.3.2

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/index.js CHANGED
@@ -74,6 +74,20 @@ var ParserDependencyMissingError = class extends RepoContractError {
74
74
  this.format = format;
75
75
  }
76
76
  };
77
+ var StandardSchemaValidateThrewError = class extends RepoContractError {
78
+ /** Always `"REPO_CONTRACT_STANDARD_SCHEMA_VALIDATE_THREW"`. */
79
+ code = "REPO_CONTRACT_STANDARD_SCHEMA_VALIDATE_THREW";
80
+ /** The id of the check whose `output.schema` threw during validation. */
81
+ checkId;
82
+ constructor(checkId, cause) {
83
+ super(
84
+ `Check "${checkId}"'s output.schema["~standard"].validate() threw instead of returning a Result`,
85
+ { cause }
86
+ );
87
+ this.name = "StandardSchemaValidateThrewError";
88
+ this.checkId = checkId;
89
+ }
90
+ };
77
91
  var PolicyThrewError = class extends RepoContractError {
78
92
  /** Always `"REPO_CONTRACT_POLICY_THREW"`. */
79
93
  code = "REPO_CONTRACT_POLICY_THREW";
@@ -358,13 +372,46 @@ function validateOutput(checkId, output) {
358
372
  if (output === null || typeof output !== "object") {
359
373
  throw new InvalidCheckConfigError(checkId, "output must be an object when provided.");
360
374
  }
361
- const { format } = output;
375
+ const { format, schema } = output;
362
376
  if (typeof format !== "string" || !OUTPUT_FORMATS.includes(format)) {
363
377
  throw new InvalidCheckConfigError(
364
378
  checkId,
365
379
  `output.format must be one of ${OUTPUT_FORMATS.map((f) => `"${f}"`).join(", ")}.`
366
380
  );
367
381
  }
382
+ validateOutputSchema(checkId, schema);
383
+ }
384
+ function validateOutputSchema(checkId, schema) {
385
+ if (schema === void 0) return;
386
+ if (schema === null || typeof schema !== "object" && typeof schema !== "function") {
387
+ throw new InvalidCheckConfigError(
388
+ checkId,
389
+ "output.schema must be a Standard Schema-compliant object or function when provided (see https://standardschema.dev)."
390
+ );
391
+ }
392
+ const standard = schema["~standard"];
393
+ if (standard === null || typeof standard !== "object") {
394
+ throw new InvalidCheckConfigError(
395
+ checkId,
396
+ 'output.schema must have a "~standard" property (see https://standardschema.dev).'
397
+ );
398
+ }
399
+ const { version, vendor, validate } = standard;
400
+ if (version !== 1) {
401
+ throw new InvalidCheckConfigError(checkId, 'output.schema["~standard"].version must be 1.');
402
+ }
403
+ if (typeof vendor !== "string") {
404
+ throw new InvalidCheckConfigError(
405
+ checkId,
406
+ 'output.schema["~standard"].vendor must be a string.'
407
+ );
408
+ }
409
+ if (typeof validate !== "function") {
410
+ throw new InvalidCheckConfigError(
411
+ checkId,
412
+ 'output.schema["~standard"].validate must be a function.'
413
+ );
414
+ }
368
415
  }
369
416
  function validatePolicy(checkId, policy) {
370
417
  if (typeof policy !== "function") {
@@ -405,6 +452,28 @@ function validateDependencyGraph(checks) {
405
452
  }
406
453
  }
407
454
 
455
+ // src/parsing/format-schema-issues.ts
456
+ function formatSchemaIssues(issues) {
457
+ return `Schema validation failed: ${issues.map(formatIssue).join("; ")}`;
458
+ }
459
+ function formatIssue(issue) {
460
+ const path = formatPath(issue.path);
461
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
462
+ }
463
+ function formatPath(path) {
464
+ if (path === void 0 || path.length === 0) return "";
465
+ return path.map(
466
+ (segment) => (
467
+ // eslint-disable-next-line secure-coding/no-improper-type-validation -- `segment` is declared `PropertyKey | StandardSchemaV1.PathSegment` (never `null` or an array), so `typeof segment === "object"` can only match the `PathSegment` object form here; the null/array-safe rewrite this rule suggests is flagged as an unreachable condition by @typescript-eslint/no-unnecessary-condition given that exact type, so satisfying both rules at once is impossible -- the type itself is the guarantee this check would otherwise add at runtime.
468
+ typeof segment === "object" ? segment.key : segment
469
+ )
470
+ ).map((key, index) => {
471
+ if (typeof key === "number") return `[${String(key)}]`;
472
+ const rendered = String(key);
473
+ return index === 0 ? rendered : `.${rendered}`;
474
+ }).join("");
475
+ }
476
+
408
477
  // src/parsing/parse-json.ts
409
478
  function parseJson(stdout) {
410
479
  try {
@@ -443,7 +512,24 @@ async function parseYaml(stdout, checkId) {
443
512
  }
444
513
 
445
514
  // src/parsing/parse-output.ts
446
- async function parseOutput(format, stdout, checkId) {
515
+ async function parseOutput(format, stdout, checkId, schema) {
516
+ const parsed = await dispatch(format, stdout, checkId);
517
+ if (!parsed.success || schema === void 0) return parsed;
518
+ let result;
519
+ try {
520
+ result = await schema["~standard"].validate(parsed.value);
521
+ if (typeof result !== "object" || result === null) {
522
+ throw new TypeError(`validate() returned ${String(result)} instead of a Result object`);
523
+ }
524
+ } catch (error) {
525
+ throw new StandardSchemaValidateThrewError(checkId, error);
526
+ }
527
+ if (result.issues === void 0) {
528
+ return { format, success: true, value: result.value };
529
+ }
530
+ return { format, success: false, error: formatSchemaIssues(result.issues) };
531
+ }
532
+ async function dispatch(format, stdout, checkId) {
447
533
  switch (format) {
448
534
  case "json":
449
535
  return parseJson(stdout);
@@ -461,7 +547,12 @@ async function buildEvidence(results, startedAt, completedAt) {
461
547
  results.map(async ([checkId, check, raw]) => {
462
548
  if (check.output === void 0) return [checkId, check, raw];
463
549
  try {
464
- const output = await parseOutput(check.output.format, raw.stdout, checkId);
550
+ const output = await parseOutput(
551
+ check.output.format,
552
+ raw.stdout,
553
+ checkId,
554
+ check.output.schema
555
+ );
465
556
  return [checkId, check, { ...raw, output }];
466
557
  } catch (error) {
467
558
  thrown.push(error);
@@ -474,7 +565,10 @@ async function buildEvidence(results, startedAt, completedAt) {
474
565
  throw only;
475
566
  }
476
567
  if (thrown.length > 1) {
477
- throw new AggregateError(thrown, `${String(thrown.length)} check output(s) failed to parse.`);
568
+ throw new AggregateError(
569
+ thrown,
570
+ `${String(thrown.length)} check output(s) failed during output processing.`
571
+ );
478
572
  }
479
573
  const evidence = {
480
574
  version: 1,
@@ -1066,6 +1160,6 @@ async function runRepoContractAfterValidation(config, options) {
1066
1160
  return { evidence, verdict };
1067
1161
  }
1068
1162
 
1069
- export { DependencyDeclaredLaterError, InvalidCheckConfigError, InvalidRepoContractConfigError, ParserDependencyMissingError, PolicyReadFailedParseValueError, PolicyReadUnrequestedOutputError, PolicyThrewError, RepoContractError, UnknownCheckIdError, defineRepoContract, runRepoContract };
1163
+ export { DependencyDeclaredLaterError, InvalidCheckConfigError, InvalidRepoContractConfigError, ParserDependencyMissingError, PolicyReadFailedParseValueError, PolicyReadUnrequestedOutputError, PolicyThrewError, RepoContractError, StandardSchemaValidateThrewError, UnknownCheckIdError, defineRepoContract, runRepoContract };
1070
1164
  //# sourceMappingURL=index.js.map
1071
1165
  //# sourceMappingURL=index.js.map