@opsydyn/elysia-spectral 1.1.1 → 1.2.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 +12 -0
- package/README.md +53 -1
- package/dist/core/index.d.mts +1 -1
- package/dist/{index-Dx83hCT9.d.mts → index-CyJXdIRT.d.mts} +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0](https://github.com/opsydyn/elysia-spectral/compare/v1.1.1...v1.2.0) (2026-04-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* optional bearer auth for the lint dashboard route ([7360e1c](https://github.com/opsydyn/elysia-spectral/commit/7360e1c323328005160598832dbd02b315a0cd2f))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* publish dashboard documentation update ([c8d656a](https://github.com/opsydyn/elysia-spectral/commit/c8d656ae1a6bbcd67ce63bd8e430861b07cd216b))
|
|
14
|
+
|
|
3
15
|
## [1.1.1](https://github.com/opsydyn/elysia-spectral/compare/v1.1.0...v1.1.1) (2026-04-28)
|
|
4
16
|
|
|
5
17
|
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Current package scope:
|
|
|
62
62
|
- Bruno collection output (OpenCollection YAML or JSON)
|
|
63
63
|
- reusable runtime for CI and tests
|
|
64
64
|
- opt-in healthcheck endpoint for cached and fresh runs
|
|
65
|
+
- opt-in HTML lint dashboard with severity filters, search, and copy-friendly artifact paths
|
|
65
66
|
|
|
66
67
|
## Data flow
|
|
67
68
|
|
|
@@ -347,6 +348,54 @@ Behavior:
|
|
|
347
348
|
- the route returns `503` when findings meet or exceed `failOn`
|
|
348
349
|
- the route is hidden from generated OpenAPI docs
|
|
349
350
|
|
|
351
|
+
### Add an HTML lint dashboard endpoint
|
|
352
|
+
|
|
353
|
+
The dashboard route is opt-in and renders the latest lint result as a self-contained HTML page — no JS bundle, no build step, no external assets.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
spectralPlugin({
|
|
357
|
+
dashboard: {
|
|
358
|
+
path: '/__openapi/dashboard'
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`dashboard: {}` mounts the default path (`/__openapi/dashboard`); pass `path` to override.
|
|
364
|
+
|
|
365
|
+
To gate the dashboard behind a static bearer token, pass `bearerToken`:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
spectralPlugin({
|
|
369
|
+
dashboard: {
|
|
370
|
+
path: '/__openapi/dashboard',
|
|
371
|
+
bearerToken: process.env.LINT_DASHBOARD_TOKEN
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
When `bearerToken` is set the route returns `401 Unauthorized` (with a `WWW-Authenticate: Bearer` header) unless the request carries a matching `Authorization: Bearer <token>` header. Leave `bearerToken` undefined to keep the route open — useful in local dev. This mirrors the bearer pattern documented in the [Elysia OpenAPI guide](https://elysiajs.com/patterns/openapi).
|
|
377
|
+
|
|
378
|
+
What it surfaces:
|
|
379
|
+
|
|
380
|
+
- pass / fail banner at the configured `failOn` threshold
|
|
381
|
+
- run metadata (timestamp, source, duration, cache hit)
|
|
382
|
+
- severity summary chips that filter the findings table
|
|
383
|
+
- a search input that matches rule code, operation, message, and JSON pointer
|
|
384
|
+
- copy-to-clipboard buttons next to artifact paths
|
|
385
|
+
- the trademarked `SPEC IS TIGHT, SHIP IT RIGHT` tagline on a clean run
|
|
386
|
+
|
|
387
|
+
Keyboard shortcuts: `r` re-runs, `/` focuses the filter, `Enter`/`Space` toggles the focused severity chip.
|
|
388
|
+
|
|
389
|
+
Append `?fresh=1` to force a fresh lint run instead of returning the cached result.
|
|
390
|
+
|
|
391
|
+
| State | Screenshot |
|
|
392
|
+
| --- | --- |
|
|
393
|
+
| Pass |  |
|
|
394
|
+
| Fail |  |
|
|
395
|
+
| Recovered |  |
|
|
396
|
+
|
|
397
|
+
The dashboard is hidden from generated OpenAPI docs and is intended for local and internal-only environments. Gate it behind your standard auth or environment checks before exposing it on a public host.
|
|
398
|
+
|
|
350
399
|
### Persist JSON reports and OpenAPI snapshots
|
|
351
400
|
|
|
352
401
|
```ts
|
|
@@ -651,6 +700,7 @@ That starts `apps/dev-app` with:
|
|
|
651
700
|
- OpenAPI UI at `/openapi`
|
|
652
701
|
- raw OpenAPI JSON at `/openapi/json`
|
|
653
702
|
- opt-in lint healthcheck at `/health/openapi-lint`
|
|
703
|
+
- opt-in HTML lint dashboard at `/api-lint/dashboard`
|
|
654
704
|
- JSON lint report output at `./artifacts/openapi-lint.json`
|
|
655
705
|
- OpenAPI snapshot output at `./elysia-spectral-dev-app.open-api.json`
|
|
656
706
|
|
|
@@ -687,6 +737,7 @@ type SpectralPluginOptions = {
|
|
|
687
737
|
/** Severity level at which the lint run is considered failed. Defaults to 'error'. */
|
|
688
738
|
failOn?: SeverityThreshold
|
|
689
739
|
healthcheck?: false | { path?: string }
|
|
740
|
+
dashboard?: false | { path?: string; bearerToken?: string }
|
|
690
741
|
output?: {
|
|
691
742
|
/** Print findings to the console. Default: true. */
|
|
692
743
|
console?: boolean
|
|
@@ -973,8 +1024,9 @@ Startup linting and route exposure solve different problems:
|
|
|
973
1024
|
|
|
974
1025
|
- startup linting protects boot and local feedback loops
|
|
975
1026
|
- healthchecks expose operational state to external callers
|
|
1027
|
+
- the dashboard turns the same cached result into a human-readable page
|
|
976
1028
|
|
|
977
|
-
Separating them avoids a production surprise where enabling linting also adds a route you did not intend to expose.
|
|
1029
|
+
Separating them avoids a production surprise where enabling linting also adds a route you did not intend to expose. Each route is opt-in, so dashboards stay off by default and never leak into generated OpenAPI docs.
|
|
978
1030
|
|
|
979
1031
|
### Why repo-level rulesets are the default customization path
|
|
980
1032
|
|
package/dist/core/index.d.mts
CHANGED
|
@@ -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-
|
|
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-CyJXdIRT.mjs";
|
|
2
2
|
export { LoadResolvedRulesetOptions, LoadedRuleset, OpenApiLintArtifactWriteError, OpenApiLintThresholdError, ResolvedRulesetCandidate, RulesetLoadError, RulesetResolver, RulesetResolverContext, RulesetResolverInput, createOpenApiLintRuntime, defaultRulesetResolvers, enforceThreshold, lintOpenApi, loadResolvedRuleset, loadRuleset, shouldFail };
|
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-
|
|
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-CyJXdIRT.mjs";
|
|
2
2
|
import { RulesetDefinition } from "@stoplight/spectral-core";
|
|
3
3
|
import { Elysia } from "elysia";
|
|
4
4
|
|
package/dist/index.mjs
CHANGED
|
@@ -288,7 +288,17 @@ const spectralPlugin = (options = {}) => {
|
|
|
288
288
|
}
|
|
289
289
|
if (options.dashboard) {
|
|
290
290
|
const dashboardPath = options.dashboard.path ?? "/__openapi/dashboard";
|
|
291
|
+
const bearerToken = options.dashboard.bearerToken;
|
|
291
292
|
plugin = plugin.get(dashboardPath, async ({ request, set }) => {
|
|
293
|
+
if (bearerToken) {
|
|
294
|
+
const header = request.headers.get("authorization") ?? "";
|
|
295
|
+
if ((header.startsWith("Bearer ") ? header.slice(7) : "") !== bearerToken) {
|
|
296
|
+
set.status = 401;
|
|
297
|
+
set.headers["www-authenticate"] = "Bearer realm=\"elysia-spectral\"";
|
|
298
|
+
set.headers["content-type"] = "text/plain; charset=utf-8";
|
|
299
|
+
return "Unauthorized";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
292
302
|
const fresh = new URL(request.url).searchParams.get("fresh") === "1";
|
|
293
303
|
const threshold = options.failOn ?? "error";
|
|
294
304
|
const currentApp = hostAppRef.current;
|