@opsydyn/elysia-spectral 1.5.0 → 1.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.
@@ -1,446 +1,10 @@
1
- import spectralCore from "@stoplight/spectral-core";
2
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
3
1
  import path from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import spectralFunctions from "@stoplight/spectral-functions";
6
- import spectralRulesets from "@stoplight/spectral-rulesets";
7
- import YAML from "yaml";
8
2
  import signale from "signale";
9
3
  import { styleText } from "node:util";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
10
5
  import { brunoToOpenCollection, openApiToBruno } from "@usebruno/converters";
11
- //#region src/core/finding-guidance.ts
12
- const guidanceByCode = {
13
- "elysia-operation-summary": "Add detail.summary to the Elysia route options so generated docs and clients have a short operation label.",
14
- "elysia-operation-tags": "Add detail.tags with at least one stable tag, for example ['Users'] or ['Dev'].",
15
- "operation-description": "Add detail.description with a short user-facing explanation of what the route does.",
16
- "operation-tags": "Add a non-empty detail.tags array on the route so the OpenAPI operation is grouped consistently.",
17
- "operation-operationId": "Add detail.operationId with a unique camelCase identifier so generated clients and SDKs have stable method names.",
18
- "operation-success-response": "Add at least one 2xx response schema to the route, for example response: { 200: t.Object(...) }.",
19
- "oas3-api-servers": "Add a servers array to the OpenAPI documentation config with at least one base URL.",
20
- "info-contact": "Add an info.contact object to the OpenAPI documentation config with a name and url or email.",
21
- "rfc9457-problem-details": "Add an \"application/problem+json\" content entry to the error response. See RFC 9457 for the Problem Details schema."
22
- };
23
- const getFindingRecommendation = (code, message) => {
24
- const direct = guidanceByCode[code];
25
- if (direct) return direct;
26
- if (code === "oas3-schema" && message.includes("required property \"responses\"")) return "Add a response schema to the route, for example response: { 200: t.Object(...) } or response: { 200: t.Array(...) }.";
27
- if (code.startsWith("operation-")) return "Add the missing operation metadata under detail on the Elysia route options.";
28
- };
29
- //#endregion
30
- //#region src/core/normalize-findings.ts
31
- const httpMethods = new Set([
32
- "get",
33
- "put",
34
- "post",
35
- "delete",
36
- "options",
37
- "head",
38
- "patch",
39
- "trace"
40
- ]);
41
- const normalizeFindings = (diagnostics, spec) => {
42
- const findings = diagnostics.map((diagnostic) => normalizeFinding(diagnostic, spec));
43
- const summary = findings.reduce((current, finding) => {
44
- current[finding.severity] += 1;
45
- current.total += 1;
46
- return current;
47
- }, {
48
- error: 0,
49
- warn: 0,
50
- info: 0,
51
- hint: 0,
52
- total: 0
53
- });
54
- return {
55
- ok: summary.error === 0,
56
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
- source: "manual",
58
- failOn: "error",
59
- durationMs: null,
60
- summary,
61
- findings
62
- };
63
- };
64
- const normalizeFinding = (diagnostic, spec) => {
65
- const path = [...diagnostic.path];
66
- const finding = {
67
- code: String(diagnostic.code),
68
- message: diagnostic.message,
69
- severity: toLintSeverity(diagnostic.severity),
70
- path,
71
- documentPointer: toDocumentPointer(path)
72
- };
73
- const recommendation = getFindingRecommendation(String(diagnostic.code), diagnostic.message);
74
- if (recommendation) finding.recommendation = recommendation;
75
- if (diagnostic.source !== void 0) finding.source = diagnostic.source;
76
- if (diagnostic.range) finding.range = {
77
- start: diagnostic.range.start,
78
- end: diagnostic.range.end
79
- };
80
- const operation = inferOperation(path, spec);
81
- if (operation) finding.operation = operation;
82
- return finding;
83
- };
84
- const toLintSeverity = (severity) => {
85
- switch (severity) {
86
- case 0: return "error";
87
- case 1: return "warn";
88
- case 2: return "info";
89
- default: return "hint";
90
- }
91
- };
92
- const toDocumentPointer = (path) => {
93
- if (path.length === 0) return "";
94
- return `/${path.map((segment) => String(segment).replace(/~/g, "~0").replace(/\//g, "~1")).join("/")}`;
95
- };
96
- const inferOperation = (path, spec) => {
97
- if (path[0] !== "paths") return;
98
- const routePath = typeof path[1] === "string" ? path[1] : void 0;
99
- const method = typeof path[2] === "string" && httpMethods.has(path[2]) ? path[2] : void 0;
100
- if (!routePath && !method) return;
101
- const operationRecord = routePath && method ? getNestedValue(spec, [
102
- "paths",
103
- routePath,
104
- method
105
- ]) : void 0;
106
- const operation = {};
107
- if (routePath !== void 0) operation.path = routePath;
108
- if (method !== void 0) operation.method = method;
109
- if (operationRecord && typeof operationRecord === "object" && "operationId" in operationRecord) operation.operationId = String(operationRecord.operationId);
110
- return operation;
111
- };
112
- const getNestedValue = (value, path) => {
113
- let current = value;
114
- for (const segment of path) {
115
- if (current === null || typeof current !== "object") return;
116
- current = current[segment];
117
- }
118
- return current;
119
- };
120
- //#endregion
121
- //#region src/core/lint-openapi.ts
122
- const { Spectral } = spectralCore;
123
- const lintOpenApi = async (spec, ruleset) => {
124
- const spectral = new Spectral();
125
- spectral.setRuleset(ruleset);
126
- return normalizeFindings(await spectral.run(spec, { ignoreUnknownFormat: false }), spec);
127
- };
128
- //#endregion
129
- //#region src/presets/recommended.ts
130
- const { schema: schema$3, truthy: truthy$3 } = spectralFunctions;
131
- const { oas: oas$3 } = spectralRulesets;
132
- const operationSelector$2 = "$.paths[*][get,put,post,delete,options,head,patch,trace]";
133
- /**
134
- * Baseline quality preset. Equivalent to the package default ruleset.
135
- *
136
- * - Extends spectral:oas/recommended
137
- * - elysia-operation-summary and elysia-operation-tags at warn
138
- * - oas3-api-servers and info-contact disabled (local-dev friendly)
139
- */
140
- const recommended = {
141
- extends: [[oas$3, "recommended"]],
142
- rules: {
143
- "oas3-api-servers": "off",
144
- "info-contact": "off",
145
- "elysia-operation-summary": {
146
- description: "Operations should define a summary for generated docs and clients.",
147
- severity: "warn",
148
- given: operationSelector$2,
149
- then: {
150
- field: "summary",
151
- function: truthy$3
152
- }
153
- },
154
- "elysia-operation-tags": {
155
- description: "Operations should declare at least one tag for grouping and downstream tooling.",
156
- severity: "warn",
157
- given: operationSelector$2,
158
- then: {
159
- field: "tags",
160
- function: schema$3,
161
- functionOptions: { schema: {
162
- type: "array",
163
- minItems: 1
164
- } }
165
- }
166
- }
167
- }
168
- };
169
- //#endregion
170
- //#region src/rulesets/default-ruleset.ts
171
- const defaultRuleset = recommended;
172
- //#endregion
173
- //#region src/core/load-ruleset.ts
174
- const { alphabetical, casing, defined, enumeration, falsy, length, or, pattern, schema: schema$2, truthy: truthy$2, undefined: undefinedFunction, unreferencedReusableObject, xor } = spectralFunctions;
175
- const { oas: oas$2 } = spectralRulesets;
176
- const functionMap = {
177
- alphabetical,
178
- casing,
179
- defined,
180
- enumeration,
181
- falsy,
182
- length,
183
- or,
184
- pattern,
185
- schema: schema$2,
186
- truthy: truthy$2,
187
- undefined: undefinedFunction,
188
- unreferencedReusableObject,
189
- xor
190
- };
191
- const extendsMap = { "spectral:oas": oas$2 };
192
- const autodiscoverRulesetFilenames = [
193
- "spectral.yaml",
194
- "spectral.yml",
195
- "spectral.ts",
196
- "spectral.mts",
197
- "spectral.cts",
198
- "spectral.js",
199
- "spectral.mjs",
200
- "spectral.cjs",
201
- "spectral.config.yaml",
202
- "spectral.config.yml",
203
- "spectral.config.ts",
204
- "spectral.config.mts",
205
- "spectral.config.cts",
206
- "spectral.config.js",
207
- "spectral.config.mjs",
208
- "spectral.config.cjs"
209
- ];
210
- var RulesetLoadError = class extends Error {
211
- constructor(message, options) {
212
- super(message);
213
- this.name = "RulesetLoadError";
214
- if (options?.cause !== void 0) this.cause = options.cause;
215
- }
216
- };
217
- const loadRuleset = async (input, baseDirOrOptions = process.cwd()) => {
218
- return (await loadResolvedRuleset(input, baseDirOrOptions)).ruleset;
219
- };
220
- const loadResolvedRuleset = async (input, baseDirOrOptions = process.cwd()) => {
221
- const options = normalizeLoadResolvedRulesetOptions(baseDirOrOptions);
222
- const context = {
223
- baseDir: options.baseDir,
224
- defaultRuleset: options.defaultRuleset,
225
- mergeAutodiscoveredWithDefault: options.mergeAutodiscoveredWithDefault
226
- };
227
- for (const resolver of options.resolvers) {
228
- const loaded = await resolver(input, context);
229
- if (loaded) {
230
- const normalized = { ruleset: normalizeRulesetDefinition(loaded.ruleset) };
231
- if (loaded.source) normalized.source = loaded.source;
232
- return normalized;
233
- }
234
- }
235
- if (input === void 0) return { ruleset: options.defaultRuleset };
236
- throw new RulesetLoadError("Ruleset input could not be resolved.");
237
- };
238
- const normalizeLoadResolvedRulesetOptions = (value) => {
239
- if (typeof value === "string") return {
240
- baseDir: value,
241
- resolvers: defaultRulesetResolvers,
242
- mergeAutodiscoveredWithDefault: true,
243
- defaultRuleset
244
- };
245
- return {
246
- baseDir: value.baseDir ?? process.cwd(),
247
- resolvers: value.resolvers ?? defaultRulesetResolvers,
248
- mergeAutodiscoveredWithDefault: value.mergeAutodiscoveredWithDefault ?? true,
249
- defaultRuleset: value.defaultRuleset ?? defaultRuleset
250
- };
251
- };
252
- const resolveAutodiscoveredRuleset = async (input, context) => {
253
- if (input !== void 0) return;
254
- const autodiscoveredPath = await findAutodiscoveredRulesetPath(context.baseDir);
255
- if (!autodiscoveredPath) return;
256
- const loaded = await loadResolvedPathRuleset(autodiscoveredPath, context);
257
- if (!context.mergeAutodiscoveredWithDefault) return {
258
- ...loaded,
259
- source: {
260
- path: autodiscoveredPath,
261
- autodiscovered: true,
262
- mergedWithDefault: false
263
- }
264
- };
265
- return {
266
- ruleset: mergeRulesets(context.defaultRuleset, loaded.ruleset),
267
- source: {
268
- path: autodiscoveredPath,
269
- autodiscovered: true,
270
- mergedWithDefault: true
271
- }
272
- };
273
- };
274
- const resolvePathRuleset = async (input, context) => {
275
- if (typeof input !== "string") return;
276
- return await loadResolvedPathRuleset(input, context);
277
- };
278
- const resolveInlineRuleset = async (input) => {
279
- if (input === void 0 || typeof input === "string") return;
280
- return { ruleset: normalizeRulesetDefinition(input) };
281
- };
282
- const defaultRulesetResolvers = [
283
- resolveAutodiscoveredRuleset,
284
- resolvePathRuleset,
285
- resolveInlineRuleset
286
- ];
287
- const loadResolvedPathRuleset = async (inputPath, context) => {
288
- const resolvedPath = path.resolve(context.baseDir, inputPath);
289
- if (isYamlRulesetPath(resolvedPath)) return {
290
- ruleset: await loadYamlRuleset(resolvedPath),
291
- source: {
292
- path: inputPath,
293
- autodiscovered: false,
294
- mergedWithDefault: false
295
- }
296
- };
297
- if (!isModuleRulesetPath(resolvedPath)) throw new RulesetLoadError(`Unsupported ruleset path: ${inputPath}. Supported local rulesets are .yaml, .yml, .js, .mjs, .cjs, .ts, .mts, and .cts.`);
298
- return {
299
- ruleset: await loadModuleRuleset(resolvedPath),
300
- source: {
301
- path: inputPath,
302
- autodiscovered: false,
303
- mergedWithDefault: false
304
- }
305
- };
306
- };
307
- const findAutodiscoveredRulesetPath = async (baseDir) => {
308
- for (const filename of autodiscoverRulesetFilenames) {
309
- const candidatePath = path.resolve(baseDir, filename);
310
- try {
311
- await access(candidatePath);
312
- return `./${filename}`;
313
- } catch (error) {
314
- if (error.code !== "ENOENT") throw error;
315
- }
316
- }
317
- };
318
- const loadYamlRuleset = async (resolvedPath) => {
319
- let fileContents;
320
- try {
321
- fileContents = await readFile(resolvedPath, "utf8");
322
- } catch (error) {
323
- throw new RulesetLoadError(`Unable to read ruleset at ${resolvedPath}.`, { cause: error });
324
- }
325
- let parsed;
326
- try {
327
- parsed = YAML.parse(fileContents);
328
- } catch (error) {
329
- throw new RulesetLoadError(`Unable to parse YAML ruleset at ${resolvedPath}.`, { cause: error });
330
- }
331
- return normalizeRulesetDefinition(parsed);
332
- };
333
- const loadModuleRuleset = async (resolvedPath) => {
334
- let imported;
335
- try {
336
- imported = await import(pathToFileURL(resolvedPath).href);
337
- } catch (error) {
338
- throw new RulesetLoadError(`Unable to import module ruleset at ${resolvedPath}.`, { cause: error });
339
- }
340
- const resolvedRuleset = resolveModuleRulesetValue(imported);
341
- if (resolvedRuleset === void 0) throw new RulesetLoadError(`Module ruleset at ${resolvedPath} must export a ruleset as the default export or a named "ruleset" export.`);
342
- return normalizeRulesetDefinition(resolvedRuleset, {
343
- ...functionMap,
344
- ...resolveModuleFunctions(imported)
345
- });
346
- };
347
- const resolveModuleRulesetValue = (imported) => {
348
- if (!isRecord$1(imported)) return;
349
- if ("default" in imported) return imported.default;
350
- if ("ruleset" in imported) return imported.ruleset;
351
- };
352
- const resolveModuleFunctions = (imported) => {
353
- if (!isRecord$1(imported) || !("functions" in imported)) return {};
354
- const { functions } = imported;
355
- if (!isRecord$1(functions)) throw new RulesetLoadError("Module ruleset \"functions\" export must be an object map of function names to Spectral functions.");
356
- const entries = Object.entries(functions).filter(([, value]) => typeof value === "function");
357
- return Object.fromEntries(entries);
358
- };
359
- const isYamlRulesetPath = (value) => value.endsWith(".yaml") || value.endsWith(".yml");
360
- const isModuleRulesetPath = (value) => value.endsWith(".js") || value.endsWith(".mjs") || value.endsWith(".cjs") || value.endsWith(".ts") || value.endsWith(".mts") || value.endsWith(".cts");
361
- const normalizeRulesetDefinition = (input, availableFunctions = functionMap) => {
362
- if (!isRecord$1(input)) throw new RulesetLoadError("Ruleset must be an object.");
363
- const normalized = { ...input };
364
- if ("extends" in normalized) normalized.extends = normalizeExtends(normalized.extends);
365
- if ("rules" in normalized) normalized.rules = normalizeRules(normalized.rules, availableFunctions);
366
- return normalized;
367
- };
368
- const mergeRuleEntry = (base, override) => {
369
- if (!isRecord$1(override)) return override;
370
- if ("given" in override || "then" in override) return override;
371
- if (isRecord$1(base) && ("given" in base || "then" in base)) return {
372
- ...base,
373
- ...override
374
- };
375
- const keys = Object.keys(override);
376
- if (keys.length === 1 && keys[0] === "severity") return override.severity;
377
- return override;
378
- };
379
- const mergeRulesets = (baseRuleset, overrideRuleset) => {
380
- const mergedBase = baseRuleset;
381
- const mergedOverride = overrideRuleset;
382
- const baseRules = isRecord$1(mergedBase.rules) ? mergedBase.rules : {};
383
- const overrideRules = isRecord$1(mergedOverride.rules) ? mergedOverride.rules : {};
384
- const mergedRules = { ...baseRules };
385
- for (const [name, overrideRule] of Object.entries(overrideRules)) mergedRules[name] = mergeRuleEntry(baseRules[name], overrideRule);
386
- const baseExtends = toExtendsArray(mergedBase.extends);
387
- const overrideExtends = toExtendsArray(mergedOverride.extends);
388
- const mergedExtends = [...baseExtends, ...overrideExtends];
389
- const merged = {
390
- ...mergedBase,
391
- ...mergedOverride
392
- };
393
- delete merged.extends;
394
- delete merged.rules;
395
- if (mergedExtends.length > 0) merged.extends = mergedExtends;
396
- if (Object.keys(mergedRules).length > 0) merged.rules = mergedRules;
397
- return merged;
398
- };
399
- const toExtendsArray = (value) => {
400
- if (value === void 0) return [];
401
- return Array.isArray(value) ? [...value] : [value];
402
- };
403
- const normalizeExtends = (value) => {
404
- if (typeof value === "string") return resolveExtendsEntry(value);
405
- if (!Array.isArray(value)) return value;
406
- return value.map((entry) => {
407
- if (typeof entry === "string") return resolveExtendsEntry(entry);
408
- if (Array.isArray(entry) && entry.length >= 1 && typeof entry[0] === "string") return [resolveExtendsEntry(entry[0]), entry[1]];
409
- return entry;
410
- });
411
- };
412
- const resolveExtendsEntry = (value) => {
413
- const resolved = extendsMap[value];
414
- if (!resolved) throw new RulesetLoadError(`Unsupported ruleset extend target: "${value}". Supported extend targets: spectral:oas.`);
415
- return resolved;
416
- };
417
- const normalizeRules = (value, availableFunctions) => {
418
- if (!isRecord$1(value)) return value;
419
- const entries = Object.entries(value).map(([ruleName, ruleValue]) => [ruleName, normalizeRule(ruleValue, availableFunctions)]);
420
- return Object.fromEntries(entries);
421
- };
422
- const normalizeRule = (value, availableFunctions) => {
423
- if (!isRecord$1(value)) return value;
424
- const normalized = { ...value };
425
- if ("then" in normalized) normalized.then = normalizeThen(normalized.then, availableFunctions);
426
- return normalized;
427
- };
428
- const normalizeThen = (value, availableFunctions) => {
429
- if (Array.isArray(value)) return value.map((entry) => normalizeThenEntry(entry, availableFunctions));
430
- return normalizeThenEntry(value, availableFunctions);
431
- };
432
- const normalizeThenEntry = (value, availableFunctions) => {
433
- if (!isRecord$1(value)) return value;
434
- const normalized = { ...value };
435
- if (typeof normalized.function === "string") {
436
- const resolved = availableFunctions[normalized.function];
437
- if (!resolved) throw new RulesetLoadError(`Unsupported Spectral function: ${String(normalized.function)}.`);
438
- normalized.function = resolved;
439
- }
440
- return normalized;
441
- };
442
- const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
443
- //#endregion
6
+ import YAML from "yaml";
7
+ import { pathToFileURL } from "node:url";
444
8
  //#region src/output/terminal-format.ts
