@opsydyn/elysia-spectral 0.3.0 → 0.4.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 +7 -0
- package/README.md +84 -11
- package/dist/core/index.d.mts +1 -1
- package/dist/core/index.mjs +1 -1
- package/dist/{core-DBjV7-E8.mjs → core-D_ro1XEW.mjs} +3 -1
- package/dist/{index-YZElrxnl.d.mts → index-CMyl_MsI.d.mts} +4 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/opsydyn/elysia-spectral/compare/v0.3.0...v0.4.0) (2026-04-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add source metadata to LintRunResult ([bdd5a81](https://github.com/opsydyn/elysia-spectral/commit/bdd5a817c00b5fdccf4e1d27c8dbf4c6d4c36d2c))
|
|
9
|
+
|
|
3
10
|
## [0.3.0](https://github.com/opsydyn/elysia-spectral/compare/v0.2.5...v0.3.0) (2026-04-14)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -426,29 +426,101 @@ spectralPlugin({
|
|
|
426
426
|
|
|
427
427
|
Use `'warn'` for local development and `'error'` when artifact generation is required.
|
|
428
428
|
|
|
429
|
-
### Run the runtime in CI
|
|
429
|
+
### Run the runtime in CI
|
|
430
|
+
|
|
431
|
+
Use `createOpenApiLintRuntime` to run a standalone lint check in CI without starting an HTTP server. Import your Elysia app instance directly — the runtime uses `app.handle()` in-process to retrieve the generated OpenAPI document. No port binding required.
|
|
432
|
+
|
|
433
|
+
Create a script at `scripts/lint-openapi.ts`:
|
|
430
434
|
|
|
431
435
|
```ts
|
|
432
|
-
import {
|
|
433
|
-
import { openapi } from '@elysiajs/openapi'
|
|
436
|
+
import { app } from '../src/app'
|
|
434
437
|
import { createOpenApiLintRuntime } from '@opsydyn/elysia-spectral'
|
|
435
438
|
|
|
436
|
-
const app = new Elysia().use(openapi())
|
|
437
|
-
|
|
438
439
|
const runtime = createOpenApiLintRuntime({
|
|
439
|
-
|
|
440
|
+
preset: 'strict',
|
|
440
441
|
failOn: 'error',
|
|
441
442
|
output: {
|
|
442
443
|
console: true,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
sarifReportPath: './reports/openapi.sarif',
|
|
445
|
+
junitReportPath: './reports/openapi.junit.xml',
|
|
446
|
+
specSnapshotPath: './reports/openapi-snapshot.json',
|
|
447
|
+
artifactWriteFailures: 'error',
|
|
448
|
+
},
|
|
447
449
|
})
|
|
448
450
|
|
|
449
|
-
|
|
451
|
+
try {
|
|
452
|
+
await runtime.run(app)
|
|
453
|
+
process.exit(0)
|
|
454
|
+
} catch {
|
|
455
|
+
process.exit(1)
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Run it as a CI step:
|
|
460
|
+
|
|
461
|
+
```yaml
|
|
462
|
+
- name: Lint OpenAPI spec
|
|
463
|
+
run: bun scripts/lint-openapi.ts
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Integrate SARIF with GitHub code scanning
|
|
467
|
+
|
|
468
|
+
SARIF output maps lint findings to code locations and surfaces them as GitHub code scanning alerts on pull requests.
|
|
469
|
+
|
|
470
|
+
```yaml
|
|
471
|
+
- name: Lint OpenAPI spec
|
|
472
|
+
run: bun scripts/lint-openapi.ts
|
|
473
|
+
|
|
474
|
+
- name: Upload SARIF to GitHub code scanning
|
|
475
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
476
|
+
with:
|
|
477
|
+
sarif_file: reports/openapi.sarif
|
|
478
|
+
if: always()
|
|
450
479
|
```
|
|
451
480
|
|
|
481
|
+
`if: always()` ensures the SARIF upload runs even when the lint step fails, so findings are visible on failing PRs.
|
|
482
|
+
|
|
483
|
+
### Report lint findings as JUnit test results
|
|
484
|
+
|
|
485
|
+
JUnit output lets CI systems that consume test XML (Buildkite, CircleCI, GitLab, GitHub via third-party actions) show lint findings alongside test results.
|
|
486
|
+
|
|
487
|
+
```yaml
|
|
488
|
+
- name: Lint OpenAPI spec
|
|
489
|
+
run: bun scripts/lint-openapi.ts
|
|
490
|
+
|
|
491
|
+
- name: Publish lint results
|
|
492
|
+
uses: dorny/test-reporter@v1
|
|
493
|
+
with:
|
|
494
|
+
name: OpenAPI Lint
|
|
495
|
+
path: reports/openapi.junit.xml
|
|
496
|
+
reporter: java-junit
|
|
497
|
+
if: always()
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Track OpenAPI snapshot drift
|
|
501
|
+
|
|
502
|
+
Commit the generated OpenAPI snapshot and use `git diff --exit-code` to detect when the API surface changes unexpectedly in a PR.
|
|
503
|
+
|
|
504
|
+
1. Generate and commit the initial snapshot:
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
bun scripts/lint-openapi.ts
|
|
508
|
+
git add reports/openapi-snapshot.json
|
|
509
|
+
git commit -m "chore: add openapi snapshot"
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
1. In CI, regenerate the snapshot and check for drift:
|
|
513
|
+
|
|
514
|
+
```yaml
|
|
515
|
+
- name: Lint OpenAPI spec
|
|
516
|
+
run: bun scripts/lint-openapi.ts
|
|
517
|
+
|
|
518
|
+
- name: Check for snapshot drift
|
|
519
|
+
run: git diff --exit-code reports/openapi-snapshot.json
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
If the snapshot has changed, the CI step fails and the diff is visible in the logs. Deliberate API changes are acknowledged by updating the committed snapshot — accidental ones are caught before they ship.
|
|
523
|
+
|
|
452
524
|
### Work on this repository locally
|
|
453
525
|
|
|
454
526
|
From the monorepo root:
|
|
@@ -606,6 +678,7 @@ Example successful response:
|
|
|
606
678
|
"result": {
|
|
607
679
|
"ok": true,
|
|
608
680
|
"generatedAt": "2026-04-06T12:00:00.000Z",
|
|
681
|
+
"source": "startup",
|
|
609
682
|
"summary": {
|
|
610
683
|
"error": 0,
|
|
611
684
|
"warn": 0,
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as defaultRulesetResolvers, a as OpenApiLintArtifactWriteError, b as lintOpenApi, c as resolveStartupMode, d as LoadedRuleset, f as ResolvedRulesetCandidate, g as RulesetResolverInput, h as RulesetResolverContext, i as shouldFail, l as normalizeFindings, m as RulesetResolver, n as enforceThreshold, o as createOpenApiLintRuntime, p as RulesetLoadError, r as exceedsThreshold, s as isEnabled, t as OpenApiLintThresholdError, u as LoadResolvedRulesetOptions, v as loadResolvedRuleset, y as loadRuleset } from "../index-
|
|
1
|
+
import { _ as defaultRulesetResolvers, a as OpenApiLintArtifactWriteError, b as lintOpenApi, c as resolveStartupMode, d as LoadedRuleset, f as ResolvedRulesetCandidate, g as RulesetResolverInput, h as RulesetResolverContext, i as shouldFail, l as normalizeFindings, m as RulesetResolver, n as enforceThreshold, o as createOpenApiLintRuntime, p as RulesetLoadError, r as exceedsThreshold, s as isEnabled, t as OpenApiLintThresholdError, u as LoadResolvedRulesetOptions, v as loadResolvedRuleset, y as loadRuleset } from "../index-CMyl_MsI.mjs";
|
|
2
2
|
export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as OpenApiLintThresholdError, c as shouldFail, g as loadRuleset, h as loadResolvedRuleset, i as resolveStartupMode, m as defaultRulesetResolvers, n as createOpenApiLintRuntime, o as enforceThreshold, p as RulesetLoadError, r as isEnabled, s as exceedsThreshold, t as OpenApiLintArtifactWriteError, v as lintOpenApi, y as normalizeFindings } from "../core-
|
|
1
|
+
import { a as OpenApiLintThresholdError, c as shouldFail, g as loadRuleset, h as loadResolvedRuleset, i as resolveStartupMode, m as defaultRulesetResolvers, n as createOpenApiLintRuntime, o as enforceThreshold, p as RulesetLoadError, r as isEnabled, s as exceedsThreshold, t as OpenApiLintArtifactWriteError, v as lintOpenApi, y as normalizeFindings } from "../core-D_ro1XEW.mjs";
|
|
2
2
|
export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };
|
|
@@ -53,6 +53,7 @@ const normalizeFindings = (diagnostics, spec) => {
|
|
|
53
53
|
return {
|
|
54
54
|
ok: summary.error === 0,
|
|
55
55
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56
|
+
source: "manual",
|
|
56
57
|
summary,
|
|
57
58
|
findings
|
|
58
59
|
};
|
|
@@ -1155,7 +1156,7 @@ const createOpenApiLintRuntime = (options = {}) => {
|
|
|
1155
1156
|
lastSuccess: null,
|
|
1156
1157
|
lastFailure: null,
|
|
1157
1158
|
running: false,
|
|
1158
|
-
async run(app) {
|
|
1159
|
+
async run(app, source = "manual") {
|
|
1159
1160
|
if (inFlight) return await inFlight;
|
|
1160
1161
|
inFlight = (async () => {
|
|
1161
1162
|
const startedAt = /* @__PURE__ */ new Date();
|
|
@@ -1174,6 +1175,7 @@ const createOpenApiLintRuntime = (options = {}) => {
|
|
|
1174
1175
|
} else if (options.preset && !loadedRuleset.source?.path) reporter.ruleset(`OpenAPI lint using "${options.preset}" preset.`);
|
|
1175
1176
|
else if (loadedRuleset.source?.path) reporter.ruleset(`OpenAPI lint loaded ruleset ${loadedRuleset.source.path}.`);
|
|
1176
1177
|
const result = await lintOpenApi(spec, loadedRuleset.ruleset);
|
|
1178
|
+
result.source = source;
|
|
1177
1179
|
await writeOutputSinks(result, spec, options, artifactWriteFailureMode);
|
|
1178
1180
|
runtime.latest = result;
|
|
1179
1181
|
reporter.complete("OpenAPI lint completed.");
|
|
@@ -7,6 +7,7 @@ type SeverityThreshold = 'error' | 'warn' | 'info' | 'hint' | 'never';
|
|
|
7
7
|
type LintSeverity = 'error' | 'warn' | 'info' | 'hint';
|
|
8
8
|
type StartupLintMode = 'enforce' | 'report' | 'off';
|
|
9
9
|
type OpenApiLintRuntimeStatus = 'idle' | 'running' | 'passed' | 'failed';
|
|
10
|
+
type LintRunSource = 'startup' | 'healthcheck' | 'manual';
|
|
10
11
|
type ArtifactWriteFailureMode = 'warn' | 'error';
|
|
11
12
|
type SpectralLogger = {
|
|
12
13
|
info: (message: string) => void;
|
|
@@ -81,6 +82,7 @@ type LintFinding = {
|
|
|
81
82
|
type LintRunResult = {
|
|
82
83
|
ok: boolean;
|
|
83
84
|
generatedAt: string;
|
|
85
|
+
source: LintRunSource;
|
|
84
86
|
summary: {
|
|
85
87
|
error: number;
|
|
86
88
|
warn: number;
|
|
@@ -108,7 +110,7 @@ type OpenApiLintRuntime = {
|
|
|
108
110
|
lastSuccess: LintRunResult | null;
|
|
109
111
|
lastFailure: OpenApiLintRuntimeFailure | null;
|
|
110
112
|
running: boolean;
|
|
111
|
-
run: (app: AnyElysia) => Promise<LintRunResult>;
|
|
113
|
+
run: (app: AnyElysia, source?: LintRunSource) => Promise<LintRunResult>;
|
|
112
114
|
};
|
|
113
115
|
//#endregion
|
|
114
116
|
//#region src/core/lint-openapi.d.ts
|
|
@@ -172,4 +174,4 @@ declare const exceedsThreshold: (severity: LintSeverity, threshold: SeverityThre
|
|
|
172
174
|
declare const shouldFail: (result: LintRunResult, threshold: SeverityThreshold) => boolean;
|
|
173
175
|
declare const enforceThreshold: (result: LintRunResult, threshold: SeverityThreshold) => void;
|
|
174
176
|
//#endregion
|
|
175
|
-
export {
|
|
177
|
+
export { OpenApiLintSink as A, LintRunResult as C, OpenApiLintRuntime as D, OpenApiLintArtifacts as E, SpectralLogger as F, SpectralPluginOptions as I, StartupLintMode as L, PresetName as M, SeverityThreshold as N, OpenApiLintRuntimeFailure as O, SpecProvider as P, LintFinding as S, LintSeverity as T, defaultRulesetResolvers as _, OpenApiLintArtifactWriteError as a, lintOpenApi as b, resolveStartupMode as c, LoadedRuleset as d, ResolvedRulesetCandidate as f, RulesetResolverInput as g, RulesetResolverContext as h, shouldFail as i, OpenApiLintSinkContext as j, OpenApiLintRuntimeStatus as k, normalizeFindings as l, RulesetResolver as m, enforceThreshold as n, createOpenApiLintRuntime as o, RulesetLoadError as p, exceedsThreshold as r, isEnabled as s, OpenApiLintThresholdError as t, LoadResolvedRulesetOptions as u, loadResolvedRuleset as v, LintRunSource as w, ArtifactWriteFailureMode as x, loadRuleset as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as OpenApiLintSink, C as LintRunResult, D as OpenApiLintRuntime, E as OpenApiLintArtifacts, F as SpectralLogger, I as SpectralPluginOptions, L as StartupLintMode, M as PresetName, N as SeverityThreshold, O as OpenApiLintRuntimeFailure, P as SpecProvider, S as LintFinding, T as LintSeverity, _ as defaultRulesetResolvers, a as OpenApiLintArtifactWriteError, b as lintOpenApi, c as resolveStartupMode, d as LoadedRuleset, f as ResolvedRulesetCandidate, g as RulesetResolverInput, h as RulesetResolverContext, i as shouldFail, j as OpenApiLintSinkContext, k as OpenApiLintRuntimeStatus, l as normalizeFindings, m as RulesetResolver, n as enforceThreshold, o as createOpenApiLintRuntime, p as RulesetLoadError, r as exceedsThreshold, s as isEnabled, t as OpenApiLintThresholdError, u as LoadResolvedRulesetOptions, v as loadResolvedRuleset, w as LintRunSource, x as ArtifactWriteFailureMode, y as loadRuleset } from "./index-CMyl_MsI.mjs";
|
|
2
2
|
import { RulesetDefinition } from "@stoplight/spectral-core";
|
|
3
3
|
import { Elysia } from "elysia";
|
|
4
4
|
|
|
@@ -72,4 +72,4 @@ declare const strict: RulesetDefinition;
|
|
|
72
72
|
//#region src/presets/index.d.ts
|
|
73
73
|
declare const presets: Record<PresetName, RulesetDefinition>;
|
|
74
74
|
//#endregion
|
|
75
|
-
export { ArtifactWriteFailureMode, LintFinding, LintRunResult, LintSeverity, LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintArtifacts, OpenApiLintRuntime, OpenApiLintRuntimeFailure, OpenApiLintRuntimeStatus, OpenApiLintSink, OpenApiLintSinkContext, OpenApiLintThresholdError, PresetName, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, SeverityThreshold, SpecProvider, SpectralLogger, SpectralPluginOptions, StartupLintMode, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
|
|
75
|
+
export { ArtifactWriteFailureMode, LintFinding, LintRunResult, LintRunSource, LintSeverity, LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintArtifacts, OpenApiLintRuntime, OpenApiLintRuntimeFailure, OpenApiLintRuntimeStatus, OpenApiLintSink, OpenApiLintSinkContext, OpenApiLintThresholdError, PresetName, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, SeverityThreshold, SpecProvider, SpectralLogger, SpectralPluginOptions, StartupLintMode, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as recommended, a as OpenApiLintThresholdError, c as shouldFail, d as server, f as resolveReporter, g as loadRuleset, h as loadResolvedRuleset, i as resolveStartupMode, l as presets, m as defaultRulesetResolvers, n as createOpenApiLintRuntime, o as enforceThreshold, p as RulesetLoadError, r as isEnabled, s as exceedsThreshold, t as OpenApiLintArtifactWriteError, u as strict, v as lintOpenApi, y as normalizeFindings } from "./core-
|
|
1
|
+
import { _ as recommended, a as OpenApiLintThresholdError, c as shouldFail, d as server, f as resolveReporter, g as loadRuleset, h as loadResolvedRuleset, i as resolveStartupMode, l as presets, m as defaultRulesetResolvers, n as createOpenApiLintRuntime, o as enforceThreshold, p as RulesetLoadError, r as isEnabled, s as exceedsThreshold, t as OpenApiLintArtifactWriteError, u as strict, v as lintOpenApi, y as normalizeFindings } from "./core-D_ro1XEW.mjs";
|
|
2
2
|
import { Elysia } from "elysia";
|
|
3
3
|
//#region src/plugin.ts
|
|
4
4
|
const spectralPlugin = (options = {}) => {
|
|
@@ -11,7 +11,7 @@ const spectralPlugin = (options = {}) => {
|
|
|
11
11
|
const startupMode = resolveStartupMode(options);
|
|
12
12
|
if (startupMode === "off") return;
|
|
13
13
|
try {
|
|
14
|
-
await runtime.run(app);
|
|
14
|
+
await runtime.run(app, "startup");
|
|
15
15
|
} catch (error) {
|
|
16
16
|
if (startupMode === "report" && error instanceof OpenApiLintThresholdError) {
|
|
17
17
|
reporter.report(`OpenAPI lint exceeded the "${options.failOn ?? "error"}" threshold, but startup is continuing because startup.mode is "report".`);
|
|
@@ -37,7 +37,7 @@ const spectralPlugin = (options = {}) => {
|
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
39
39
|
const usedCache = !fresh && (runtime.latest !== null || runtime.running);
|
|
40
|
-
const result = usedCache ? runtime.latest ?? await runtime.run(currentApp) : await runtime.run(currentApp);
|
|
40
|
+
const result = usedCache ? runtime.latest ?? await runtime.run(currentApp, "healthcheck") : await runtime.run(currentApp, "healthcheck");
|
|
41
41
|
if (result === null) {
|
|
42
42
|
set.status = 500;
|
|
43
43
|
return {
|