@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 +21 -0
- package/README.md +166 -11
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{core-DBjV7-E8.mjs → core-DTKNy6TU.mjs} +28 -6
- package/dist/{index-YZElrxnl.d.mts → index-B4fxn0G6.d.mts} +7 -11
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +4 -4
- package/package.json +4 -2
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
|
|
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 {
|
|
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
|
-
|
|
443
|
+
preset: 'strict',
|
|
440
444
|
failOn: 'error',
|
|
441
445
|
output: {
|
|
442
446
|
console: true,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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 `
|
|
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
|
-
|
|
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).
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold,
|
|
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 };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { OpenApiLintArtifactWriteError, OpenApiLintThresholdError, RulesetLoadError, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold,
|
|
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}.
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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,
|
|
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 {
|
|
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,
|
|
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
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
|
|
5
|
-
"packageManager": "bun@1.
|
|
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
|
},
|