@scalar/openapi-parser 0.10.13 → 0.10.15
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 +13 -0
- package/README.md +22 -0
- package/dist/lib/Validator/Validator.js +1 -1
- package/dist/plugins/fetch-urls/fetchUrls.d.ts +1 -0
- package/dist/plugins/fetch-urls/fetchUrls.d.ts.map +1 -1
- package/dist/plugins/fetch-urls/fetchUrls.js +79 -7
- package/dist/plugins/fetch-urls/index.d.ts +1 -1
- package/dist/plugins/fetch-urls/index.d.ts.map +1 -1
- package/dist/plugins/fetch-urls/index.js +1 -1
- package/dist/plugins/read-files/index.d.ts +1 -1
- package/dist/plugins/read-files/index.d.ts.map +1 -1
- package/dist/plugins/read-files/readFiles.d.ts.map +1 -1
- package/dist/plugins/read-files/readFiles.js +18 -3
- package/dist/types/index.d.ts +23 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/dereference.js +2 -2
- package/dist/utils/filter.js +1 -1
- package/dist/utils/isFilesystem.d.ts +2 -1
- package/dist/utils/isFilesystem.d.ts.map +1 -1
- package/dist/utils/load/load.d.ts +11 -2
- package/dist/utils/load/load.d.ts.map +1 -1
- package/dist/utils/load/load.js +77 -38
- package/dist/utils/makeFilesystem.d.ts +18 -1
- package/dist/utils/makeFilesystem.d.ts.map +1 -1
- package/dist/utils/makeFilesystem.js +55 -9
- package/dist/utils/openapi/actions/details.js +1 -1
- package/dist/utils/openapi/actions/toJson.js +1 -1
- package/dist/utils/openapi/actions/toYaml.js +1 -1
- package/dist/utils/openapi/commands/loadCommand.d.ts +180 -180
- package/dist/utils/resolveReferences.d.ts +6 -0
- package/dist/utils/resolveReferences.d.ts.map +1 -1
- package/dist/utils/resolveReferences.js +19 -9
- package/dist/utils/upgrade.js +1 -1
- package/dist/utils/upgradeFromTwoToThree.d.ts.map +1 -1
- package/dist/utils/upgradeFromTwoToThree.js +58 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @scalar/openapi-parser
|
|
2
2
|
|
|
3
|
+
## 0.10.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4abe4f8: feat: add resolveInternalRefs to bundle documents
|
|
8
|
+
- 4abe4f8: feat: external $ref’s (absolute and relative URLs)
|
|
9
|
+
|
|
10
|
+
## 0.10.14
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- cf5bf65: fix: migrate file type, basic auth, parameters and responses headers correctly
|
|
15
|
+
|
|
3
16
|
## 0.10.13
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -239,11 +239,33 @@ import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
|
|
|
239
239
|
const { filesystem } = await load(
|
|
240
240
|
'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml',
|
|
241
241
|
{
|
|
242
|
+
// Optional: If you directly pass an OpenAPI (not an URL), let it know where the content came from
|
|
243
|
+
// That’s the only way we can resolve external references, if they are relative.
|
|
244
|
+
source: 'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml'
|
|
242
245
|
plugins: [fetchUrls()],
|
|
243
246
|
},
|
|
244
247
|
)
|
|
245
248
|
```
|
|
246
249
|
|
|
250
|
+
#### Bundle external references
|
|
251
|
+
|
|
252
|
+
By default, `dereference` tries to resolve both internal and external references. You can disable resolving internal references by setting `resolveInternalRefs` to `false`:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { dereference, load } from '@scalar/openapi-parser'
|
|
256
|
+
import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
|
|
257
|
+
|
|
258
|
+
// Load a file and all referenced files
|
|
259
|
+
const { filesystem } = await load('https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml', {
|
|
260
|
+
plugins: [fetchUrls()],
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const result = await dereference(filesystem, {
|
|
264
|
+
// Skips internal references like `$ref: '#/components/schemas/foobar'`
|
|
265
|
+
resolveInternalRefs: false,
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
247
269
|
#### Intercept HTTP requests
|
|
248
270
|
|
|
249
271
|
If you’re using the package in a browser environment, you may run into CORS issues when fetching from URLs. You can intercept the requests, for example to use a proxy, though:
|
|
@@ -27,7 +27,7 @@ class Validator {
|
|
|
27
27
|
*/
|
|
28
28
|
async validate(filesystem, options) {
|
|
29
29
|
const entrypoint = filesystem.find((file) => file.isEntrypoint);
|
|
30
|
-
const specification = entrypoint?.
|
|
30
|
+
const specification = entrypoint?.content;
|
|
31
31
|
// TODO: How does this work with a filesystem?
|
|
32
32
|
this.specification = specification;
|
|
33
33
|
// TODO: defaulting info.version to keep parser compatible with the previous one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchUrls.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/fetchUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,6BAA6B;;CAEzC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC7C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,KAAK,
|
|
1
|
+
{"version":3,"file":"fetchUrls.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/fetchUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,6BAA6B;;CAEzC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC7C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,KAAK,UAqDL,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,UAyE7D"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fetchUrlsDefaultConfiguration = {
|
|
2
|
-
limit:
|
|
2
|
+
limit: 100,
|
|
3
3
|
};
|
|
4
4
|
const fetchUrls = (customConfiguration) => {
|
|
5
5
|
// State
|
|
@@ -10,18 +10,24 @@ const fetchUrls = (customConfiguration) => {
|
|
|
10
10
|
...customConfiguration,
|
|
11
11
|
};
|
|
12
12
|
return {
|
|
13
|
+
// Make it run after readFiles
|
|
14
|
+
priority: 0,
|
|
13
15
|
check(value) {
|
|
14
16
|
// Not a string
|
|
15
17
|
if (typeof value !== 'string') {
|
|
16
18
|
return false;
|
|
17
19
|
}
|
|
18
|
-
//
|
|
19
|
-
if (
|
|
20
|
+
// Seems to be JSON
|
|
21
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// Seems to be YAML
|
|
25
|
+
if (value.includes('\n')) {
|
|
20
26
|
return false;
|
|
21
27
|
}
|
|
22
28
|
return true;
|
|
23
29
|
},
|
|
24
|
-
async get(
|
|
30
|
+
async get(url) {
|
|
25
31
|
// Limit ht enumber of requests
|
|
26
32
|
if (configuration?.limit !== false && numberOfRequests >= configuration?.limit) {
|
|
27
33
|
console.warn(`[fetchUrls] Maximum number of requests reeached (${configuration?.limit}), skipping request`);
|
|
@@ -29,15 +35,81 @@ const fetchUrls = (customConfiguration) => {
|
|
|
29
35
|
}
|
|
30
36
|
try {
|
|
31
37
|
numberOfRequests++;
|
|
32
|
-
const response = await (configuration?.fetch ? configuration.fetch(
|
|
38
|
+
const response = await (configuration?.fetch ? configuration.fetch(url) : fetch(url));
|
|
33
39
|
return await response.text();
|
|
34
40
|
}
|
|
35
41
|
catch (error) {
|
|
36
|
-
console.error('[fetchUrls]', error.message, `(${
|
|
42
|
+
console.error('[fetchUrls]', error.message, `(${url})`);
|
|
37
43
|
return undefined;
|
|
38
44
|
}
|
|
39
45
|
},
|
|
46
|
+
getUri(value, source) {
|
|
47
|
+
return getAbsoluteUrl(value, source);
|
|
48
|
+
},
|
|
40
49
|
};
|
|
41
50
|
};
|
|
51
|
+
function getAbsoluteUrl(value, source) {
|
|
52
|
+
if (typeof source !== 'string') {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
// Already an absolute URL
|
|
56
|
+
if (value.startsWith('http')) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
// Make it an absolute URL
|
|
60
|
+
if (source) {
|
|
61
|
+
// Skip if source or value is JSON/YAML content
|
|
62
|
+
if (source.startsWith('{') ||
|
|
63
|
+
source.startsWith('[') ||
|
|
64
|
+
source.includes('\n') ||
|
|
65
|
+
value.startsWith('{') ||
|
|
66
|
+
value.startsWith('[') ||
|
|
67
|
+
value.includes('\n')) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
// If source is an HTTP URL, handle both absolute and relative paths
|
|
72
|
+
if (source.startsWith('http')) {
|
|
73
|
+
const sourceUrl = new URL(source);
|
|
74
|
+
// If value starts with /, treat it as relative to the host root
|
|
75
|
+
if (value.startsWith('/')) {
|
|
76
|
+
// Simply combine the origin with the absolute path
|
|
77
|
+
return `${sourceUrl.origin}${value}`;
|
|
78
|
+
}
|
|
79
|
+
// Otherwise treat it as relative to the source URL's directory
|
|
80
|
+
return new URL(value, sourceUrl.href).toString();
|
|
81
|
+
}
|
|
82
|
+
// Handle file system paths
|
|
83
|
+
if (source.startsWith('/') || !source.startsWith('http')) {
|
|
84
|
+
const sourceParts = source.split('/').filter(Boolean).slice(0, -1);
|
|
85
|
+
const valueParts = value.split('/').filter(Boolean);
|
|
86
|
+
const isAbsolute = source.startsWith('/');
|
|
87
|
+
// If value is absolute path, start fresh
|
|
88
|
+
if (value.startsWith('/')) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
// Process each part of the path
|
|
92
|
+
const resultParts = [...sourceParts];
|
|
93
|
+
for (const part of valueParts) {
|
|
94
|
+
if (part === '..') {
|
|
95
|
+
resultParts.pop();
|
|
96
|
+
}
|
|
97
|
+
else if (part !== '.') {
|
|
98
|
+
resultParts.push(part);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Preserve the leading slash for absolute paths, but avoid double slashes
|
|
102
|
+
return isAbsolute ? '/' + resultParts.join('/') : resultParts.join('/');
|
|
103
|
+
}
|
|
104
|
+
return new URL(value, source).toString();
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn('[fetchUrls] Failed to resolve URL:', error.message, `(${value})`);
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Otherwise, return the value as is
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
42
114
|
|
|
43
|
-
export { fetchUrls, fetchUrlsDefaultConfiguration };
|
|
115
|
+
export { fetchUrls, fetchUrlsDefaultConfiguration, getAbsoluteUrl };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { fetchUrlsDefaultConfiguration, fetchUrls, getAbsoluteUrl, } from './fetchUrls.ts';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,SAAS,EACT,cAAc,GACf,MAAM,gBAAgB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { fetchUrls, fetchUrlsDefaultConfiguration } from './fetchUrls.js';
|
|
1
|
+
export { fetchUrls, fetchUrlsDefaultConfiguration, getAbsoluteUrl } from './fetchUrls.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { readFiles } from './readFiles.ts';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readFiles.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/readFiles.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"readFiles.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/readFiles.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,SAAS,EAAE,MAAM,UAyE7B,CAAA"}
|
|
@@ -6,6 +6,8 @@ import { isYaml } from '../../utils/isYaml.js';
|
|
|
6
6
|
|
|
7
7
|
const readFiles = () => {
|
|
8
8
|
return {
|
|
9
|
+
// Make it run before fetchUrls
|
|
10
|
+
priority: 100,
|
|
9
11
|
check(value) {
|
|
10
12
|
// Not a string
|
|
11
13
|
if (typeof value !== 'string') {
|
|
@@ -30,6 +32,9 @@ const readFiles = () => {
|
|
|
30
32
|
return true;
|
|
31
33
|
},
|
|
32
34
|
async get(value) {
|
|
35
|
+
if (typeof value !== 'string') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
33
38
|
if (!fs.existsSync(value)) {
|
|
34
39
|
throw new Error(ERRORS.FILE_DOES_NOT_EXIST.replace('%s', value));
|
|
35
40
|
}
|
|
@@ -41,9 +46,19 @@ const readFiles = () => {
|
|
|
41
46
|
return false;
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
getUri(value, source) {
|
|
50
|
+
if (typeof value !== 'string') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (typeof source !== 'string') {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
// Already absolute
|
|
57
|
+
if (value.startsWith('/')) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
const dir = dirname(source);
|
|
61
|
+
return join(dir, value);
|
|
47
62
|
},
|
|
48
63
|
getDir(value) {
|
|
49
64
|
return dirname(value);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -55,11 +55,30 @@ export type Filesystem = FilesystemEntry[];
|
|
|
55
55
|
* Holds all information about a single file (doesn’t have to be a literal file, see Filesystem).
|
|
56
56
|
*/
|
|
57
57
|
export type FilesystemEntry = {
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The absolute path or URL to the file.
|
|
60
|
+
*
|
|
61
|
+
* @example https://example.com/path/to/file.yaml
|
|
62
|
+
* @example /Users/username/project/path/to/file.yaml
|
|
63
|
+
*/
|
|
64
|
+
uri: string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Whether this is the base document, everything else is just referenced by it.
|
|
67
|
+
*/
|
|
59
68
|
isEntrypoint: boolean;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
/**
|
|
70
|
+
* A map of the references to other files.
|
|
71
|
+
*
|
|
72
|
+
* @example {
|
|
73
|
+
* 'file.yaml': 'https://example.com/folder/file.yaml',
|
|
74
|
+
* '../file2.yaml': 'https://example.com/file2.yaml',
|
|
75
|
+
* }
|
|
76
|
+
*/
|
|
77
|
+
references: Record<string, string>;
|
|
78
|
+
/**
|
|
79
|
+
* The actual content of the document
|
|
80
|
+
*/
|
|
81
|
+
content: AnyObject;
|
|
63
82
|
};
|
|
64
83
|
/**
|
|
65
84
|
* Options, that can apply to all commands
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAEpD,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAGvE;;GAEG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAE9C,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAE3C;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAAA;AAEvD,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,aAAa,EAAE,SAAS,CAAA;IACxB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,OAAO,CAAA;IACd,aAAa,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAA;IAChC,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI;IACzE,aAAa,EAAE,CAAC,CAAA;IAChB,OAAO,EAAE,KAAK,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,aAAa,EAAE,SAAS,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAA;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAA;IACzB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,OAAO,MAAM,GAAG,MAAM,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,EAAE,CAAA;AAE1C;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,10 +11,10 @@ async function dereference(value, options) {
|
|
|
11
11
|
const entrypoint = getEntrypoint(filesystem);
|
|
12
12
|
const result = resolveReferences(filesystem, options);
|
|
13
13
|
return {
|
|
14
|
-
specification: entrypoint.
|
|
14
|
+
specification: entrypoint.content,
|
|
15
15
|
errors: result.errors,
|
|
16
16
|
schema: result.schema,
|
|
17
|
-
...details(entrypoint.
|
|
17
|
+
...details(entrypoint.content),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
package/dist/utils/filter.js
CHANGED
|
@@ -8,7 +8,7 @@ import { traverse } from './traverse.js';
|
|
|
8
8
|
function filter(specification, callback) {
|
|
9
9
|
const filesystem = makeFilesystem(specification);
|
|
10
10
|
return {
|
|
11
|
-
specification: traverse(getEntrypoint(filesystem).
|
|
11
|
+
specification: traverse(getEntrypoint(filesystem).content, (schema) => {
|
|
12
12
|
return callback(schema) ? schema : undefined;
|
|
13
13
|
}),
|
|
14
14
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { Filesystem } from '../types/index.ts';
|
|
1
2
|
/**
|
|
2
3
|
* Check if the value is a filesystem
|
|
3
4
|
*/
|
|
4
|
-
export declare function isFilesystem(value: any):
|
|
5
|
+
export declare function isFilesystem(value: any): value is Filesystem;
|
|
5
6
|
//# sourceMappingURL=isFilesystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/isFilesystem.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"isFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/isFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,UAAU,CAO5D"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { AnyApiDefinitionFormat, Filesystem, LoadResult, ThrowOnErrorOption } from '../../types/index.ts';
|
|
2
2
|
export type LoadPlugin = {
|
|
3
|
+
priority?: number;
|
|
3
4
|
check: (value?: any) => boolean;
|
|
4
|
-
get: (value: any) => any;
|
|
5
|
+
get: (value: any, source?: string) => any;
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use `getUri` instead
|
|
8
|
+
*/
|
|
5
9
|
resolvePath?: (value: any, reference: string) => string;
|
|
10
|
+
getUri?: (value: any, source?: string) => string;
|
|
6
11
|
getDir?: (value: any) => string;
|
|
7
12
|
getFilename?: (value: any) => string;
|
|
8
13
|
};
|
|
9
14
|
export type LoadOptions = {
|
|
10
15
|
plugins?: LoadPlugin[];
|
|
11
|
-
|
|
16
|
+
source?: string | undefined;
|
|
12
17
|
filesystem?: Filesystem;
|
|
13
18
|
} & ThrowOnErrorOption;
|
|
14
19
|
/**
|
|
@@ -19,6 +24,10 @@ export type LoadOptions = {
|
|
|
19
24
|
*
|
|
20
25
|
* It builds a filesystem representation of all loaded content and collects any errors
|
|
21
26
|
* encountered during the process.
|
|
27
|
+
*
|
|
28
|
+
* Known limitation:
|
|
29
|
+
* - TODO: Internal references inside external reference might not work
|
|
30
|
+
* - TODO: Protocol-relative URLs don’t work yet
|
|
22
31
|
*/
|
|
23
32
|
export declare function load(value: AnyApiDefinitionFormat, options?: LoadOptions): Promise<LoadResult>;
|
|
24
33
|
//# sourceMappingURL=load.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/utils/load/load.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAGtB,UAAU,EACV,UAAU,EACV,kBAAkB,EACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/utils/load/load.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAGtB,UAAU,EACV,UAAU,EACV,kBAAkB,EACnB,MAAM,kBAAkB,CAAA;AAMzB,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAA;IAC/B,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,GAAG,CAAA;IACzC;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IACvD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;IAC/B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB,GAAG,kBAAkB,CAAA;AAEtB;;;;;;;;;;;;GAYG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,sBAAsB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAgKpG"}
|
package/dist/utils/load/load.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ERRORS } from '../../configuration/index.js';
|
|
2
2
|
import { getEntrypoint } from '../getEntrypoint.js';
|
|
3
3
|
import { getListOfReferences } from '../getListOfReferences.js';
|
|
4
|
-
import {
|
|
4
|
+
import { makeFilesystemEntry } from '../makeFilesystem.js';
|
|
5
5
|
import { normalize } from '../normalize.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -12,25 +12,39 @@ import { normalize } from '../normalize.js';
|
|
|
12
12
|
*
|
|
13
13
|
* It builds a filesystem representation of all loaded content and collects any errors
|
|
14
14
|
* encountered during the process.
|
|
15
|
+
*
|
|
16
|
+
* Known limitation:
|
|
17
|
+
* - TODO: Internal references inside external reference might not work
|
|
18
|
+
* - TODO: Protocol-relative URLs don’t work yet
|
|
15
19
|
*/
|
|
16
20
|
async function load(value, options) {
|
|
17
21
|
const errors = [];
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const filesystem = [...(options?.filesystem ?? [])];
|
|
23
|
+
// Don't load a reference twice, check the filesystem before fetching something
|
|
24
|
+
if (filesystem.find((entry) => entry.uri === options?.source)) {
|
|
20
25
|
return {
|
|
21
|
-
specification: getEntrypoint(
|
|
22
|
-
filesystem
|
|
26
|
+
specification: getEntrypoint(filesystem)?.content,
|
|
27
|
+
filesystem,
|
|
23
28
|
errors,
|
|
24
29
|
};
|
|
25
30
|
}
|
|
26
|
-
// Check whether the value is an URL or file path
|
|
27
|
-
const plugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(value));
|
|
28
31
|
let content;
|
|
32
|
+
let uri;
|
|
33
|
+
// Check whether the value is an file or URL
|
|
34
|
+
const plugin = sortPlugins(options?.plugins)?.find((thisPlugin) => thisPlugin.check(value));
|
|
35
|
+
if (options?.source) {
|
|
36
|
+
uri = options.source;
|
|
37
|
+
}
|
|
38
|
+
// Seems to be a file or URL
|
|
29
39
|
if (plugin) {
|
|
30
40
|
try {
|
|
31
|
-
|
|
41
|
+
// Get the absolute URI/path
|
|
42
|
+
uri = plugin.getUri(value, options?.source);
|
|
43
|
+
// Fetch/read the content
|
|
44
|
+
content = normalize(await plugin.get(uri));
|
|
32
45
|
}
|
|
33
46
|
catch (_error) {
|
|
47
|
+
// Couldn’t fetch/read the content
|
|
34
48
|
if (options?.throwOnError) {
|
|
35
49
|
throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value));
|
|
36
50
|
}
|
|
@@ -40,15 +54,16 @@ async function load(value, options) {
|
|
|
40
54
|
});
|
|
41
55
|
return {
|
|
42
56
|
specification: null,
|
|
43
|
-
filesystem
|
|
57
|
+
filesystem,
|
|
44
58
|
errors,
|
|
45
59
|
};
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
else {
|
|
63
|
+
// No plugin found, so it’s just the content, I guess.
|
|
49
64
|
content = normalize(value);
|
|
50
65
|
}
|
|
51
|
-
//
|
|
66
|
+
// Oops, no content
|
|
52
67
|
if (content === undefined) {
|
|
53
68
|
if (options?.throwOnError) {
|
|
54
69
|
throw new Error('No content to load');
|
|
@@ -59,61 +74,85 @@ async function load(value, options) {
|
|
|
59
74
|
});
|
|
60
75
|
return {
|
|
61
76
|
specification: null,
|
|
62
|
-
filesystem
|
|
77
|
+
filesystem,
|
|
63
78
|
errors,
|
|
64
79
|
};
|
|
65
80
|
}
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
const entryExistsAlready = options?.filesystem?.find((entry) => uri && entry.uri === uri);
|
|
82
|
+
if (entryExistsAlready) {
|
|
83
|
+
return {
|
|
84
|
+
specification: getEntrypoint(filesystem)?.content,
|
|
85
|
+
filesystem,
|
|
86
|
+
errors,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Get all external references from the content
|
|
90
|
+
const listOfReferences = getListOfReferences(content);
|
|
91
|
+
const source =
|
|
92
|
+
// A given URL/path
|
|
93
|
+
options?.source ??
|
|
94
|
+
// The reference that we come from
|
|
95
|
+
(typeof value === 'string'
|
|
96
|
+
? value
|
|
97
|
+
: // Nothing
|
|
98
|
+
undefined);
|
|
99
|
+
// Map the references to their absolute URIs
|
|
100
|
+
const mapOfReferences = listOfReferences.reduce((acc, reference) => {
|
|
101
|
+
const plugin = sortPlugins(options?.plugins)?.find((p) => p.check(reference));
|
|
102
|
+
const absoluteUri = plugin?.getUri(reference, source);
|
|
103
|
+
return { ...acc, [reference]: absoluteUri };
|
|
104
|
+
}, {});
|
|
105
|
+
const entry = makeFilesystemEntry(content, {
|
|
106
|
+
isEntrypoint: false,
|
|
107
|
+
uri,
|
|
108
|
+
references: mapOfReferences,
|
|
68
109
|
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
110
|
+
if (!filesystem.find((entry) => entry.uri === uri)) {
|
|
111
|
+
filesystem.push({
|
|
112
|
+
...entry,
|
|
113
|
+
isEntrypoint: filesystem.length === 0,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
74
116
|
// No other references
|
|
75
117
|
if (listOfReferences.length === 0) {
|
|
76
118
|
return {
|
|
77
|
-
specification:
|
|
119
|
+
specification: entry.content,
|
|
78
120
|
filesystem,
|
|
79
121
|
errors,
|
|
80
122
|
};
|
|
81
123
|
}
|
|
82
124
|
// Load other external references
|
|
83
125
|
for (const reference of listOfReferences) {
|
|
126
|
+
// Work with the absolute URI
|
|
127
|
+
const absoluteUri = mapOfReferences[reference];
|
|
84
128
|
// Find a matching plugin
|
|
85
|
-
const otherPlugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(
|
|
86
|
-
// Skip if no plugin is found (internal references don
|
|
129
|
+
const otherPlugin = sortPlugins(options?.plugins)?.find((thisPlugin) => thisPlugin.check(absoluteUri));
|
|
130
|
+
// Skip if no plugin is found (internal references don't need a plugin for example)
|
|
87
131
|
if (!otherPlugin) {
|
|
88
132
|
continue;
|
|
89
133
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (filesystem.find((entry) => entry.filename === reference)) {
|
|
134
|
+
// Don't load a reference twice, check the filesystem before fetching something
|
|
135
|
+
if (options?.filesystem?.find((entry) => entry.uri === absoluteUri)) {
|
|
93
136
|
continue;
|
|
94
137
|
}
|
|
95
|
-
const { filesystem: referencedFiles, errors: newErrors } = await load(
|
|
138
|
+
const { filesystem: referencedFiles, errors: newErrors } = await load(absoluteUri, {
|
|
96
139
|
...options,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
filename: reference,
|
|
140
|
+
source: absoluteUri,
|
|
141
|
+
filesystem,
|
|
100
142
|
});
|
|
101
143
|
errors.push(...newErrors);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
...file,
|
|
107
|
-
isEntrypoint: false,
|
|
108
|
-
};
|
|
109
|
-
}),
|
|
110
|
-
];
|
|
144
|
+
referencedFiles
|
|
145
|
+
.filter((file) => !filesystem.find((entry) => entry.uri === file.uri))
|
|
146
|
+
.forEach((file) => filesystem.push(file));
|
|
111
147
|
}
|
|
112
148
|
return {
|
|
113
|
-
specification:
|
|
149
|
+
specification: entry.content,
|
|
114
150
|
filesystem,
|
|
115
151
|
errors,
|
|
116
152
|
};
|
|
117
153
|
}
|
|
154
|
+
function sortPlugins(plugins = []) {
|
|
155
|
+
return plugins?.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
156
|
+
}
|
|
118
157
|
|
|
119
158
|
export { load };
|
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
import type { AnyObject, Filesystem, FilesystemEntry } from '../types/index.ts';
|
|
2
|
-
export declare function makeFilesystem(
|
|
2
|
+
export declare function makeFilesystem(
|
|
3
|
+
/**
|
|
4
|
+
* Pass whatever you have, it will be normalized and then used as the content.
|
|
5
|
+
*/
|
|
6
|
+
value: string | AnyObject | Filesystem | FilesystemEntry,
|
|
7
|
+
/**
|
|
8
|
+
* If you know better, you can overwrite the default values.
|
|
9
|
+
*/
|
|
10
|
+
overwrites?: Partial<FilesystemEntry> & (FilesystemEntry['references'] | {})): Filesystem;
|
|
11
|
+
export declare function makeFilesystemEntry(
|
|
12
|
+
/**
|
|
13
|
+
* Pass whatever you have, it will be normalized and then used as the content.
|
|
14
|
+
*/
|
|
15
|
+
value: string | AnyObject | Filesystem,
|
|
16
|
+
/**
|
|
17
|
+
* If you know better, you can overwrite the default values.
|
|
18
|
+
*/
|
|
19
|
+
overwrites?: Partial<FilesystemEntry> & (FilesystemEntry['references'] | {})): FilesystemEntry;
|
|
3
20
|
//# sourceMappingURL=makeFilesystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/makeFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"makeFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/makeFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAI/E,wBAAgB,cAAc;AAC5B;;GAEG;AACH,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,eAAe;AACxD;;GAEG;AACH,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,GAC3E,UAAU,CA4BZ;AAED,wBAAgB,mBAAmB;AACjC;;GAEG;AACH,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU;AACtC;;GAEG;AACH,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,GAC3E,eAAe,CAmBjB"}
|