@opsydyn/elysia-spectral 0.3.0 → 0.5.1

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1](https://github.com/opsydyn/elysia-spectral/compare/v0.5.0...v0.5.1) (2026-04-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * tighten public API surface and align LintRunResult.ok with threshold ([8ca3331](https://github.com/opsydyn/elysia-spectral/commit/8ca3331db59e5753d454e759730e3ecd931d2087))
9
+
10
+ ## [0.5.0](https://github.com/opsydyn/elysia-spectral/compare/v0.4.0...v0.5.0) (2026-04-15)
11
+
12
+
13
+ ### Features
14
+
15
+ * add Bruno collection output via output.brunoCollectionPath ([45abcd6](https://github.com/opsydyn/elysia-spectral/commit/45abcd6225bd94098d5974a27ef2c7642614c77f))
16
+
17
+ ## [0.4.0](https://github.com/opsydyn/elysia-spectral/compare/v0.3.0...v0.4.0) (2026-04-15)
18
+
19
+
20
+ ### Features
21
+
22
+ * add source metadata to LintRunResult ([bdd5a81](https://github.com/opsydyn/elysia-spectral/commit/bdd5a817c00b5fdccf4e1d27c8dbf4c6d4c36d2c))
23
+
3
24
  ## [0.3.0](https://github.com/opsydyn/elysia-spectral/compare/v0.2.5...v0.3.0) (2026-04-14)
4
25
 
5
26
 
package/README.md CHANGED
@@ -49,6 +49,8 @@ Current package scope:
49
49
 
50
50
  - startup linting
51
51
  - threshold-based failure
52
+ - first-party governance presets: `recommended`, `server`, `strict`
53
+ - RFC 9457 Problem Details enforcement (strict preset)
52
54
  - repo-level and local rulesets
53
55
  - YAML, JS, TS, and in-memory rulesets
54
56
  - resolver pipeline for advanced ruleset loading
@@ -57,6 +59,7 @@ Current package scope:
57
59
  - JUnit report output
58
60
  - SARIF report output
59
61
  - OpenAPI snapshot output
62
+ - Bruno collection output (OpenCollection YAML or JSON)
60
63
  - reusable runtime for CI and tests
61
64
  - opt-in healthcheck endpoint for cached and fresh runs
62
65
 
@@ -426,29 +429,177 @@ spectralPlugin({
426
429
 
427
430
  Use `'warn'` for local development and `'error'` when artifact generation is required.
428
431
 
429
- ### Run the runtime in CI or tests
432
+ ### Run the runtime in CI
433
+
434
+ 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.
435
+
436
+ Create a script at `scripts/lint-openapi.ts`:
430
437
 
431
438
  ```ts
432
- import { Elysia } from 'elysia'
433
- import { openapi } from '@elysiajs/openapi'
439
+ import { app } from '../src/app'
434
440
  import { createOpenApiLintRuntime } from '@opsydyn/elysia-spectral'
435
441
 
436
- const app = new Elysia().use(openapi())
437
-
438
442
  const runtime = createOpenApiLintRuntime({
439
- ruleset: './spectral.yaml',
443
+ preset: 'strict',
440
444
  failOn: 'error',
441
445
  output: {
442
446
  console: true,
443
- jsonReportPath: './artifacts/openapi-lint.json',
444
- specSnapshotPath: true,
445
- artifactWriteFailures: 'error'
447
+ sarifReportPath: './reports/openapi.sarif',
448
+ junitReportPath: './reports/openapi.junit.xml',
449
+ specSnapshotPath: './reports/openapi-snapshot.json',
450
+ artifactWriteFailures: 'error',
451
+ },
452
+ })
453
+
454
+ try {
455
+ await runtime.run(app)
456
+ process.exit(0)
457
+ } catch {
458
+ process.exit(1)
459
+ }
460
+ ```
461
+
462
+ Run it as a CI step:
463
+
464
+ ```yaml
465
+ - name: Lint OpenAPI spec
466
+ run: bun scripts/lint-openapi.ts
467
+ ```
468
+
469
+ ### Integrate SARIF with GitHub code scanning
470
+
471
+ SARIF output maps lint findings to code locations and surfaces them as GitHub code scanning alerts on pull requests.
472
+
473
+ ```yaml
474
+ - name: Lint OpenAPI spec
475
+ run: bun scripts/lint-openapi.ts
476
+
477
+ - name: Upload SARIF to GitHub code scanning
478
+ uses: github/codeql-action/upload-sarif@v3
479
+ with:
480
+ sarif_file: reports/openapi.sarif
481
+ if: always()
482
+ ```
483
+
484
+ `if: always()` ensures the SARIF upload runs even when the lint step fails, so findings are visible on failing PRs.
485
+
486
+ ### Report lint findings as JUnit test results
487
+
488
+ JUnit output lets CI systems that consume test XML (Buildkite, CircleCI, GitLab, GitHub via third-party actions) show lint findings alongside test results.
489
+
490
+ ```yaml
491
+ - name: Lint OpenAPI spec
492
+ run: bun scripts/lint-openapi.ts
493
+
494
+ - name: Publish lint results
495
+ uses: dorny/test-reporter@v1
496
+ with:
497
+ name: OpenAPI Lint
498
+ path: reports/openapi.junit.xml
499
+ reporter: java-junit
500
+ if: always()
501
+ ```
502
+
503
+ ### Generate a Bruno collection
504
+
505
+ Export the generated OpenAPI spec as a Bruno collection. Bruno is an open-source API client — generated collections let your team test API endpoints without manual import steps.
506
+
507
+ The output format is determined by the file extension:
508
+
509
+ - `.yml` / `.yaml` — OpenCollection YAML (recommended, Bruno v3.0.0+)
510
+ - `.json` — Bruno collection JSON (compatible with all Bruno versions)
511
+
512
+ ```ts
513
+ // OpenCollection YAML — recommended
514
+ spectralPlugin({
515
+ output: {
516
+ brunoCollectionPath: './bruno/collection.yml'
517
+ }
518
+ })
519
+
520
+ // Bruno JSON — for older Bruno versions
521
+ spectralPlugin({
522
+ output: {
523
+ brunoCollectionPath: './bruno/collection.json'
446
524
  }
447
525
  })
526
+ ```
527
+
528
+ The collection is written after each lint run — at startup, on healthcheck, or in CI. Commit it alongside the spec snapshot so it stays in sync with the API surface.
529
+
530
+ To regenerate in CI as part of your lint script:
531
+
532
+ ```ts
533
+ const runtime = createOpenApiLintRuntime({
534
+ preset: 'strict',
535
+ output: {
536
+ specSnapshotPath: './reports/openapi-snapshot.json',
537
+ brunoCollectionPath: './bruno/collection.yml',
538
+ },
539
+ })
448
540
 
449
541
  await runtime.run(app)
450
542
  ```
451
543
 
544
+ ### Track OpenAPI snapshot drift
545
+
546
+ Commit the generated OpenAPI snapshot and use `git diff --exit-code` to detect when the API surface changes unexpectedly in a PR.
547
+
548
+ 1. Generate and commit the initial snapshot:
549
+
550
+ ```bash
551
+ bun scripts/lint-openapi.ts
552
+ git add reports/openapi-snapshot.json
553
+ git commit -m "chore: add openapi snapshot"
554
+ ```
555
+
556
+ 1. In CI, regenerate the snapshot and check for drift:
557
+
558
+ ```yaml
559
+ - name: Lint OpenAPI spec
560
+ run: bun scripts/lint-openapi.ts
561
+
562
+ - name: Check for snapshot drift
563
+ run: git diff --exit-code reports/openapi-snapshot.json
564
+ ```
565
+
566
+ 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.
567
+
568
+ ### Generate a typed client with openapi-ts
569
+
570
+ Use the committed OpenAPI snapshot as input to [`openapi-ts`](https://openapi-ts.dev) to keep a generated TypeScript client in sync with the API surface.
571
+
572
+ 1. Install `openapi-ts`:
573
+
574
+ ```bash
575
+ bun add -d @hey-api/openapi-ts
576
+ ```
577
+
578
+ 2. Add a codegen script to `package.json`:
579
+
580
+ ```json
581
+ {
582
+ "scripts": {
583
+ "generate:client": "openapi-ts --input ./reports/openapi-snapshot.json --output ./src/generated/client --client @hey-api/client-fetch"
584
+ }
585
+ }
586
+ ```
587
+
588
+ 3. Chain it after the lint step in CI:
589
+
590
+ ```yaml
591
+ - name: Lint OpenAPI spec
592
+ run: bun scripts/lint-openapi.ts
593
+
594
+ - name: Generate typed client
595
+ run: bun run generate:client
596
+
597
+ - name: Check for client drift
598
+ run: git diff --exit-code src/generated/client/
599
+ ```
600
+
601
+ The lint gate runs first — if the spec is invalid the codegen step never runs. The drift check ensures the committed client always matches the current spec.
602
+
452
603
  ### Work on this repository locally
453
604
 
454
605
  From the monorepo root:
@@ -492,6 +643,7 @@ type OpenApiLintArtifacts = {
492
643
  junitReportPath?: string
493
644
  sarifReportPath?: string
494
645
  specSnapshotPath?: string
646
+ brunoCollectionPath?: string
495
647
  }
496
648
 
497
649
  type OpenApiLintSink = {
@@ -535,6 +687,8 @@ type SpectralPluginOptions = {
535
687
  junitReportPath?: string
536
688
  sarifReportPath?: string
537
689
  specSnapshotPath?: string | true
690
+ /** .yml/.yaml → OpenCollection YAML (Bruno v3+), .json → Bruno collection JSON */
691
+ brunoCollectionPath?: string
538
692
  pretty?: boolean
539
693
  artifactWriteFailures?: ArtifactWriteFailureMode
540
694
  sinks?: OpenApiLintSink[]
@@ -606,6 +760,7 @@ Example successful response:
606
760
  "result": {
607
761
  "ok": true,
608
762
  "generatedAt": "2026-04-06T12:00:00.000Z",
763
+ "source": "startup",
609
764
  "summary": {
610
765
  "error": 0,
611
766
  "warn": 0,
@@ -642,7 +797,7 @@ Example successful response:
642
797
 
643
798
  The current output model has two layers:
644
799
 
645
- - convenience options such as `jsonReportPath`, `junitReportPath`, `specSnapshotPath`, and `sarifReportPath`
800
+ - convenience options such as `jsonReportPath`, `junitReportPath`, `specSnapshotPath`, `sarifReportPath`, and `brunoCollectionPath`
646
801
  - sink abstractions under `output.sinks`
647
802
 
648
803
  The convenience options compile down to built-in sinks so the current API stays simple while the internal output model becomes extensible.
@@ -691,4 +846,4 @@ Production-grade linting needs more than a pass/fail boolean. The runtime tracks
691
846
 
692
847
  ### Project status
693
848
 
694
- This package currently implements the narrowed v0.1 scope from [project.md](../../project.md) and the ongoing hardening work tracked in [roadmap.md](../../roadmap.md).
849
+ The package is actively developed toward a stable `v1`. Milestones 0.2 through 0.6 are complete. Ongoing work is tracked in [roadmap.md](../../roadmap.md).
@@ -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-YZElrxnl.mjs";
2
- export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };
1
+ import { _ as lintOpenApi, a as createOpenApiLintRuntime, c as LoadedRuleset, d as RulesetResolver, f as RulesetResolverContext, g as loadRuleset, h as loadResolvedRuleset, i as OpenApiLintArtifactWriteError, l as ResolvedRulesetCandidate, m as defaultRulesetResolvers, n as enforceThreshold, o as resolveStartupMode, p as RulesetResolverInput, r as shouldFail, s as LoadResolvedRulesetOptions, t as OpenApiLintThresholdError, u as RulesetLoadError } from "../index-B4fxn0G6.mjs";
2
+ export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, resolveStartupMode, shouldFail };
@@ -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-DBjV7-E8.mjs";
2
- export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, resolveStartupMode, shouldFail };
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, r as resolveStartupMode, t as OpenApiLintArtifactWriteError } from "../core-DTKNy6TU.mjs";
2
+ export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, resolveStartupMode, shouldFail };
@@ -7,6 +7,7 @@ import spectralRulesets from "@stoplight/spectral-rulesets";
7
7
  import YAML from "yaml";
8
8
  import signale from "signale";
9
9
  import { styleText } from "node:util";
10
+ import { brunoToOpenCollection, openApiToBruno } from "@usebruno/converters";
10
11
  //#region src/core/finding-guidance.ts
11
12
  const guidanceByCode = {
12
13
  "elysia-operation-summary": "Add detail.summary to the Elysia route options so generated docs and clients have a short operation label.",
@@ -53,6 +54,7 @@ const normalizeFindings = (diagnostics, spec) => {
53
54
  return {
54
55
  ok: summary.error === 0,
55
56
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
+ source: "manual",
56
58
  summary,
57
59
  findings
58
60
  };
@@ -407,7 +409,7 @@ const normalizeExtends = (value) => {
407
409
  };
408
410
  const resolveExtendsEntry = (value) => {
409
411
  const resolved = extendsMap[value];
410
- if (!resolved) throw new RulesetLoadError(`Unsupported ruleset extend target: ${value}. v0.1 supports spectral:oas.`);
412
+ if (!resolved) throw new RulesetLoadError(`Unsupported ruleset extend target: "${value}". Supported extend targets: spectral:oas.`);
411
413
  return resolved;
412
414
  };
413
415
  const normalizeRules = (value, availableFunctions) => {
@@ -668,6 +670,17 @@ const buildSpecReference = (finding, specSnapshotPath) => {
668
670
  return `${specSnapshotPath}#${finding.documentPointer}`;
669
671
  };
670
672
  //#endregion
673
+ //#region src/output/bruno-reporter.ts
674
+ const isYamlPath = (filePath) => filePath.endsWith(".yml") || filePath.endsWith(".yaml");
675
+ const writeBrunoCollection = async (outputPath, spec) => {
676
+ const resolvedPath = path.resolve(process.cwd(), outputPath);
677
+ const collection = openApiToBruno(spec);
678
+ const content = isYamlPath(outputPath) ? YAML.stringify(brunoToOpenCollection(collection)) : JSON.stringify(collection, null, 2);
679
+ await mkdir(path.dirname(resolvedPath), { recursive: true });
680
+ await writeFile(resolvedPath, content, "utf8");
681
+ return resolvedPath;
682
+ };
683
+ //#endregion
671
684
  //#region src/output/json-reporter.ts
672
685
  const DEFAULT_SPEC_SNAPSHOT_FILENAME = "open-api.json";
673
686
  const writeJsonArtifact = async (artifactPath, payload, pretty = true) => {
@@ -866,6 +879,16 @@ const createOutputSinks = (options) => {
866
879
  return { junitReportPath: writtenJunitReportPath };
867
880
  }
868
881
  });
882
+ const configuredBrunoCollectionPath = options.output?.brunoCollectionPath;
883
+ if (configuredBrunoCollectionPath) sinks.push({
884
+ name: "Bruno collection",
885
+ kind: "artifact",
886
+ async write(_result, context) {
887
+ const writtenPath = await writeBrunoCollection(configuredBrunoCollectionPath, context.spec);
888
+ reporter.artifact(`OpenAPI lint wrote Bruno collection to ${writtenPath}.`);
889
+ return { brunoCollectionPath: writtenPath };
890
+ }
891
+ });
869
892
  if (configuredSarifReportPath) sinks.push({
870
893
  name: "SARIF report",
871
894
  kind: "artifact",
@@ -1155,7 +1178,7 @@ const createOpenApiLintRuntime = (options = {}) => {
1155
1178
  lastSuccess: null,
1156
1179
  lastFailure: null,
1157
1180
  running: false,
1158
- async run(app) {
1181
+ async run(app, source = "manual") {
1159
1182
  if (inFlight) return await inFlight;
1160
1183
  inFlight = (async () => {
1161
1184
  const startedAt = /* @__PURE__ */ new Date();
@@ -1174,6 +1197,8 @@ const createOpenApiLintRuntime = (options = {}) => {
1174
1197
  } else if (options.preset && !loadedRuleset.source?.path) reporter.ruleset(`OpenAPI lint using "${options.preset}" preset.`);
1175
1198
  else if (loadedRuleset.source?.path) reporter.ruleset(`OpenAPI lint loaded ruleset ${loadedRuleset.source.path}.`);
1176
1199
  const result = await lintOpenApi(spec, loadedRuleset.ruleset);
1200
+ result.source = source;
1201
+ result.ok = !shouldFail(result, options.failOn ?? "error");
1177
1202
  await writeOutputSinks(result, spec, options, artifactWriteFailureMode);
1178
1203
  runtime.latest = result;
1179
1204
  reporter.complete("OpenAPI lint completed.");
@@ -1243,13 +1268,10 @@ const mergeArtifacts = (current, next) => ({
1243
1268
  ...current,
1244
1269
  ...next
1245
1270
  });
1246
- const isEnabled = (options = {}) => {
1247
- return resolveStartupMode(options) !== "off";
1248
- };
1249
1271
  const resolveStartupMode = (options = {}) => {
1250
1272
  if (options.startup?.mode) return options.startup.mode;
1251
1273
  if (typeof options.enabled === "function") return options.enabled(process.env) ? "enforce" : "off";
1252
1274
  return options.enabled === false ? "off" : "enforce";
1253
1275
  };
1254
1276
  //#endregion
1255
- export { recommended as _, OpenApiLintThresholdError as a, shouldFail as c, server as d, resolveReporter as f, loadRuleset as g, loadResolvedRuleset as h, resolveStartupMode as i, presets as l, defaultRulesetResolvers as m, createOpenApiLintRuntime as n, enforceThreshold as o, RulesetLoadError as p, isEnabled as r, exceedsThreshold as s, OpenApiLintArtifactWriteError as t, strict as u, lintOpenApi as v, normalizeFindings as y };
1277
+ export { enforceThreshold as a, strict as c, RulesetLoadError as d, defaultRulesetResolvers as f, lintOpenApi as g, recommended as h, OpenApiLintThresholdError as i, server as l, loadRuleset as m, createOpenApiLintRuntime as n, shouldFail as o, loadResolvedRuleset as p, resolveStartupMode as r, presets as s, OpenApiLintArtifactWriteError as t, resolveReporter as u };
@@ -1,4 +1,4 @@
1
- import { ISpectralDiagnostic, RulesetDefinition } from "@stoplight/spectral-core";
1
+ import { RulesetDefinition } from "@stoplight/spectral-core";
2
2
  import { AnyElysia } from "elysia";
3
3
 
4
4
  //#region src/types.d.ts
@@ -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;
@@ -18,6 +19,7 @@ type OpenApiLintArtifacts = {
18
19
  junitReportPath?: string;
19
20
  sarifReportPath?: string;
20
21
  specSnapshotPath?: string;
22
+ brunoCollectionPath?: string;
21
23
  };
22
24
  type OpenApiLintSinkContext = {
23
25
  spec: Record<string, unknown>;
@@ -40,6 +42,7 @@ type SpectralPluginOptions = {
40
42
  junitReportPath?: string;
41
43
  sarifReportPath?: string;
42
44
  specSnapshotPath?: string | true;
45
+ brunoCollectionPath?: string;
43
46
  pretty?: boolean;
44
47
  artifactWriteFailures?: ArtifactWriteFailureMode;
45
48
  sinks?: OpenApiLintSink[];
@@ -81,6 +84,7 @@ type LintFinding = {
81
84
  type LintRunResult = {
82
85
  ok: boolean;
83
86
  generatedAt: string;
87
+ source: LintRunSource;
84
88
  summary: {
85
89
  error: number;
86
90
  warn: number;
@@ -91,9 +95,6 @@ type LintRunResult = {
91
95
  artifacts?: OpenApiLintArtifacts;
92
96
  findings: LintFinding[];
93
97
  };
94
- interface SpecProvider {
95
- getSpec(): Promise<unknown>;
96
- }
97
98
  type OpenApiLintRuntimeFailure = {
98
99
  name: string;
99
100
  message: string;
@@ -108,7 +109,7 @@ type OpenApiLintRuntime = {
108
109
  lastSuccess: LintRunResult | null;
109
110
  lastFailure: OpenApiLintRuntimeFailure | null;
110
111
  running: boolean;
111
- run: (app: AnyElysia) => Promise<LintRunResult>;
112
+ run: (app: AnyElysia, source?: LintRunSource) => Promise<LintRunResult>;
112
113
  };
113
114
  //#endregion
114
115
  //#region src/core/lint-openapi.d.ts
@@ -149,9 +150,6 @@ declare const loadRuleset: (input?: RulesetResolverInput, baseDirOrOptions?: str
149
150
  declare const loadResolvedRuleset: (input?: RulesetResolverInput, baseDirOrOptions?: string | LoadResolvedRulesetOptions) => Promise<LoadedRuleset>;
150
151
  declare const defaultRulesetResolvers: RulesetResolver[];
151
152
  //#endregion
152
- //#region src/core/normalize-findings.d.ts
153
- declare const normalizeFindings: (diagnostics: ISpectralDiagnostic[], spec: unknown) => LintRunResult;
154
- //#endregion
155
153
  //#region src/core/runtime.d.ts
156
154
  declare const createOpenApiLintRuntime: (options?: SpectralPluginOptions) => OpenApiLintRuntime;
157
155
  declare class OpenApiLintArtifactWriteError extends Error {
@@ -159,7 +157,6 @@ declare class OpenApiLintArtifactWriteError extends Error {
159
157
  readonly cause: unknown;
160
158
  constructor(artifact: string, cause: unknown);
161
159
  }
162
- declare const isEnabled: (options?: SpectralPluginOptions) => boolean;
163
160
  declare const resolveStartupMode: (options?: SpectralPluginOptions) => StartupLintMode;
164
161
  //#endregion
165
162
  //#region src/core/thresholds.d.ts
@@ -168,8 +165,7 @@ declare class OpenApiLintThresholdError extends Error {
168
165
  readonly result: LintRunResult;
169
166
  constructor(threshold: SeverityThreshold, result: LintRunResult);
170
167
  }
171
- declare const exceedsThreshold: (severity: LintSeverity, threshold: SeverityThreshold) => boolean;
172
168
  declare const shouldFail: (result: LintRunResult, threshold: SeverityThreshold) => boolean;
173
169
  declare const enforceThreshold: (result: LintRunResult, threshold: SeverityThreshold) => void;
174
170
  //#endregion
175
- export { OpenApiLintSinkContext as A, LintRunResult as C, OpenApiLintRuntimeFailure as D, OpenApiLintRuntime as E, SpectralPluginOptions as F, StartupLintMode as I, SeverityThreshold as M, SpecProvider as N, OpenApiLintRuntimeStatus as O, SpectralLogger as P, LintFinding as S, OpenApiLintArtifacts 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, PresetName as j, OpenApiLintSink 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, LintSeverity as w, ArtifactWriteFailureMode as x, loadRuleset as y };
171
+ export { SeverityThreshold as A, OpenApiLintArtifacts as C, OpenApiLintSink as D, OpenApiLintRuntimeStatus as E, SpectralPluginOptions as M, StartupLintMode as N, OpenApiLintSinkContext as O, LintSeverity as S, OpenApiLintRuntimeFailure as T, lintOpenApi as _, createOpenApiLintRuntime as a, LintRunResult as b, LoadedRuleset as c, RulesetResolver as d, RulesetResolverContext as f, loadRuleset as g, loadResolvedRuleset as h, OpenApiLintArtifactWriteError as i, SpectralLogger as j, PresetName as k, ResolvedRulesetCandidate as l, defaultRulesetResolvers as m, enforceThreshold as n, resolveStartupMode as o, RulesetResolverInput as p, shouldFail as r, LoadResolvedRulesetOptions as s, OpenApiLintThresholdError as t, RulesetLoadError as u, ArtifactWriteFailureMode as v, OpenApiLintRuntime as w, LintRunSource as x, LintFinding as y };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as OpenApiLintSinkContext, C as LintRunResult, D as OpenApiLintRuntimeFailure, E as OpenApiLintRuntime, F as SpectralPluginOptions, I as StartupLintMode, M as SeverityThreshold, N as SpecProvider, O as OpenApiLintRuntimeStatus, P as SpectralLogger, S as LintFinding, T as OpenApiLintArtifacts, _ 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 PresetName, k as OpenApiLintSink, 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 LintSeverity, x as ArtifactWriteFailureMode, y as loadRuleset } from "./index-YZElrxnl.mjs";
1
+ import { A as SeverityThreshold, C as OpenApiLintArtifacts, D as OpenApiLintSink, E as OpenApiLintRuntimeStatus, M as SpectralPluginOptions, N as StartupLintMode, O as OpenApiLintSinkContext, S as LintSeverity, T as OpenApiLintRuntimeFailure, _ as lintOpenApi, a as createOpenApiLintRuntime, b as LintRunResult, c as LoadedRuleset, d as RulesetResolver, f as RulesetResolverContext, g as loadRuleset, h as loadResolvedRuleset, i as OpenApiLintArtifactWriteError, j as SpectralLogger, k as PresetName, l as ResolvedRulesetCandidate, m as defaultRulesetResolvers, n as enforceThreshold, o as resolveStartupMode, p as RulesetResolverInput, r as shouldFail, s as LoadResolvedRulesetOptions, t as OpenApiLintThresholdError, u as RulesetLoadError, v as ArtifactWriteFailureMode, w as OpenApiLintRuntime, x as LintRunSource, y as LintFinding } from "./index-B4fxn0G6.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, SpectralLogger, SpectralPluginOptions, StartupLintMode, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, 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-DBjV7-E8.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-DTKNy6TU.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 {
@@ -82,4 +82,4 @@ const spectralPlugin = (options = {}) => {
82
82
  return plugin;
83
83
  };
84
84
  //#endregion
85
- export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, exceedsThreshold, isEnabled, lintOpenApi, loadResolvedRuleset, loadRuleset, normalizeFindings, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
85
+ export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, presets, recommended, resolveStartupMode, server, shouldFail, spectralPlugin, strict };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@opsydyn/elysia-spectral",
3
- "version": "0.3.0",
3
+ "version": "0.5.1",
4
4
  "description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
5
- "packageManager": "bun@1.2.9",
5
+ "packageManager": "bun@1.3.11",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -71,6 +71,8 @@
71
71
  "@stoplight/spectral-functions": "^1.10.2",
72
72
  "@stoplight/spectral-parsers": "^1.0.4",
73
73
  "@stoplight/spectral-rulesets": "^1.22.1",
74
+ "@usebruno/converters": "^0.18.1",
75
+ "@usebruno/lang": "^0.35.0",
74
76
  "signale": "^1.4.0",
75
77
  "yaml": "^2.8.1"
76
78
  },