@opsydyn/elysia-spectral 1.5.1 → 1.5.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/CHANGELOG.md +7 -0
- package/README.md +10 -1
- package/dist/core/index.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{runtime-4LlfDIZv.mjs → runtime-PGHAFx-E.mjs} +47 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.2](https://github.com/opsydyn/elysia-spectral/compare/v1.5.1...v1.5.2) (2026-05-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* complete self-describing lint reports ([b0917ce](https://github.com/opsydyn/elysia-spectral/commit/b0917ce4c977a1378ba851efc643058a70fea70b))
|
|
9
|
+
|
|
3
10
|
## [1.5.1](https://github.com/opsydyn/elysia-spectral/compare/v1.5.0...v1.5.1) (2026-05-12)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ Current package scope:
|
|
|
58
58
|
- resolver pipeline for advanced ruleset loading
|
|
59
59
|
- console output
|
|
60
60
|
- JSON report output
|
|
61
|
+
- self-describing JSON report metadata (`failOn`, `durationMs`, relative artifact paths)
|
|
61
62
|
- JUnit report output
|
|
62
63
|
- SARIF report output
|
|
63
64
|
- OpenAPI snapshot output
|
|
@@ -846,6 +847,10 @@ type LintRunResult = {
|
|
|
846
847
|
generatedAt: string
|
|
847
848
|
/** Where the lint run was triggered from. */
|
|
848
849
|
source: LintRunSource
|
|
850
|
+
/** The configured threshold that produced this result. */
|
|
851
|
+
failOn: SeverityThreshold
|
|
852
|
+
/** Duration of the completed lint run in milliseconds. */
|
|
853
|
+
durationMs: number | null
|
|
849
854
|
summary: {
|
|
850
855
|
error: number
|
|
851
856
|
warn: number
|
|
@@ -1008,6 +1013,8 @@ Example successful response:
|
|
|
1008
1013
|
"ok": true,
|
|
1009
1014
|
"generatedAt": "2026-04-06T12:00:00.000Z",
|
|
1010
1015
|
"source": "startup",
|
|
1016
|
+
"failOn": "error",
|
|
1017
|
+
"durationMs": 42,
|
|
1011
1018
|
"summary": {
|
|
1012
1019
|
"error": 0,
|
|
1013
1020
|
"warn": 0,
|
|
@@ -1049,6 +1056,8 @@ The current output model has two layers:
|
|
|
1049
1056
|
|
|
1050
1057
|
The convenience options compile down to built-in sinks so the current API stays simple while the internal output model becomes extensible.
|
|
1051
1058
|
|
|
1059
|
+
Persisted JSON reports are self-describing: they embed the configured `failOn` threshold, the completed `durationMs`, and relative artifact paths so the same report shape is portable across CI runners and developer machines.
|
|
1060
|
+
|
|
1052
1061
|
## Explanation
|
|
1053
1062
|
|
|
1054
1063
|
### Why this package exists
|
|
@@ -1096,4 +1105,4 @@ Production-grade linting needs more than a pass/fail boolean. The runtime tracks
|
|
|
1096
1105
|
|
|
1097
1106
|
### Project status
|
|
1098
1107
|
|
|
1099
|
-
The package is published to npm and used in production.
|
|
1108
|
+
The package is published to npm and used in production. The current pre-`1.0` feature roadmap is functionally complete: startup/runtime flows, presets, output sinks, CI workflows, dashboard support, and self-describing result artifacts are all shipped. Remaining work is primarily `1.0` stabilization — public API/package boundaries, backwards-compatibility audit, and migration notes — tracked in [roadmap.md](../../roadmap.md).
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as RulesetLoadError } from "../ruleset-load-error-CogUOC7W.mjs";
|
|
2
|
-
import { a as enforceThreshold, i as OpenApiLintThresholdError, n as createOpenApiLintRuntime, o as shouldFail, t as OpenApiLintArtifactWriteError } from "../runtime-
|
|
2
|
+
import { a as enforceThreshold, i as OpenApiLintThresholdError, n as createOpenApiLintRuntime, o as shouldFail, t as OpenApiLintArtifactWriteError } from "../runtime-PGHAFx-E.mjs";
|
|
3
3
|
import { n as loadResolvedRuleset, r as loadRuleset, t as defaultRulesetResolvers } from "../load-ruleset-CiikrzWx.mjs";
|
|
4
4
|
import { t as lintOpenApi } from "../lint-openapi-D76sC7S5.mjs";
|
|
5
5
|
export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as RulesetLoadError } from "./ruleset-load-error-CogUOC7W.mjs";
|
|
2
|
-
import { a as enforceThreshold, i as OpenApiLintThresholdError, n as createOpenApiLintRuntime, o as shouldFail, r as resolveStartupMode, s as resolveReporter, t as OpenApiLintArtifactWriteError } from "./runtime-
|
|
2
|
+
import { a as enforceThreshold, i as OpenApiLintThresholdError, n as createOpenApiLintRuntime, o as shouldFail, r as resolveStartupMode, s as resolveReporter, t as OpenApiLintArtifactWriteError } from "./runtime-PGHAFx-E.mjs";
|
|
3
3
|
import { t as recommended } from "./recommended-DgrTqq-3.mjs";
|
|
4
4
|
import { i as server, r as strict, t as presets } from "./presets-CCfU_diN.mjs";
|
|
5
5
|
import { Elysia } from "elysia";
|
|
@@ -411,6 +411,11 @@ const toSarifArtifactUri = (value) => {
|
|
|
411
411
|
};
|
|
412
412
|
//#endregion
|
|
413
413
|
//#region src/output/sinks.ts
|
|
414
|
+
const relativiseArtifactPath = (artifactPath) => {
|
|
415
|
+
const resolvedPath = path.resolve(process.cwd(), artifactPath);
|
|
416
|
+
const relativePath = path.relative(process.cwd(), resolvedPath);
|
|
417
|
+
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
418
|
+
};
|
|
414
419
|
const createOutputSinks = (options) => {
|
|
415
420
|
const reporter = resolveReporter(options.logger);
|
|
416
421
|
const sinks = [];
|
|
@@ -421,24 +426,17 @@ const createOutputSinks = (options) => {
|
|
|
421
426
|
if (configuredSpecSnapshotPath) sinks.push({
|
|
422
427
|
name: "spec snapshot",
|
|
423
428
|
kind: "artifact",
|
|
429
|
+
phase: "pre-finalize",
|
|
424
430
|
async write(_result, context) {
|
|
425
431
|
const writtenSpecSnapshotPath = await writeSpecSnapshot(configuredSpecSnapshotPath === true ? await resolveDefaultSpecSnapshotPath() : configuredSpecSnapshotPath, context.spec, options.output?.pretty !== false);
|
|
426
432
|
reporter.artifact(`OpenAPI lint wrote spec snapshot to ${writtenSpecSnapshotPath}.`);
|
|
427
433
|
return { specSnapshotPath: writtenSpecSnapshotPath };
|
|
428
434
|
}
|
|
429
435
|
});
|
|
430
|
-
if (configuredJsonReportPath) sinks.push({
|
|
431
|
-
name: "JSON report",
|
|
432
|
-
kind: "artifact",
|
|
433
|
-
async write(result) {
|
|
434
|
-
const writtenJsonReportPath = await writeJsonReport(configuredJsonReportPath, result, options.output?.pretty !== false);
|
|
435
|
-
reporter.artifact(`OpenAPI lint wrote JSON report to ${writtenJsonReportPath}.`);
|
|
436
|
-
return { jsonReportPath: writtenJsonReportPath };
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
436
|
if (configuredJunitReportPath) sinks.push({
|
|
440
437
|
name: "JUnit report",
|
|
441
438
|
kind: "artifact",
|
|
439
|
+
phase: "pre-finalize",
|
|
442
440
|
async write(result) {
|
|
443
441
|
const writtenJunitReportPath = await writeJunitReport(configuredJunitReportPath, result);
|
|
444
442
|
reporter.artifact(`OpenAPI lint wrote JUnit report to ${writtenJunitReportPath}.`);
|
|
@@ -449,6 +447,7 @@ const createOutputSinks = (options) => {
|
|
|
449
447
|
if (configuredBrunoCollectionPath) sinks.push({
|
|
450
448
|
name: "Bruno collection",
|
|
451
449
|
kind: "artifact",
|
|
450
|
+
phase: "pre-finalize",
|
|
452
451
|
async write(_result, context) {
|
|
453
452
|
const writtenPath = await writeBrunoCollection(configuredBrunoCollectionPath, context.spec);
|
|
454
453
|
reporter.artifact(`OpenAPI lint wrote Bruno collection to ${writtenPath}.`);
|
|
@@ -458,6 +457,7 @@ const createOutputSinks = (options) => {
|
|
|
458
457
|
if (configuredSarifReportPath) sinks.push({
|
|
459
458
|
name: "SARIF report",
|
|
460
459
|
kind: "artifact",
|
|
460
|
+
phase: "pre-finalize",
|
|
461
461
|
async write(result) {
|
|
462
462
|
const writtenSarifReportPath = await writeSarifReport(configuredSarifReportPath, result, options.output?.pretty !== false);
|
|
463
463
|
reporter.artifact(`OpenAPI lint wrote SARIF report to ${writtenSarifReportPath}.`);
|
|
@@ -467,11 +467,29 @@ const createOutputSinks = (options) => {
|
|
|
467
467
|
for (const sink of options.output?.sinks ?? []) sinks.push({
|
|
468
468
|
name: sink.name,
|
|
469
469
|
kind: "custom",
|
|
470
|
+
phase: "post-finalize",
|
|
470
471
|
write: async (result, context) => await Promise.resolve(sink.write(result, context))
|
|
471
472
|
});
|
|
473
|
+
if (configuredJsonReportPath) sinks.push({
|
|
474
|
+
name: "JSON report",
|
|
475
|
+
kind: "artifact",
|
|
476
|
+
phase: "post-finalize",
|
|
477
|
+
async write(result) {
|
|
478
|
+
const writtenJsonReportPath = await writeJsonReport(configuredJsonReportPath, {
|
|
479
|
+
...result,
|
|
480
|
+
artifacts: {
|
|
481
|
+
...result.artifacts ?? {},
|
|
482
|
+
jsonReportPath: relativiseArtifactPath(configuredJsonReportPath)
|
|
483
|
+
}
|
|
484
|
+
}, options.output?.pretty !== false);
|
|
485
|
+
reporter.artifact(`OpenAPI lint wrote JSON report to ${writtenJsonReportPath}.`);
|
|
486
|
+
return { jsonReportPath: writtenJsonReportPath };
|
|
487
|
+
}
|
|
488
|
+
});
|
|
472
489
|
if (options.output?.console !== false) sinks.push({
|
|
473
490
|
name: "console",
|
|
474
491
|
kind: "report",
|
|
492
|
+
phase: "post-finalize",
|
|
475
493
|
async write(result) {
|
|
476
494
|
reportToConsole(result, reporter);
|
|
477
495
|
}
|
|
@@ -645,10 +663,12 @@ const createOpenApiLintRuntime = (options = {}) => {
|
|
|
645
663
|
result.source = source;
|
|
646
664
|
result.failOn = options.failOn ?? "error";
|
|
647
665
|
result.ok = !shouldFail(result, result.failOn);
|
|
648
|
-
|
|
649
|
-
|
|
666
|
+
const { preFinalizeSinks, postFinalizeSinks } = partitionOutputSinks(createOutputSinks(options));
|
|
667
|
+
await writeOutputSinks(preFinalizeSinks, result, spec, reporter, artifactWriteFailureMode);
|
|
650
668
|
finalizeRuntimeRun(runtime, startedAt);
|
|
651
669
|
result.durationMs = runtime.durationMs;
|
|
670
|
+
await writeOutputSinks(postFinalizeSinks, result, spec, reporter, artifactWriteFailureMode);
|
|
671
|
+
runtime.latest = result;
|
|
652
672
|
reporter.complete("OpenAPI lint completed.");
|
|
653
673
|
enforceThreshold(result, options.failOn ?? "error");
|
|
654
674
|
runtime.status = "passed";
|
|
@@ -694,9 +714,7 @@ const handleArtifactWriteFailure = (artifact, error, mode, reporter) => {
|
|
|
694
714
|
if (mode === "error") throw wrappedError;
|
|
695
715
|
reporter.warn(wrappedError.message);
|
|
696
716
|
};
|
|
697
|
-
const writeOutputSinks = async (result, spec,
|
|
698
|
-
const reporter = resolveReporter(options.logger);
|
|
699
|
-
const sinks = createOutputSinks(options);
|
|
717
|
+
const writeOutputSinks = async (sinks, result, spec, reporter, artifactWriteFailureMode) => {
|
|
700
718
|
for (const sink of sinks) try {
|
|
701
719
|
const artifacts = await sink.write(result, {
|
|
702
720
|
spec,
|
|
@@ -711,6 +729,21 @@ const writeOutputSinks = async (result, spec, options, artifactWriteFailureMode)
|
|
|
711
729
|
throw error;
|
|
712
730
|
}
|
|
713
731
|
};
|
|
732
|
+
const partitionOutputSinks = (sinks) => {
|
|
733
|
+
const preFinalizeSinks = [];
|
|
734
|
+
const postFinalizeSinks = [];
|
|
735
|
+
for (const sink of sinks) {
|
|
736
|
+
if (sink.phase === "post-finalize") {
|
|
737
|
+
postFinalizeSinks.push(sink);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
preFinalizeSinks.push(sink);
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
preFinalizeSinks,
|
|
744
|
+
postFinalizeSinks
|
|
745
|
+
};
|
|
746
|
+
};
|
|
714
747
|
const relativiseArtifacts = (artifacts) => {
|
|
715
748
|
const cwd = process.cwd();
|
|
716
749
|
const result = {};
|