Upgrade @scalar/openapi-parser (#3442)

This commit is contained in:
Greg Bergé
2025-07-07 14:33:40 +02:00
committed by GitHub
parent afba8fcb34
commit 4f5cbfe732
8 changed files with 77 additions and 29 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/openapi-parser": patch
---
Upgrade Scalar parser
+2 -6
View File
@@ -169,7 +169,7 @@
"name": "@gitbook/openapi-parser", "name": "@gitbook/openapi-parser",
"version": "2.1.4", "version": "2.1.4",
"dependencies": { "dependencies": {
"@scalar/openapi-parser": "^0.10.10", "@scalar/openapi-parser": "^0.18.0",
"@scalar/openapi-types": "^0.1.9", "@scalar/openapi-types": "^0.1.9",
}, },
"devDependencies": { "devDependencies": {
@@ -1100,7 +1100,7 @@
"@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="], "@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="], "@scalar/openapi-parser": ["@scalar/openapi-parser@0.18.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-qAWEfJR5f/LwSFULFeSkD56LHgU9ovyj+e1uqy9XaW9Hb9P0ewIGKi+Gd4wTyxfFkdPLXTFYXApjviP6jV+new=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="], "@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
@@ -3768,8 +3768,6 @@
"@scalar/api-client/@scalar/object-utils": ["@scalar/object-utils@1.2.1", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1", "type-fest": "^4.20.0" } }, "sha512-2P/0In6XSrV1Ye8yU3LyOWJKWkzXpxf0thHmTfJriqGBZz41s06td2KKqoqHSEEi+FAsl3O1ngmOOzLM3QUkkw=="], "@scalar/api-client/@scalar/object-utils": ["@scalar/object-utils@1.2.1", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1", "type-fest": "^4.20.0" } }, "sha512-2P/0In6XSrV1Ye8yU3LyOWJKWkzXpxf0thHmTfJriqGBZz41s06td2KKqoqHSEEi+FAsl3O1ngmOOzLM3QUkkw=="],
"@scalar/api-client/@scalar/openapi-parser": ["@scalar/openapi-parser@0.18.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-qAWEfJR5f/LwSFULFeSkD56LHgU9ovyj+e1uqy9XaW9Hb9P0ewIGKi+Gd4wTyxfFkdPLXTFYXApjviP6jV+new=="],
"@scalar/api-client/@scalar/openapi-types": ["@scalar/openapi-types@0.3.3", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-mr3OvbCyvuzSDgv9gR9D3pJAsl82BRNBp1P8ldgRgKF4Wiswfa7nQeKibpX0kHSGXLgiyvxWJULUFwgdWMXNNA=="], "@scalar/api-client/@scalar/openapi-types": ["@scalar/openapi-types@0.3.3", "", { "dependencies": { "zod": "3.24.1" } }, "sha512-mr3OvbCyvuzSDgv9gR9D3pJAsl82BRNBp1P8ldgRgKF4Wiswfa7nQeKibpX0kHSGXLgiyvxWJULUFwgdWMXNNA=="],
"@scalar/api-client/@scalar/themes": ["@scalar/themes@0.13.4", "", { "dependencies": { "@scalar/types": "0.2.3", "nanoid": "^5.1.5" } }, "sha512-PMiSqX+MZF29u32ogEjTVA5c/LBd5cE/QPF7lK0EJeemXRy2r/nWbvrXtqD9Y7Sw54g1caVNjK8yJcCYJdB6hw=="], "@scalar/api-client/@scalar/themes": ["@scalar/themes@0.13.4", "", { "dependencies": { "@scalar/types": "0.2.3", "nanoid": "^5.1.5" } }, "sha512-PMiSqX+MZF29u32ogEjTVA5c/LBd5cE/QPF7lK0EJeemXRy2r/nWbvrXtqD9Y7Sw54g1caVNjK8yJcCYJdB6hw=="],
@@ -3782,8 +3780,6 @@
"@scalar/components/@scalar/themes": ["@scalar/themes@0.13.4", "", { "dependencies": { "@scalar/types": "0.2.3", "nanoid": "^5.1.5" } }, "sha512-PMiSqX+MZF29u32ogEjTVA5c/LBd5cE/QPF7lK0EJeemXRy2r/nWbvrXtqD9Y7Sw54g1caVNjK8yJcCYJdB6hw=="], "@scalar/components/@scalar/themes": ["@scalar/themes@0.13.4", "", { "dependencies": { "@scalar/types": "0.2.3", "nanoid": "^5.1.5" } }, "sha512-PMiSqX+MZF29u32ogEjTVA5c/LBd5cE/QPF7lK0EJeemXRy2r/nWbvrXtqD9Y7Sw54g1caVNjK8yJcCYJdB6hw=="],
"@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.18.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-qAWEfJR5f/LwSFULFeSkD56LHgU9ovyj+e1uqy9XaW9Hb9P0ewIGKi+Gd4wTyxfFkdPLXTFYXApjviP6jV+new=="],
"@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], "@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/oas-utils/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], "@scalar/oas-utils/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
+1 -1
View File
@@ -12,7 +12,7 @@
"version": "2.1.4", "version": "2.1.4",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@scalar/openapi-parser": "^0.10.10", "@scalar/openapi-parser": "^0.18.0",
"@scalar/openapi-types": "^0.1.9" "@scalar/openapi-types": "^0.1.9"
}, },
"devDependencies": { "devDependencies": {
@@ -39,11 +39,8 @@ describe('#createFileSystem', () => {
value: url, value: url,
rootURL: url, rootURL: url,
}); });
expect(filesystem).toHaveLength(4); expect(filesystem).toHaveLength(1);
expect(filesystem[0]?.isEntrypoint).toBe(true); expect(filesystem[0]?.isEntrypoint).toBe(true);
expect(filesystem[1]?.isEntrypoint).toBe(false); expect(filesystem[0]?.filename).toBe('openapi.json');
expect(filesystem[1]?.filename).toBe('user.yaml');
expect(filesystem[2]?.filename).toBe('../tag.yaml');
expect(filesystem[3]?.filename).toBe('http://localhost:3020/root/pet.yaml');
}); });
}); });
+21 -4
View File
@@ -1,5 +1,7 @@
import { load } from '@scalar/openapi-parser'; import { bundle } from '@scalar/openapi-parser';
import { parseJson, parseYaml } from '@scalar/openapi-parser/plugins-browser';
import type { ParseOpenAPIInput } from './parse'; import type { ParseOpenAPIInput } from './parse';
import { fetchURL } from './scalar-plugins/fetchURL';
import { fetchURLs } from './scalar-plugins/fetchURLs'; import { fetchURLs } from './scalar-plugins/fetchURLs';
import type { Filesystem } from './types'; import type { Filesystem } from './types';
@@ -12,9 +14,24 @@ export async function createFileSystem(
): Promise<Filesystem> { ): Promise<Filesystem> {
const { value, rootURL, options } = input; const { value, rootURL, options } = input;
const { filesystem } = await load(value, { const result = await bundle(value, {
plugins: [fetchURLs({ rootURL }), ...(options?.plugins || [])], treeShake: false,
plugins: [
fetchURL(),
parseYaml(),
parseJson(),
fetchURLs({ rootURL }),
...(options?.plugins || []),
],
}); });
return filesystem; return [
{
dir: '.',
isEntrypoint: true,
references: [],
filename: 'openapi.json',
specification: result,
},
];
} }
+5 -3
View File
@@ -1,13 +1,15 @@
import type { AnyApiDefinitionFormat, LoadPlugin } from '@scalar/openapi-parser'; import type { AnyObject, bundle } from '@scalar/openapi-parser';
import { OpenAPIParseError } from './error'; import { OpenAPIParseError } from './error';
import { convertOpenAPIV2ToOpenAPIV3 } from './v2'; import { convertOpenAPIV2ToOpenAPIV3 } from './v2';
import { parseOpenAPIV3 } from './v3'; import { parseOpenAPIV3 } from './v3';
export type ParsePlugin = Parameters<typeof bundle>[1]['plugins'][number];
export interface ParseOpenAPIInput { export interface ParseOpenAPIInput {
/** /**
* The API definition to parse. * The API definition to parse.
*/ */
value: AnyApiDefinitionFormat; value: string | AnyObject;
/** /**
* The root URL of the specified OpenAPI document. * The root URL of the specified OpenAPI document.
*/ */
@@ -20,7 +22,7 @@ export interface ParseOpenAPIInput {
* Options for the parser. * Options for the parser.
*/ */
options?: { options?: {
plugins?: LoadPlugin[]; plugins?: ParsePlugin[];
}; };
} }
@@ -0,0 +1,27 @@
import { normalize } from '@scalar/openapi-parser';
import type { ParsePlugin } from '../parse';
export const fetchUrlsDefaultConfiguration = {
limit: 40,
};
export const fetchURL = (): ParsePlugin => ({
validate(value) {
return URL.canParse(value);
},
async exec(value) {
try {
const response = await fetch(value);
if (!response.ok) {
return { ok: false };
}
const text = await response.text();
return {
ok: true,
data: normalize(text),
};
} catch {
return { ok: false };
}
},
});
@@ -1,10 +1,11 @@
import { type LoadPlugin, normalize } from '@scalar/openapi-parser'; import { normalize } from '@scalar/openapi-parser';
import type { ParsePlugin } from '../parse';
export const fetchUrlsDefaultConfiguration = { export const fetchUrlsDefaultConfiguration = {
limit: 40, limit: 40,
}; };
export const fetchURLs: (customConfiguration: { export const fetchURLs = (customConfiguration: {
/** /**
* Root URL to resolve relative URLs. * Root URL to resolve relative URLs.
*/ */
@@ -14,7 +15,7 @@ export const fetchURLs: (customConfiguration: {
* Limit the number of requests. Set to `false` to disable the limit. * Limit the number of requests. Set to `false` to disable the limit.
*/ */
limit?: number | false; limit?: number | false;
}) => LoadPlugin = (customConfiguration) => { }): ParsePlugin => {
// State // State
let numberOfRequests = 0; let numberOfRequests = 0;
@@ -25,7 +26,7 @@ export const fetchURLs: (customConfiguration: {
}; };
return { return {
check(value?: any) { validate(value) {
// Not a string // Not a string
if (typeof value !== 'string') { if (typeof value !== 'string') {
return false; return false;
@@ -42,10 +43,10 @@ export const fetchURLs: (customConfiguration: {
return true; return true;
}, },
async get(value?: any) { async exec(value) {
// Limit the number of requests // Limit the number of requests
if (configuration?.limit !== false && numberOfRequests >= configuration?.limit) { if (configuration?.limit !== false && numberOfRequests >= configuration?.limit) {
return undefined; return { ok: false };
} }
try { try {
@@ -53,14 +54,17 @@ export const fetchURLs: (customConfiguration: {
const url = getReferenceUrl({ value, rootURL: configuration.rootURL }); const url = getReferenceUrl({ value, rootURL: configuration.rootURL });
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
return undefined; return { ok: false };
} }
const text = await response.text(); const text = await response.text();
// Try to normalize the text to be sure it's a valid JSON or YAML. // Try to normalize the text to be sure it's a valid JSON or YAML.
await normalize(text); await normalize(text);
return text; return {
} catch (_error: any) { ok: true,
return undefined; data: text,
};
} catch {
return { ok: false };
} }
}, },
}; };