@opsydyn/elysia-spectral 0.5.2 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0](https://github.com/opsydyn/elysia-spectral/compare/v0.5.2...v1.0.0) (2026-04-15)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * result.artifacts paths are now relative (e.g. "./artifacts/openapi-lint.json") rather than absolute. Callers that used the path directly to open the file should resolve it with path.resolve().
9
+
10
+ ### Features
11
+
12
+ * add durationMs, failOn to LintRunResult; relativise artifact paths ([421a20f](https://github.com/opsydyn/elysia-spectral/commit/421a20f6bf655432bceff8369c63bf4762edbc24))
13
+
3
14
  ## [0.5.2](https://github.com/opsydyn/elysia-spectral/compare/v0.5.1...v0.5.2) (2026-04-15)
4
15
 
5
16
 
package/README.md CHANGED
@@ -63,6 +63,35 @@ Current package scope:
63
63
  - reusable runtime for CI and tests
64
64
  - opt-in healthcheck endpoint for cached and fresh runs
65
65
 
66
+ ## Data flow
67
+
68
+ ```mermaid
69
+ flowchart TD
70
+ A["Elysia routes\n(TypeScript + t.Object schemas)"]
71
+ B["@elysiajs/openapi\ngenerates OpenAPI spec at runtime"]
72
+ C["PublicSpecProvider\nfetches /openapi/json"]
73
+ D["lintOpenApi\nSpectral rules engine"]
74
+ E["LintRunResult\n{ ok, source, summary, findings }"]
75
+
76
+ F["Console output"]
77
+ G["JSON report\nopenapi-lint.json"]
78
+ H["OpenAPI snapshot\n*.open-api.json"]
79
+ I["SARIF\nGitHub code scanning"]
80
+ J["JUnit\nCI test reporters"]
81
+ K["Bruno collection\n.yml / .json"]
82
+
83
+ A -->|"route schemas are\nthe source of truth"| B
84
+ B -->|"spec JSON"| C
85
+ C -->|"spec JSON"| D
86
+ D -->|"findings + summary"| E
87
+ E --> F
88
+ E --> G
89
+ E --> H
90
+ E --> I
91
+ E --> J
92
+ E --> K
93
+ ```
94
+
66
95
  ## Tutorial
67
96
 
68
97
  ### Add OpenAPI linting to an Elysia app
@@ -153,6 +182,10 @@ bun run src/index.ts
153
182
  - `./artifacts/openapi-lint.json` contains the full lint result
154
183
  - `./<package-name>.open-api.json` contains the generated OpenAPI snapshot
155
184
 
185
+ A passing run:
186
+
187
+ ![OpenAPI lint pass banner](ship-it.png)
188
+
156
189
  If startup fails, the terminal output includes:
157
190
 
158
191
  - the failing rule code
@@ -160,6 +193,8 @@ If startup fails, the terminal output includes:
160
193
  - a fix hint when one is known
161
194
  - a spec reference in `open-api.json#/json/pointer` form
162
195
 
196
+ ![OpenAPI lint error output](errrors.png)
197
+
163
198
  ### Choose a preset
164
199
 
165
200
  Three first-party presets are available. Import them directly or set `preset` in the plugin options.
@@ -1,2 +1,2 @@
1
- import { a as createOpenApiLintRuntime, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver } from "../index-11HnbLDN.mjs";
1
+ import { a as createOpenApiLintRuntime, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver } from "../index--_yjTKg8.mjs";
2
2
  export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
@@ -1,2 +1,2 @@
1
- import { a as enforceThreshold, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, i as OpenApiLintThresholdError, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, t as OpenApiLintArtifactWriteError } from "../core-DTKNy6TU.mjs";
1
+ import { a as enforceThreshold, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, i as OpenApiLintThresholdError, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, t as OpenApiLintArtifactWriteError } from "../core-BIi33eA1.mjs";
2
2
  export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
@@ -55,6 +55,8 @@ const normalizeFindings = (diagnostics, spec) => {
55
55
  ok: summary.error === 0,
56
56
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
57
  source: "manual",
58
+ failOn: "error",
59
+ durationMs: null,
58
60
  summary,
59
61
  findings
60
62
  };
@@ -1198,7 +1200,8 @@ const createOpenApiLintRuntime = (options = {}) => {
1198
1200
  else if (loadedRuleset.source?.path) reporter.ruleset(`OpenAPI lint loaded ruleset ${loadedRuleset.source.path}.`);
1199
1201
  const result = await lintOpenApi(spec, loadedRuleset.ruleset);
1200
1202
  result.source = source;
1201
- result.ok = !shouldFail(result, options.failOn ?? "error");
1203
+ result.failOn = options.failOn ?? "error";
1204
+ result.ok = !shouldFail(result, result.failOn);
1202
1205
  await writeOutputSinks(result, spec, options, artifactWriteFailureMode);
1203
1206
  runtime.latest = result;
1204
1207
  reporter.complete("OpenAPI lint completed.");
@@ -1206,6 +1209,7 @@ const createOpenApiLintRuntime = (options = {}) => {
1206
1209
  runtime.status = "passed";
1207
1210
  runtime.lastSuccess = result;
1208
1211
  finalizeRuntimeRun(runtime, startedAt);
1212
+ result.durationMs = runtime.durationMs;
1209
1213
  return result;
1210
1214
  } catch (error) {
1211
1215
  runtime.status = "failed";
@@ -1264,9 +1268,18 @@ const writeOutputSinks = async (result, spec, options, artifactWriteFailureMode)
1264
1268
  throw error;
1265
1269
  }
1266
1270
  };
1271
+ const relativiseArtifacts = (artifacts) => {
1272
+ const cwd = process.cwd();
1273
+ const result = {};
1274
+ for (const [key, value] of Object.entries(artifacts)) if (typeof value === "string" && path.isAbsolute(value)) {
1275
+ const rel = path.relative(cwd, value);
1276
+ result[key] = rel.startsWith(".") ? rel : `./${rel}`;
1277
+ } else result[key] = value;
1278
+ return result;
1279
+ };
1267
1280
  const mergeArtifacts = (current, next) => ({
1268
1281
  ...current,
1269
- ...next
1282
+ ...relativiseArtifacts(next)
1270
1283
  });
1271
1284
  const resolveStartupMode = (options = {}) => {
1272
1285
  if (options.startup?.mode) return options.startup.mode;
@@ -85,6 +85,8 @@ type LintRunResult = {
85
85
  ok: boolean;
86
86
  generatedAt: string;
87
87
  source: LintRunSource;
88
+ failOn: SeverityThreshold;
89
+ durationMs: number | null;
88
90
  summary: {
89
91
  error: number;
90
92
  warn: number;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as SpectralLogger, C as OpenApiLintRuntime, D as OpenApiLintSinkContext, E as OpenApiLintSink, M as StartupLintMode, O as PresetName, S as OpenApiLintArtifacts, T as OpenApiLintRuntimeStatus, _ as ArtifactWriteFailureMode, a as createOpenApiLintRuntime, b as LintRunSource, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, j as SpectralPluginOptions, k as SeverityThreshold, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver, v as LintFinding, w as OpenApiLintRuntimeFailure, x as LintSeverity, y as LintRunResult } from "./index-11HnbLDN.mjs";
1
+ import { A as SpectralLogger, C as OpenApiLintRuntime, D as OpenApiLintSinkContext, E as OpenApiLintSink, M as StartupLintMode, O as PresetName, S as OpenApiLintArtifacts, T as OpenApiLintRuntimeStatus, _ as ArtifactWriteFailureMode, a as createOpenApiLintRuntime, b as LintRunSource, c as ResolvedRulesetCandidate, d as RulesetResolverContext, f as RulesetResolverInput, g as lintOpenApi, h as loadRuleset, i as OpenApiLintArtifactWriteError, j as SpectralPluginOptions, k as SeverityThreshold, l as RulesetLoadError, m as loadResolvedRuleset, n as enforceThreshold, o as LoadResolvedRulesetOptions, p as defaultRulesetResolvers, r as shouldFail, s as LoadedRuleset, t as OpenApiLintThresholdError, u as RulesetResolver, v as LintFinding, w as OpenApiLintRuntimeFailure, x as LintSeverity, y as LintRunResult } from "./index--_yjTKg8.mjs";
2
2
  import { RulesetDefinition } from "@stoplight/spectral-core";
3
3
  import { Elysia } from "elysia";
4
4
 
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as enforceThreshold, c as strict, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, h as recommended, i as OpenApiLintThresholdError, l as server, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, r as resolveStartupMode, s as presets, t as OpenApiLintArtifactWriteError, u as resolveReporter } from "./core-DTKNy6TU.mjs";
1
+ import { a as enforceThreshold, c as strict, d as RulesetLoadError, f as defaultRulesetResolvers, g as lintOpenApi, h as recommended, i as OpenApiLintThresholdError, l as server, m as loadRuleset, n as createOpenApiLintRuntime, o as shouldFail, p as loadResolvedRuleset, r as resolveStartupMode, s as presets, t as OpenApiLintArtifactWriteError, u as resolveReporter } from "./core-BIi33eA1.mjs";
2
2
  import { Elysia } from "elysia";
3
3
  //#region src/plugin.ts
4
4
  const spectralPlugin = (options = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsydyn/elysia-spectral",
3
- "version": "0.5.2",
3
+ "version": "1.0.0",
4
4
  "description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "publishConfig": {