445
9
  const severityStyles = {
446
10
  error: [
@@ -915,133 +479,6 @@ const createOutputSinks = (options) => {
915
479
  return sinks;
916
480
  };
917
481
  //#endregion
918
- //#region src/presets/server.ts
919
- const { schema: schema$1, truthy: truthy$1 } = spectralFunctions;
920
- const { oas: oas$1 } = spectralRulesets;
921
- const operationSelector$1 = "$.paths[*][get,put,post,delete,options,head,patch,trace]";
922
- /**
923
- * Production API quality preset. Suitable as a CI gate for teams shipping
924
- * public or internal APIs where contract quality matters.
925
- *
926
- * Tightens recommended:
927
- * - elysia-operation-summary and elysia-operation-tags escalated to error
928
- * - operation-description, operation-operationId, operation-success-response at warn
929
- * - oas3-api-servers at warn (servers should be declared in production specs)
930
- */
931
- const server = {
932
- extends: [[oas$1, "recommended"]],
933
- rules: {
934
- "oas3-api-servers": "warn",
935
- "info-contact": "off",
936
- "elysia-operation-summary": {
937
- description: "Operations should define a summary for generated docs and clients.",
938
- severity: "error",
939
- given: operationSelector$1,
940
- then: {
941
- field: "summary",
942
- function: truthy$1
943
- }
944
- },
945
- "elysia-operation-tags": {
946
- description: "Operations should declare at least one tag for grouping and downstream tooling.",
947
- severity: "error",
948
- given: operationSelector$1,
949
- then: {
950
- field: "tags",
951
- function: schema$1,
952
- functionOptions: { schema: {
953
- type: "array",
954
- minItems: 1
955
- } }
956
- }
957
- },
958
- "operation-description": "warn",
959
- "operation-operationId": "warn",
960
- "operation-success-response": "warn"
961
- }
962
- };
963
- //#endregion
964
- //#region src/presets/strict.ts
965
- const { schema, truthy } = spectralFunctions;
966
- const { oas } = spectralRulesets;
967
- const operationSelector = "$.paths[*][get,put,post,delete,options,head,patch,trace]";
968
- const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
969
- /**
970
- * Checks that all 4xx/5xx responses declare application/problem+json as
971
- * their content type, conforming to RFC 9457 Problem Details for HTTP APIs.
972
- */
973
- const checkProblemDetails = (operation) => {
974
- if (!isRecord(operation) || !isRecord(operation.responses)) return;
975
- const results = [];
976
- for (const [statusCode, response] of Object.entries(operation.responses)) {
977
- const code = Number(statusCode);
978
- if (!Number.isFinite(code) || code < 400) continue;
979
- if (!isRecord(response)) continue;
980
- const content = response.content;
981
- if (!isRecord(content) || !("application/problem+json" in content)) results.push({
982
- message: `${statusCode} error response should use "application/problem+json" content type (RFC 9457 Problem Details).`,
983
- path: ["responses", statusCode]
984
- });
985
- }
986
- return results.length > 0 ? results : void 0;
987
- };
988
- /**
989
- * Full API governance preset. Suitable for teams with formal API governance
990
- * requirements, public API programs, or downstream client generation pipelines.
991
- *
992
- * Tightens server:
993
- * - All elysia rules and operation metadata rules escalated to error
994
- * - info-contact at warn (API ownership should be declared)
995
- * - oas3-api-servers at error (server declaration is required)
996
- * - rfc9457-problem-details at warn (error responses should use Problem Details)
997
- */
998
- const strict = {
999
- extends: [[oas, "recommended"]],
1000
- rules: {
1001
- "oas3-api-servers": "error",
1002
- "info-contact": "warn",
1003
- "elysia-operation-summary": {
1004
- description: "Operations should define a summary for generated docs and clients.",
1005
- severity: "error",
1006
- given: operationSelector,
1007
- then: {
1008
- field: "summary",
1009
- function: truthy
1010
- }
1011
- },
1012
- "elysia-operation-tags": {
1013
- description: "Operations should declare at least one tag for grouping and downstream tooling.",
1014
- severity: "error",
1015
- given: operationSelector,
1016
- then: {
1017
- field: "tags",
1018
- function: schema,
1019
- functionOptions: { schema: {
1020
- type: "array",
1021
- minItems: 1
1022
- } }
1023
- }
1024
- },
1025
- "operation-description": "error",
1026
- "operation-operationId": "error",
1027
- "operation-success-response": "error",
1028
- "rfc9457-problem-details": {
1029
- description: "Error responses (4xx, 5xx) should use RFC 9457 Problem Details (application/problem+json).",
1030
- message: "{{error}}",
1031
- severity: "warn",
1032
- given: operationSelector,
1033
- then: { function: checkProblemDetails }
1034
- }
1035
- }
1036
- };
1037
- //#endregion
1038
- //#region src/presets/index.ts
1039
- const presets = {
1040
- recommended,
1041
- server,
1042
- strict
1043
- };
1044
- //#endregion
1045
482
  //#region src/providers/spec-provider.ts
1046
483
  var BaseSpecProvider = class {
1047
484
  constructor(app, options = {}) {
@@ -1078,11 +515,11 @@ var PublicSpecProvider = class extends BaseSpecProvider {
1078
515
  if (response.ok) return await parseSpecResponse(response, sourceLabel, this.specPath);
1079
516
  if (this.options?.baseUrl) return await this.fetchViaLoopback();
1080
517
  const body = await safeReadBody(response);
1081
- throw new PublicSpecProviderError([`Unable to load OpenAPI JSON from ${this.specPath} via ${sourceLabel}: received ${describeResponse(response)}${body ? ` with body ${body}.` : "."}`, `Fix: ensure @elysiajs/openapi is mounted and exposing "${this.specPath}", or update source.specPath to the correct OpenAPI JSON route.`].join(" "));
518
+ throw new PublicSpecProviderError([`Unable to load OpenAPI JSON from ${this.specPath} via ${sourceLabel}: received ${describeResponse(response)}${body ? ` with body ${body}.` : "."}`, `Fix: ensure an OpenAPI generator (for example @elysiajs/openapi) is installed and mounted so it exposes "${this.specPath}", or update source.specPath to the correct OpenAPI JSON route.`].join(" "));
1082
519
  } catch (error) {
1083
520
  if (this.options?.baseUrl) return await this.fetchViaLoopback(error);
1084
521
  if (error instanceof PublicSpecProviderError) throw error;
1085
- throw new PublicSpecProviderError([`Unable to resolve OpenAPI JSON from ${this.specPath} via ${sourceLabel}.`, "Fix: ensure the app can serve the configured OpenAPI JSON route, or set source.baseUrl if the document is only reachable over HTTP."].join(" "), { cause: error });
522
+ throw new PublicSpecProviderError([`Unable to resolve OpenAPI JSON from ${this.specPath} via ${sourceLabel}.`, "Fix: ensure the app mounts an OpenAPI generator (for example @elysiajs/openapi) or otherwise serves the configured OpenAPI JSON route, or set source.baseUrl if the document is only reachable over HTTP."].join(" "), { cause: error });
1086
523
  }
1087
524
  }
1088
525
  async fetchViaLoopback(cause) {
@@ -1192,7 +629,13 @@ const createOpenApiLintRuntime = (options = {}) => {
1192
629
  reporter.start("OpenAPI lint started.");
1193
630
  try {
1194
631
  const spec = await new PublicSpecProvider(app, options.source).getSpec();
1195
- const loadedRuleset = await loadResolvedRuleset(options.ruleset, { ...options.preset ? { defaultRuleset: presets[options.preset] } : {} });
632
+ const [{ lintOpenApi }, { loadResolvedRuleset }, presetsModule] = await Promise.all([
633
+ import("./lint-openapi-D76sC7S5.mjs").then((n) => n.n),
634
+ import("./load-ruleset-CiikrzWx.mjs").then((n) => n.i),
635
+ options.preset ? import("./presets-CCfU_diN.mjs").then((n) => n.n) : Promise.resolve(null)
636
+ ]);
637
+ const defaultRuleset = options.preset ? presetsModule?.presets[options.preset] : void 0;
638
+ const loadedRuleset = await loadResolvedRuleset(options.ruleset, { ...defaultRuleset ? { defaultRuleset } : {} });
1196
639
  if (loadedRuleset.source?.autodiscovered) {
1197
640
  const base = options.preset ? `"${options.preset}" preset` : "package default ruleset";
1198
641
  reporter.ruleset(`OpenAPI lint autodiscovered ruleset ${loadedRuleset.source.path} and merged it with the ${base}.`);
@@ -1287,4 +730,4 @@ const resolveStartupMode = (options = {}) => {
1287
730
  return options.enabled === false ? "off" : "enforce";
1288
731
  };
1289
732
  //#endregion
1290
- 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 };
733
+ export { enforceThreshold as a, OpenApiLintThresholdError as i, createOpenApiLintRuntime as n, shouldFail as o, resolveStartupMode as r, resolveReporter as s, OpenApiLintArtifactWriteError as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsydyn/elysia-spectral",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Thin Elysia plugin that lints generated OpenAPI documents with Spectral.",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "publishConfig": {