@scalar/openapi-parser 0.25.3 → 0.25.4
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 +6 -0
- package/dist/lib/Validator/Validator.d.ts.map +1 -1
- package/dist/lib/Validator/Validator.js +5 -2
- package/dist/lib/Validator/Validator.js.map +2 -2
- package/dist/utils/validate-path-parameters.d.ts +6 -0
- package/dist/utils/validate-path-parameters.d.ts.map +1 -0
- package/dist/utils/validate-path-parameters.js +95 -0
- package/dist/utils/validate-path-parameters.js.map +7 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Validator.d.ts","sourceRoot":"","sources":["../../../src/lib/Validator/Validator.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAKhD,OAAO,EAAiC,KAAK,cAAc,EAAmB,MAAM,iBAAiB,CAAA;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"Validator.d.ts","sourceRoot":"","sources":["../../../src/lib/Validator/Validator.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAKhD,OAAO,EAAiC,KAAK,cAAc,EAAmB,MAAM,iBAAiB,CAAA;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAe9F,qBAAa,SAAS;IACb,OAAO,EAAE,cAAc,CAAA;IAE9B,OAAc,iBAAiB,oCAAkB;IAGjD,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAK;IAE9D,SAAS,CAAC,MAAM,EAAE,MAAM,CAAA;IAExB,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAA;IAEtC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAE5B,aAAa,EAAE,SAAS,CAAA;IAE/B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,cAAc;IAsF9E;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,gBAAgB;CAkC3D"}
|
|
@@ -6,6 +6,7 @@ import { ERRORS, OpenApiSpecifications, OpenApiVersions } from "../../configurat
|
|
|
6
6
|
import { details as getOpenApiVersion } from "../../utils/details.js";
|
|
7
7
|
import { resolveReferences } from "../../utils/resolve-references.js";
|
|
8
8
|
import { transformErrors } from "../../utils/transform-errors.js";
|
|
9
|
+
import { validatePathParameters } from "../../utils/validate-path-parameters.js";
|
|
9
10
|
const jsonSchemaVersions = {
|
|
10
11
|
"http://json-schema.org/draft-04/schema#": Ajv04,
|
|
11
12
|
"http://json-schema.org/draft-07/schema#": Ajv,
|
|
@@ -67,9 +68,11 @@ class Validator {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
const resolvedReferences = resolveReferences(filesystem, options);
|
|
71
|
+
const semanticErrors = validatePathParameters(resolvedReferences.schema);
|
|
72
|
+
const errors = [...resolvedReferences.errors, ...semanticErrors];
|
|
70
73
|
return {
|
|
71
|
-
valid: schemaResult && resolvedReferences.valid,
|
|
72
|
-
errors
|
|
74
|
+
valid: schemaResult && resolvedReferences.valid && semanticErrors.length === 0,
|
|
75
|
+
errors,
|
|
73
76
|
schema: resolvedReferences.schema
|
|
74
77
|
};
|
|
75
78
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/Validator/Validator.ts"],
|
|
4
|
-
"sourcesContent": ["import Ajv, { type ValidateFunction } from 'ajv'\nimport Ajv2020 from 'ajv/dist/2020.js'\nimport Ajv04 from 'ajv-draft-04'\nimport addFormats from 'ajv-formats'\n\nimport { ERRORS, OpenApiSpecifications, type OpenApiVersion, OpenApiVersions } from '@/configuration'\nimport type { AnyObject, Filesystem, ThrowOnErrorOption, ValidateResult } from '@/types/index'\nimport { details as getOpenApiVersion } from '@/utils/details'\nimport { resolveReferences } from '@/utils/resolve-references'\nimport { transformErrors } from '@/utils/transform-errors'\n\n/**\n * Configure available JSON Schema versions\n */\nconst jsonSchemaVersions = {\n 'http://json-schema.org/draft-04/schema#': Ajv04,\n 'http://json-schema.org/draft-07/schema#': Ajv,\n 'https://json-schema.org/draft/2020-12/schema': Ajv2020,\n}\n\nexport class Validator {\n public version: OpenApiVersion\n\n public static supportedVersions = OpenApiVersions\n\n // Object with function *or* object { errors: string }\n protected ajvValidators: Record<string, ValidateFunction> = {}\n\n protected errors: string\n\n protected specificationVersion: string\n\n protected specificationType: string\n\n public specification: AnyObject\n\n /**\n * Checks whether a specification is valid and all references can be resolved.\n */\n validate(filesystem: Filesystem, options?: ThrowOnErrorOption): ValidateResult {\n const entrypoint = filesystem.find((file) => file.isEntrypoint)\n const specification = entrypoint?.specification\n\n // TODO: How does this work with a filesystem?\n this.specification = specification\n\n // TODO: defaulting info.version to keep parser compatible with the previous one\n // we should bubble this error up and not throw on it\n if (this.specification?.info && !this.specification.info.version) {\n this.specification.info.version = '0.0.1'\n }\n\n try {\n // AnyObject is empty or invalid\n if (specification === undefined || specification === null) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EMPTY_OR_INVALID)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, ERRORS.EMPTY_OR_INVALID),\n }\n }\n\n // Meta data about the specification\n const { version, specificationType, specificationVersion } = getOpenApiVersion(specification)\n\n this.version = version\n this.specificationVersion = specificationVersion\n this.specificationType = specificationType\n\n // AnyObject is not supported\n if (!version) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.OPENAPI_VERSION_NOT_SUPPORTED)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, ERRORS.OPENAPI_VERSION_NOT_SUPPORTED),\n }\n }\n\n // Get the correct OpenAPI validator\n const validateSchema = this.getAjvValidator(version)\n const schemaResult = validateSchema(specification)\n\n // Error handling\n if (validateSchema.errors) {\n if (validateSchema.errors.length > 0) {\n if (options?.throwOnError) {\n throw new Error(validateSchema.errors[0].message)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, validateSchema.errors),\n }\n }\n }\n\n // Check if the references are valid\n const resolvedReferences = resolveReferences(filesystem, options)\n\n return {\n valid: schemaResult && resolvedReferences.valid,\n errors
|
|
5
|
-
"mappings": "AAAA,OAAO,aAAoC;AAC3C,OAAO,aAAa;AACpB,OAAO,WAAW;AAClB,OAAO,gBAAgB;AAEvB,SAAS,QAAQ,uBAA4C,uBAAuB;AAEpF,SAAS,WAAW,yBAAyB;AAC7C,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import Ajv, { type ValidateFunction } from 'ajv'\nimport Ajv2020 from 'ajv/dist/2020.js'\nimport Ajv04 from 'ajv-draft-04'\nimport addFormats from 'ajv-formats'\n\nimport { ERRORS, OpenApiSpecifications, type OpenApiVersion, OpenApiVersions } from '@/configuration'\nimport type { AnyObject, Filesystem, ThrowOnErrorOption, ValidateResult } from '@/types/index'\nimport { details as getOpenApiVersion } from '@/utils/details'\nimport { resolveReferences } from '@/utils/resolve-references'\nimport { transformErrors } from '@/utils/transform-errors'\nimport { validatePathParameters } from '@/utils/validate-path-parameters'\n\n/**\n * Configure available JSON Schema versions\n */\nconst jsonSchemaVersions = {\n 'http://json-schema.org/draft-04/schema#': Ajv04,\n 'http://json-schema.org/draft-07/schema#': Ajv,\n 'https://json-schema.org/draft/2020-12/schema': Ajv2020,\n}\n\nexport class Validator {\n public version: OpenApiVersion\n\n public static supportedVersions = OpenApiVersions\n\n // Object with function *or* object { errors: string }\n protected ajvValidators: Record<string, ValidateFunction> = {}\n\n protected errors: string\n\n protected specificationVersion: string\n\n protected specificationType: string\n\n public specification: AnyObject\n\n /**\n * Checks whether a specification is valid and all references can be resolved.\n */\n validate(filesystem: Filesystem, options?: ThrowOnErrorOption): ValidateResult {\n const entrypoint = filesystem.find((file) => file.isEntrypoint)\n const specification = entrypoint?.specification\n\n // TODO: How does this work with a filesystem?\n this.specification = specification\n\n // TODO: defaulting info.version to keep parser compatible with the previous one\n // we should bubble this error up and not throw on it\n if (this.specification?.info && !this.specification.info.version) {\n this.specification.info.version = '0.0.1'\n }\n\n try {\n // AnyObject is empty or invalid\n if (specification === undefined || specification === null) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EMPTY_OR_INVALID)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, ERRORS.EMPTY_OR_INVALID),\n }\n }\n\n // Meta data about the specification\n const { version, specificationType, specificationVersion } = getOpenApiVersion(specification)\n\n this.version = version\n this.specificationVersion = specificationVersion\n this.specificationType = specificationType\n\n // AnyObject is not supported\n if (!version) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.OPENAPI_VERSION_NOT_SUPPORTED)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, ERRORS.OPENAPI_VERSION_NOT_SUPPORTED),\n }\n }\n\n // Get the correct OpenAPI validator\n const validateSchema = this.getAjvValidator(version)\n const schemaResult = validateSchema(specification)\n\n // Error handling\n if (validateSchema.errors) {\n if (validateSchema.errors.length > 0) {\n if (options?.throwOnError) {\n throw new Error(validateSchema.errors[0].message)\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, validateSchema.errors),\n }\n }\n }\n\n // Check if the references are valid\n const resolvedReferences = resolveReferences(filesystem, options)\n const semanticErrors = validatePathParameters(resolvedReferences.schema)\n const errors = [...resolvedReferences.errors, ...semanticErrors]\n\n return {\n valid: schemaResult && resolvedReferences.valid && semanticErrors.length === 0,\n errors,\n schema: resolvedReferences.schema,\n }\n } catch (error) {\n // Something went horribly wrong!\n if (options?.throwOnError) {\n throw error\n }\n\n return {\n valid: false,\n errors: transformErrors(specification, error.message ?? error),\n }\n }\n }\n\n /**\n * Ajv JSON schema validator\n */\n getAjvValidator(version: OpenApiVersion): ValidateFunction {\n // Schema loaded already\n if (this.ajvValidators[version]) {\n return this.ajvValidators[version]\n }\n\n // Load OpenAPI Schema\n const schema = OpenApiSpecifications[version]\n\n // Load JSON Schema\n const AjvClass = jsonSchemaVersions[schema.$schema as keyof typeof jsonSchemaVersions]\n\n // Get the correct Ajv validator\n const ajv = new AjvClass({\n // Ajv is a bit too strict in its strict validation of OpenAPI schemas.\n // Switch strict mode off.\n strict: false,\n // Enable discriminator support for better oneOf error messages\n discriminator: true,\n // Show all errors, not just the first one\n allErrors: true,\n })\n\n // Register formats\n // https://ajv.js.org/packages/ajv-formats.html#formats\n addFormats(ajv)\n\n // OpenAPI 3.1 and 3.2 uses media-range format\n if (version === '3.1' || version === '3.2') {\n ajv.addFormat('media-range', true)\n }\n\n return (this.ajvValidators[version] = ajv.compile(schema))\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnyObject, ErrorObject } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates path-template semantics that are not enforced by the JSON schema.
|
|
4
|
+
*/
|
|
5
|
+
export declare function validatePathParameters(specification: AnyObject): ErrorObject[];
|
|
6
|
+
//# sourceMappingURL=validate-path-parameters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-path-parameters.d.ts","sourceRoot":"","sources":["../../src/utils/validate-path-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAU3D;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,SAAS,GAAG,WAAW,EAAE,CAoE9E"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const OPERATION_KEYS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
2
|
+
const PATH_PARAMETER_PATTERN = /{([^}]+)}/g;
|
|
3
|
+
function validatePathParameters(specification) {
|
|
4
|
+
const paths = specification?.paths;
|
|
5
|
+
if (!paths || typeof paths !== "object") {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const errors = [];
|
|
9
|
+
for (const [pathName, pathItem] of Object.entries(paths)) {
|
|
10
|
+
if (!isRecord(pathItem)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const operations = Object.entries(pathItem).filter(
|
|
14
|
+
([key, value]) => OPERATION_KEYS.has(key) && isRecord(value)
|
|
15
|
+
);
|
|
16
|
+
if (operations.length === 0) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const templateParameters = new Set(getTemplateParameterNames(pathName));
|
|
20
|
+
const pathLevelParameters = getPathParameters(pathItem.parameters, ["paths", pathName, "parameters"]);
|
|
21
|
+
for (const parameter of pathLevelParameters) {
|
|
22
|
+
if (!templateParameters.has(parameter.name)) {
|
|
23
|
+
errors.push({
|
|
24
|
+
path: parameter.path,
|
|
25
|
+
message: `Path parameter "${parameter.name}" must have the corresponding {${parameter.name}} segment in the "${pathName}" path`
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const [operationKey, operation] of operations) {
|
|
30
|
+
const operationParameters = getPathParameters(operation.parameters, [
|
|
31
|
+
"paths",
|
|
32
|
+
pathName,
|
|
33
|
+
operationKey,
|
|
34
|
+
"parameters"
|
|
35
|
+
]);
|
|
36
|
+
for (const parameter of operationParameters) {
|
|
37
|
+
if (!templateParameters.has(parameter.name)) {
|
|
38
|
+
errors.push({
|
|
39
|
+
path: parameter.path,
|
|
40
|
+
message: `Path parameter "${parameter.name}" must have the corresponding {${parameter.name}} segment in the "${pathName}" path`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const effectiveParameters = new Set(
|
|
45
|
+
[...pathLevelParameters, ...operationParameters].map((parameter) => parameter.name)
|
|
46
|
+
);
|
|
47
|
+
for (const templateParameter of templateParameters) {
|
|
48
|
+
if (!effectiveParameters.has(templateParameter)) {
|
|
49
|
+
errors.push({
|
|
50
|
+
path: ["paths", pathName, operationKey],
|
|
51
|
+
message: `Declared path parameter "${templateParameter}" needs to be defined as a path parameter at either the path or operation level`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return deduplicateErrors(errors);
|
|
58
|
+
}
|
|
59
|
+
function getTemplateParameterNames(pathName) {
|
|
60
|
+
return [...pathName.matchAll(PATH_PARAMETER_PATTERN)].map((match) => match[1]);
|
|
61
|
+
}
|
|
62
|
+
function getPathParameters(parameters, pathPrefix) {
|
|
63
|
+
if (!Array.isArray(parameters)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return parameters.flatMap((parameter, index) => {
|
|
67
|
+
if (!isRecord(parameter) || parameter.in !== "path" || typeof parameter.name !== "string") {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
name: parameter.name,
|
|
73
|
+
path: [...pathPrefix, String(index), "name"]
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function deduplicateErrors(errors) {
|
|
79
|
+
const seen = /* @__PURE__ */ new Set();
|
|
80
|
+
return errors.filter((error) => {
|
|
81
|
+
const key = `${error.message}||${error.path?.join(".") ?? ""}`;
|
|
82
|
+
if (seen.has(key)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
seen.add(key);
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function isRecord(value) {
|
|
90
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
validatePathParameters
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=validate-path-parameters.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/validate-path-parameters.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AnyObject, ErrorObject } from '@/types/index'\n\nconst OPERATION_KEYS = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'])\nconst PATH_PARAMETER_PATTERN = /{([^}]+)}/g\n\ntype PathParameter = {\n name: string\n path: string[]\n}\n\n/**\n * Validates path-template semantics that are not enforced by the JSON schema.\n */\nexport function validatePathParameters(specification: AnyObject): ErrorObject[] {\n const paths = specification?.paths\n\n if (!paths || typeof paths !== 'object') {\n return []\n }\n\n const errors: ErrorObject[] = []\n\n for (const [pathName, pathItem] of Object.entries(paths)) {\n if (!isRecord(pathItem)) {\n continue\n }\n\n const operations = Object.entries(pathItem).filter(\n ([key, value]) => OPERATION_KEYS.has(key) && isRecord(value),\n ) as Array<[string, AnyObject]>\n\n // Preserve the repo's current behaviour for empty path items.\n if (operations.length === 0) {\n continue\n }\n\n const templateParameters = new Set(getTemplateParameterNames(pathName))\n const pathLevelParameters = getPathParameters(pathItem.parameters, ['paths', pathName, 'parameters'])\n\n for (const parameter of pathLevelParameters) {\n if (!templateParameters.has(parameter.name)) {\n errors.push({\n path: parameter.path,\n message: `Path parameter \"${parameter.name}\" must have the corresponding {${parameter.name}} segment in the \"${pathName}\" path`,\n })\n }\n }\n\n for (const [operationKey, operation] of operations) {\n const operationParameters = getPathParameters(operation.parameters, [\n 'paths',\n pathName,\n operationKey,\n 'parameters',\n ])\n\n for (const parameter of operationParameters) {\n if (!templateParameters.has(parameter.name)) {\n errors.push({\n path: parameter.path,\n message: `Path parameter \"${parameter.name}\" must have the corresponding {${parameter.name}} segment in the \"${pathName}\" path`,\n })\n }\n }\n\n const effectiveParameters = new Set(\n [...pathLevelParameters, ...operationParameters].map((parameter) => parameter.name),\n )\n\n for (const templateParameter of templateParameters) {\n if (!effectiveParameters.has(templateParameter)) {\n errors.push({\n path: ['paths', pathName, operationKey],\n message: `Declared path parameter \"${templateParameter}\" needs to be defined as a path parameter at either the path or operation level`,\n })\n }\n }\n }\n }\n\n return deduplicateErrors(errors)\n}\n\nfunction getTemplateParameterNames(pathName: string) {\n return [...pathName.matchAll(PATH_PARAMETER_PATTERN)].map((match) => match[1])\n}\n\nfunction getPathParameters(parameters: unknown, pathPrefix: string[]): PathParameter[] {\n if (!Array.isArray(parameters)) {\n return []\n }\n\n return parameters.flatMap((parameter, index) => {\n if (!isRecord(parameter) || parameter.in !== 'path' || typeof parameter.name !== 'string') {\n return []\n }\n\n return [\n {\n name: parameter.name,\n path: [...pathPrefix, String(index), 'name'],\n },\n ]\n })\n}\n\nfunction deduplicateErrors(errors: ErrorObject[]) {\n const seen = new Set<string>()\n\n return errors.filter((error) => {\n const key = `${error.message}||${error.path?.join('.') ?? ''}`\n\n if (seen.has(key)) {\n return false\n }\n\n seen.add(key)\n\n return true\n })\n}\n\nfunction isRecord(value: unknown): value is AnyObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"parser",
|
|
18
18
|
"typescript"
|
|
19
19
|
],
|
|
20
|
-
"version": "0.25.
|
|
20
|
+
"version": "0.25.4",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22"
|
|
23
23
|
},
|
|
@@ -54,18 +54,18 @@
|
|
|
54
54
|
"jsonpointer": "^5.0.1",
|
|
55
55
|
"leven": "^4.0.0",
|
|
56
56
|
"yaml": "^2.8.0",
|
|
57
|
-
"@scalar/helpers": "0.4.1",
|
|
58
57
|
"@scalar/json-magic": "0.12.3",
|
|
59
|
-
"@scalar/
|
|
60
|
-
"@scalar/openapi-upgrader": "0.2.0"
|
|
58
|
+
"@scalar/helpers": "0.4.1",
|
|
59
|
+
"@scalar/openapi-upgrader": "0.2.0",
|
|
60
|
+
"@scalar/openapi-types": "0.6.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@apidevtools/swagger-parser": "10.1.0",
|
|
64
64
|
"@types/node": "^24.1.0",
|
|
65
65
|
"just-diff": "^6.0.2",
|
|
66
66
|
"vite": "^7.3.1",
|
|
67
|
-
"@scalar/
|
|
68
|
-
"@scalar/
|
|
67
|
+
"@scalar/build-tooling": "0.5.0",
|
|
68
|
+
"@scalar/types": "0.7.3"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "scalar-build-esbuild",
|