@mintlify/common 1.0.1202 → 1.0.1203
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/dist/openapi/additionalOperations.d.ts +5 -0
- package/dist/openapi/additionalOperations.js +63 -0
- package/dist/openapi/filenameUtils.d.ts +1 -1
- package/dist/openapi/filenameUtils.js +8 -2
- package/dist/openapi/findNavGroup.d.ts +3 -3
- package/dist/openapi/findNavGroup.js +9 -3
- package/dist/openapi/generateOpenApiPages.d.ts +4 -2
- package/dist/openapi/generateOpenApiPages.js +30 -53
- package/dist/openapi/getOpenApiOperationMethodAndEndpoint.js +20 -15
- package/dist/openapi/getTagDisplayName.d.ts +1 -1
- package/dist/openapi/index.d.ts +1 -0
- package/dist/openapi/index.js +1 -0
- package/dist/openapi/parseApiString.js +4 -6
- package/dist/openapi/parseOpenApiString.d.ts +4 -3
- package/dist/openapi/parseOpenApiString.js +24 -26
- package/dist/openapi/querystringParameters.d.ts +5 -0
- package/dist/openapi/querystringParameters.js +81 -0
- package/dist/openapi/tagHierarchy.d.ts +15 -0
- package/dist/openapi/tagHierarchy.js +101 -0
- package/dist/openapi/validate.js +24 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/openapi.d.ts +3 -2
- package/package.json +3 -3
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type OpenAPIV3_2 } from '@mintlify/validation';
|
|
2
|
+
import type { OpenApiValidateError } from './validate.js';
|
|
3
|
+
type PathItemSources = Partial<Pick<OpenAPIV3_2.Document, 'paths' | 'webhooks' | 'components'>>;
|
|
4
|
+
export declare const validateAdditionalOperations: (document: PathItemSources) => OpenApiValidateError[];
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getPathItemOperations, isAdditionalHttpMethod, isFixedHttpMethod, } from '@mintlify/validation';
|
|
2
|
+
const escapePointer = (segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
3
|
+
const callbackPathItemsOf = (location, pathItem) => {
|
|
4
|
+
if (!pathItem || '$ref' in pathItem)
|
|
5
|
+
return [];
|
|
6
|
+
return getPathItemOperations(pathItem).flatMap(([method, operation]) => {
|
|
7
|
+
const callbacks = operation.callbacks;
|
|
8
|
+
if (!callbacks)
|
|
9
|
+
return [];
|
|
10
|
+
const operationPointer = isFixedHttpMethod(method)
|
|
11
|
+
? escapePointer(method)
|
|
12
|
+
: `additionalOperations/${escapePointer(method)}`;
|
|
13
|
+
return Object.entries(callbacks).flatMap(([name, callback]) => {
|
|
14
|
+
if (!callback || '$ref' in callback)
|
|
15
|
+
return [];
|
|
16
|
+
return Object.entries(callback).flatMap(([expression, item]) => {
|
|
17
|
+
const itemLocation = `${location}/${operationPointer}/callbacks/${escapePointer(name)}/${escapePointer(expression)}`;
|
|
18
|
+
const entry = [itemLocation, item];
|
|
19
|
+
return [entry, ...callbackPathItemsOf(itemLocation, item)];
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const pathItemsOf = (document) => {
|
|
25
|
+
var _a, _b, _c, _d, _e, _f;
|
|
26
|
+
const roots = [
|
|
27
|
+
...Object.entries((_a = document.paths) !== null && _a !== void 0 ? _a : {}).map(([path, item]) => [`/paths/${escapePointer(path)}`, item]),
|
|
28
|
+
...Object.entries((_b = document.webhooks) !== null && _b !== void 0 ? _b : {}).map(([name, item]) => [
|
|
29
|
+
`/webhooks/${escapePointer(name)}`,
|
|
30
|
+
item,
|
|
31
|
+
]),
|
|
32
|
+
...Object.entries((_d = (_c = document.components) === null || _c === void 0 ? void 0 : _c.pathItems) !== null && _d !== void 0 ? _d : {}).map(([name, item]) => [
|
|
33
|
+
`/components/pathItems/${escapePointer(name)}`,
|
|
34
|
+
item,
|
|
35
|
+
]),
|
|
36
|
+
...Object.entries((_f = (_e = document.components) === null || _e === void 0 ? void 0 : _e.callbacks) !== null && _f !== void 0 ? _f : {}).flatMap(([name, callback]) => {
|
|
37
|
+
if (!callback || '$ref' in callback)
|
|
38
|
+
return [];
|
|
39
|
+
return Object.entries(callback).map(([expression, item]) => [
|
|
40
|
+
`/components/callbacks/${escapePointer(name)}/${escapePointer(expression)}`,
|
|
41
|
+
item,
|
|
42
|
+
]);
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
return roots.flatMap((root) => [root, ...callbackPathItemsOf(root[0], root[1])]);
|
|
46
|
+
};
|
|
47
|
+
export const validateAdditionalOperations = (document) => {
|
|
48
|
+
const errors = [];
|
|
49
|
+
for (const [location, pathItem] of pathItemsOf(document)) {
|
|
50
|
+
if (!pathItem || !('additionalOperations' in pathItem) || !pathItem.additionalOperations) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
for (const method of Object.keys(pathItem.additionalOperations)) {
|
|
54
|
+
if (!isAdditionalHttpMethod(method)) {
|
|
55
|
+
errors.push({
|
|
56
|
+
path: `${location}/additionalOperations/${method}`,
|
|
57
|
+
message: 'additionalOperations methods must be uppercase, must not duplicate a fixed method, and cannot be WEBHOOK',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return errors;
|
|
63
|
+
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { NavigationEntry } from '@mintlify/models';
|
|
2
2
|
export declare const prepareStringToBeValidFilename: (str?: string) => string | undefined;
|
|
3
|
-
export declare const generateUniqueFilenameWithoutExtension: (pages: NavigationEntry[], base: string) => string;
|
|
3
|
+
export declare const generateUniqueFilenameWithoutExtension: (pages: NavigationEntry[] | Readonly<Record<string, unknown>>, base: string) => string;
|
|
@@ -9,11 +9,17 @@ export const prepareStringToBeValidFilename = (str) => str
|
|
|
9
9
|
.toLowerCase()
|
|
10
10
|
: undefined;
|
|
11
11
|
export const generateUniqueFilenameWithoutExtension = (pages, base) => {
|
|
12
|
+
const hasFilename = (filename) => {
|
|
13
|
+
const candidate = filename.toLowerCase();
|
|
14
|
+
return Array.isArray(pages)
|
|
15
|
+
? pages.includes(candidate)
|
|
16
|
+
: Object.prototype.hasOwnProperty.call(pages, candidate);
|
|
17
|
+
};
|
|
12
18
|
let filename = base;
|
|
13
|
-
if (
|
|
19
|
+
if (hasFilename(filename)) {
|
|
14
20
|
let extension = 1;
|
|
15
21
|
filename = `${base}-${extension}`;
|
|
16
|
-
while (
|
|
22
|
+
while (hasFilename(filename)) {
|
|
17
23
|
extension += 1;
|
|
18
24
|
filename = `${base}-${extension}`;
|
|
19
25
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DecoratedPagesConfig, PagesConfig } from '@mintlify/validation';
|
|
2
2
|
export declare const DEFAULT_API_GROUP_NAME = "API Reference";
|
|
3
3
|
export declare const DEFAULT_WEBSOCKETS_GROUP_NAME = "Websockets";
|
|
4
|
-
export declare function findNavGroup(nav:
|
|
5
|
-
export declare function findNavGroup(nav:
|
|
4
|
+
export declare function findNavGroup(nav: PagesConfig, groupName?: string, tagKey?: string): PagesConfig;
|
|
5
|
+
export declare function findNavGroup(nav: DecoratedPagesConfig, groupName?: string, tagKey?: string): DecoratedPagesConfig;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
export const DEFAULT_API_GROUP_NAME = 'API Reference';
|
|
2
2
|
export const DEFAULT_WEBSOCKETS_GROUP_NAME = 'Websockets';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const tagKeys = new WeakMap();
|
|
4
|
+
export function findNavGroup(nav, groupName = DEFAULT_API_GROUP_NAME, tagKey) {
|
|
5
|
+
const group = nav.find((fileOrGroup) => typeof fileOrGroup === 'object' &&
|
|
6
|
+
'group' in fileOrGroup &&
|
|
7
|
+
fileOrGroup.group === groupName &&
|
|
8
|
+
(tagKey === undefined || tagKeys.get(fileOrGroup) === tagKey));
|
|
9
|
+
if (group === undefined || typeof group !== 'object' || !('pages' in group)) {
|
|
6
10
|
const newGroup = {
|
|
7
11
|
group: groupName,
|
|
8
12
|
pages: [],
|
|
9
13
|
};
|
|
14
|
+
if (tagKey !== undefined)
|
|
15
|
+
tagKeys.set(newGroup, tagKey);
|
|
10
16
|
nav.push(newGroup);
|
|
11
17
|
return newGroup.pages;
|
|
12
18
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* but without Node.js dependencies (fs, path, etc.).
|
|
5
5
|
*/
|
|
6
6
|
import type { PageMetaTags } from '@mintlify/models';
|
|
7
|
-
import {
|
|
7
|
+
import { type DocumentV3 } from '@mintlify/validation';
|
|
8
|
+
import { type OpenApiTagGroup } from './tagHierarchy.js';
|
|
8
9
|
/**
|
|
9
10
|
* Build MDX content string matching what createOpenApiFrontmatter produces in prebuild.
|
|
10
11
|
* Includes title, description, openapi tag, version, deprecated, and all x-mint metadata
|
|
@@ -28,6 +29,7 @@ export type GeneratedApiPage = {
|
|
|
28
29
|
path: string;
|
|
29
30
|
/** Operation tag (used for sub-grouping) */
|
|
30
31
|
tag?: string;
|
|
32
|
+
tagPath?: OpenApiTagGroup[];
|
|
31
33
|
/** Whether the operation is x-hidden */
|
|
32
34
|
hidden?: boolean;
|
|
33
35
|
/** Whether this is a webhook */
|
|
@@ -41,7 +43,7 @@ export type GeneratedApiPage = {
|
|
|
41
43
|
};
|
|
42
44
|
export type GenerateOpenApiPagesOptions = {
|
|
43
45
|
/** Parsed OpenAPI v3 document */
|
|
44
|
-
spec:
|
|
46
|
+
spec: DocumentV3;
|
|
45
47
|
/** File path of the spec, e.g. "api-reference/openapi.json" */
|
|
46
48
|
specFilePath: string;
|
|
47
49
|
/** Output directory for slug generation, defaults to "api-reference" */
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { getPathItemOperations } from '@mintlify/validation';
|
|
1
2
|
import jsYaml from 'js-yaml';
|
|
2
|
-
import { OpenAPIV3 } from 'openapi-types';
|
|
3
3
|
import { optionallyAddLeadingSlash } from '../fs/optionallySlash.js';
|
|
4
4
|
import { mergePageGroups } from '../navigation/groupAccess.js';
|
|
5
5
|
import { slugToTitle } from '../slug/slugToTitle.js';
|
|
6
6
|
import { buildOpenApiMetaTag } from './buildOpenApiMetaTag.js';
|
|
7
7
|
import { prepareStringToBeValidFilename, generateUniqueFilenameWithoutExtension, } from './filenameUtils.js';
|
|
8
8
|
import { getOpenApiTitleAndDescription } from './getOpenApiTitleAndDescription.js';
|
|
9
|
-
import {
|
|
9
|
+
import { createOpenApiBadgeResolver, createOpenApiTagResolver, isOpenApi32, } from './tagHierarchy.js';
|
|
10
10
|
/**
|
|
11
11
|
* Build MDX content string matching what createOpenApiFrontmatter produces in prebuild.
|
|
12
12
|
* Includes title, description, openapi tag, version, deprecated, and all x-mint metadata
|
|
@@ -73,11 +73,14 @@ function getXMintGroups(pathObject, operationObject) {
|
|
|
73
73
|
return groups;
|
|
74
74
|
}
|
|
75
75
|
export function generateOpenApiPages(opts) {
|
|
76
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
76
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
77
77
|
const { spec, specFilePath, outputDirectory } = opts;
|
|
78
78
|
const outDir = outputDirectory !== null && outputDirectory !== void 0 ? outputDirectory : DEFAULT_OUTPUT_DIR;
|
|
79
79
|
const openApiFilePathFromRoot = optionallyAddLeadingSlash(specFilePath);
|
|
80
80
|
const pages = [];
|
|
81
|
+
const resolveTags = createOpenApiTagResolver(spec);
|
|
82
|
+
const nestedTags = isOpenApi32(spec);
|
|
83
|
+
const resolveBadge = createOpenApiBadgeResolver(spec);
|
|
81
84
|
// Track used filenames for uniqueness (mirrors the nav array in processOpenApiPath)
|
|
82
85
|
const usedFilenames = [];
|
|
83
86
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -89,26 +92,23 @@ export function generateOpenApiPages(opts) {
|
|
|
89
92
|
},
|
|
90
93
|
];
|
|
91
94
|
// Process paths
|
|
92
|
-
for (const [apiPath, pathItemObject] of Object.entries(spec.paths)) {
|
|
95
|
+
for (const [apiPath, pathItemObject] of Object.entries((_b = spec.paths) !== null && _b !== void 0 ? _b : {})) {
|
|
93
96
|
if (!pathItemObject || typeof pathItemObject !== 'object')
|
|
94
97
|
continue;
|
|
95
98
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
96
99
|
const pathItem = pathItemObject;
|
|
97
|
-
for (const method of
|
|
98
|
-
if (!(method in pathItem))
|
|
99
|
-
continue;
|
|
100
|
+
for (const [method, pathOperation] of getPathItemOperations(pathItem)) {
|
|
100
101
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
101
|
-
const operation =
|
|
102
|
-
if (!operation)
|
|
103
|
-
continue;
|
|
102
|
+
const operation = pathOperation;
|
|
104
103
|
if (operation['x-excluded'])
|
|
105
104
|
continue;
|
|
106
105
|
const xMint = operation['x-mint'];
|
|
107
106
|
const xMintGroups = getXMintGroups(pathItem, operation);
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
let
|
|
107
|
+
const tagPath = resolveTags(operation.tags);
|
|
108
|
+
const tagName = (_c = tagPath.at(-1)) === null || _c === void 0 ? void 0 : _c.name;
|
|
109
|
+
const groupName = (_d = tagPath.at(-1)) === null || _d === void 0 ? void 0 : _d.label;
|
|
110
|
+
let title = (_e = prepareStringToBeValidFilename(operation.summary)) !== null && _e !== void 0 ? _e : `${method.toLowerCase()}-${prepareStringToBeValidFilename(apiPath)}`;
|
|
111
|
+
let folder = (_f = prepareStringToBeValidFilename(tagName)) !== null && _f !== void 0 ? _f : '';
|
|
112
112
|
let base = posixJoin(outDir, folder, title);
|
|
113
113
|
if (xMint === null || xMint === void 0 ? void 0 : xMint.href) {
|
|
114
114
|
const href = xMint.href.startsWith('/') ? xMint.href.slice(1) : xMint.href;
|
|
@@ -129,20 +129,11 @@ export function generateOpenApiPages(opts) {
|
|
|
129
129
|
if (xMintGroups.length > 0) {
|
|
130
130
|
xMintMetadata = Object.assign(Object.assign({}, xMintMetadata), { groups: mergePageGroups(xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.groups, xMintGroups) });
|
|
131
131
|
}
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
href: posixResolve('/', filenameWithoutExtension),
|
|
138
|
-
method,
|
|
139
|
-
path: apiPath,
|
|
140
|
-
tag: groupName,
|
|
141
|
-
hidden: operation['x-hidden'] === true,
|
|
142
|
-
xMintMetadata,
|
|
143
|
-
xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content,
|
|
144
|
-
version: opts.version,
|
|
145
|
-
};
|
|
132
|
+
const badge = resolveBadge(operation.tags);
|
|
133
|
+
if (badge !== undefined && (xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.tag) === undefined) {
|
|
134
|
+
xMintMetadata = Object.assign(Object.assign({}, xMintMetadata), { tag: badge });
|
|
135
|
+
}
|
|
136
|
+
const page = Object.assign(Object.assign({ title: (_h = (_g = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.title) !== null && _g !== void 0 ? _g : titleTag) !== null && _h !== void 0 ? _h : slugToTitle(filenameWithoutExtension), description: (_j = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.description) !== null && _j !== void 0 ? _j : description, deprecated: (_k = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.deprecated) !== null && _k !== void 0 ? _k : operation.deprecated, openapi: openapiMetaTag, href: posixResolve('/', filenameWithoutExtension), method, path: apiPath, tag: groupName }, (nestedTags ? { tagPath } : {})), { hidden: operation['x-hidden'] === true, xMintMetadata, xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content, version: opts.version });
|
|
146
137
|
pages.push(page);
|
|
147
138
|
}
|
|
148
139
|
}
|
|
@@ -157,20 +148,17 @@ export function generateOpenApiPages(opts) {
|
|
|
157
148
|
continue;
|
|
158
149
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
159
150
|
const pathItem = webhookObject;
|
|
160
|
-
for (const
|
|
161
|
-
if (!(method in pathItem))
|
|
162
|
-
continue;
|
|
151
|
+
for (const [, pathOperation] of getPathItemOperations(pathItem)) {
|
|
163
152
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
164
|
-
const operation =
|
|
165
|
-
if (!operation)
|
|
166
|
-
continue;
|
|
153
|
+
const operation = pathOperation;
|
|
167
154
|
if (operation['x-excluded'])
|
|
168
155
|
continue;
|
|
169
156
|
const xMint = operation['x-mint'];
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
let
|
|
157
|
+
const tagPath = resolveTags(operation.tags);
|
|
158
|
+
const tagName = (_l = tagPath.at(-1)) === null || _l === void 0 ? void 0 : _l.name;
|
|
159
|
+
const groupName = (_m = tagPath.at(-1)) === null || _m === void 0 ? void 0 : _m.label;
|
|
160
|
+
let title = (_o = prepareStringToBeValidFilename(operation.summary)) !== null && _o !== void 0 ? _o : `${prepareStringToBeValidFilename(webhook)}`;
|
|
161
|
+
let folder = (_p = prepareStringToBeValidFilename(tagName)) !== null && _p !== void 0 ? _p : '';
|
|
174
162
|
let base = posixJoin(outDir, folder, title);
|
|
175
163
|
if (xMint === null || xMint === void 0 ? void 0 : xMint.href) {
|
|
176
164
|
const href = xMint.href.startsWith('/') ? xMint.href.slice(1) : xMint.href;
|
|
@@ -186,21 +174,10 @@ export function generateOpenApiPages(opts) {
|
|
|
186
174
|
method: 'webhook',
|
|
187
175
|
path: webhook,
|
|
188
176
|
});
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
openapi: openapiMetaTag,
|
|
194
|
-
href: posixResolve('/', filenameWithoutExtension),
|
|
195
|
-
method: 'webhook',
|
|
196
|
-
path: webhook,
|
|
197
|
-
tag: groupName,
|
|
198
|
-
hidden: operation['x-hidden'] === true,
|
|
199
|
-
isWebhook: true,
|
|
200
|
-
xMintMetadata: xMint === null || xMint === void 0 ? void 0 : xMint.metadata,
|
|
201
|
-
xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content,
|
|
202
|
-
version: opts.version,
|
|
203
|
-
};
|
|
177
|
+
const badge = resolveBadge(operation.tags);
|
|
178
|
+
const xMintMetadata = badge !== undefined && ((_q = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _q === void 0 ? void 0 : _q.tag) === undefined
|
|
179
|
+
? Object.assign(Object.assign({}, xMint === null || xMint === void 0 ? void 0 : xMint.metadata), { tag: badge }) : xMint === null || xMint === void 0 ? void 0 : xMint.metadata;
|
|
180
|
+
const page = Object.assign(Object.assign({ title: (_s = (_r = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _r === void 0 ? void 0 : _r.title) !== null && _s !== void 0 ? _s : slugToTitle(filenameWithoutExtension), description: (_u = (_t = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _t === void 0 ? void 0 : _t.description) !== null && _u !== void 0 ? _u : operation.description, deprecated: (_w = (_v = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _v === void 0 ? void 0 : _v.deprecated) !== null && _w !== void 0 ? _w : operation.deprecated, openapi: openapiMetaTag, href: posixResolve('/', filenameWithoutExtension), method: 'webhook', path: webhook, tag: groupName }, (nestedTags ? { tagPath } : {})), { hidden: operation['x-hidden'] === true, isWebhook: true, xMintMetadata, xMintContent: xMint === null || xMint === void 0 ? void 0 : xMint.content, version: opts.version });
|
|
204
181
|
pages.push(page);
|
|
205
182
|
}
|
|
206
183
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { httpMethods, isFixedHttpMethod, parseHttpMethod } from '@mintlify/validation';
|
|
1
2
|
import { findOpenApiPath } from './findOpenApiPath.js';
|
|
2
3
|
import { potentiallyParseOpenApiString } from './parseOpenApiString.js';
|
|
3
4
|
export const getOpenApiOperationMethodAndEndpoint = (endpointStr, openApiFiles) => {
|
|
@@ -35,24 +36,28 @@ export const getOpenApiOperationMethodAndEndpoint = (endpointStr, openApiFiles)
|
|
|
35
36
|
};
|
|
36
37
|
};
|
|
37
38
|
const getOperation = (method, pathObject) => {
|
|
38
|
-
|
|
39
|
+
var _a;
|
|
39
40
|
if (method !== undefined) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
const parsed = parseHttpMethod(method);
|
|
42
|
+
if (parsed === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (isFixedHttpMethod(parsed)) {
|
|
45
|
+
return parsed in pathObject
|
|
46
|
+
? {
|
|
47
|
+
method: parsed,
|
|
48
|
+
operation: pathObject[parsed],
|
|
49
|
+
}
|
|
50
|
+
: undefined;
|
|
46
51
|
}
|
|
52
|
+
const operation = 'additionalOperations' in pathObject ? (_a = pathObject.additionalOperations) === null || _a === void 0 ? void 0 : _a[parsed] : undefined;
|
|
53
|
+
return operation ? { method: parsed, operation } : undefined;
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
}
|
|
55
|
+
for (const fixedMethod of httpMethods) {
|
|
56
|
+
if (fixedMethod in pathObject) {
|
|
57
|
+
return {
|
|
58
|
+
method: fixedMethod,
|
|
59
|
+
operation: pathObject[fixedMethod],
|
|
60
|
+
};
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
return undefined;
|
|
@@ -2,4 +2,4 @@ import { OpenAPI } from 'openapi-types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Resolves the display name for a tag. Prioritizes x-group extension, falls back to tag name.
|
|
4
4
|
*/
|
|
5
|
-
export declare const getTagDisplayName: (tagName: string | undefined, schema: OpenAPI.Document) => string | undefined;
|
|
5
|
+
export declare const getTagDisplayName: (tagName: string | undefined, schema: Pick<OpenAPI.Document, "tags">) => string | undefined;
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -18,3 +18,4 @@ export { prepareStringToBeValidFilename, generateUniqueFilenameWithoutExtension,
|
|
|
18
18
|
export { findNavGroup, DEFAULT_API_GROUP_NAME, DEFAULT_WEBSOCKETS_GROUP_NAME, } from './findNavGroup.js';
|
|
19
19
|
export { generateOpenApiPages, buildGeneratedMdxContent, type GeneratedApiPage, type GenerateOpenApiPagesOptions, } from './generateOpenApiPages.js';
|
|
20
20
|
export { getTagDisplayName } from './getTagDisplayName.js';
|
|
21
|
+
export { createOpenApiBadgeResolver, createOpenApiTagResolver, findOpenApiNavGroup, isOpenApi32, validateOpenApiTagTree, type OpenApiTag, type OpenApiTagGroup, } from './tagHierarchy.js';
|
package/dist/openapi/index.js
CHANGED
|
@@ -18,3 +18,4 @@ export { prepareStringToBeValidFilename, generateUniqueFilenameWithoutExtension,
|
|
|
18
18
|
export { findNavGroup, DEFAULT_API_GROUP_NAME, DEFAULT_WEBSOCKETS_GROUP_NAME, } from './findNavGroup.js';
|
|
19
19
|
export { generateOpenApiPages, buildGeneratedMdxContent, } from './generateOpenApiPages.js';
|
|
20
20
|
export { getTagDisplayName } from './getTagDisplayName.js';
|
|
21
|
+
export { createOpenApiBadgeResolver, createOpenApiTagResolver, findOpenApiNavGroup, isOpenApi32, validateOpenApiTagTree, } from './tagHierarchy.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isFixedHttpMethod, parseHttpMethod, } from '@mintlify/validation';
|
|
2
2
|
import { isAbsoluteUrl } from '../isAbsoluteUrl.js';
|
|
3
3
|
import { isDocsConfig } from '../isDocsConfig.js';
|
|
4
4
|
export const parseApiString = (apiString, config) => {
|
|
@@ -6,21 +6,19 @@ export const parseApiString = (apiString, config) => {
|
|
|
6
6
|
if (!components[0] || !components[1] || components.length > 2) {
|
|
7
7
|
throw new Error('improperly formatted api string');
|
|
8
8
|
}
|
|
9
|
-
const
|
|
9
|
+
const method = parseHttpMethod(components[0]);
|
|
10
10
|
const endpointStr = components[1];
|
|
11
|
-
|
|
12
|
-
if (!VALID_METHODS.includes(method)) {
|
|
11
|
+
if (method === undefined || (!isFixedHttpMethod(method) && method !== components[0])) {
|
|
13
12
|
throw new Error('invalid http method');
|
|
14
13
|
}
|
|
15
14
|
const { origin, path } = parseEndpoint(endpointStr);
|
|
16
15
|
const servers = origin ? [{ url: origin }] : parseServers(config);
|
|
17
16
|
return {
|
|
18
17
|
path,
|
|
19
|
-
method
|
|
18
|
+
method,
|
|
20
19
|
servers,
|
|
21
20
|
};
|
|
22
21
|
};
|
|
23
|
-
const VALID_METHODS = Object.values(OpenAPIV3.HttpMethods);
|
|
24
22
|
const parseEndpoint = (endpoint) => {
|
|
25
23
|
if (!isAbsoluteUrl(endpoint)) {
|
|
26
24
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type HttpMethod } from '@mintlify/validation';
|
|
2
|
+
export type OpenApiStringMethod = HttpMethod | 'webhook';
|
|
2
3
|
/**
|
|
3
4
|
*
|
|
4
5
|
* @param str either the openapi or api string defined in the frontmatter
|
|
@@ -8,7 +9,7 @@ import { OpenAPIV3 } from 'openapi-types';
|
|
|
8
9
|
* returns undefined for any of the values (fails silently)
|
|
9
10
|
*/
|
|
10
11
|
export declare const potentiallyParseOpenApiString: (str: string) => {
|
|
11
|
-
method:
|
|
12
|
+
method: OpenApiStringMethod;
|
|
12
13
|
endpoint: string;
|
|
13
14
|
filename?: string;
|
|
14
15
|
} | undefined;
|
|
@@ -20,7 +21,7 @@ export declare const potentiallyParseOpenApiString: (str: string) => {
|
|
|
20
21
|
* filename: filename of the openapi file (if applicable)
|
|
21
22
|
*/
|
|
22
23
|
export declare const parseOpenApiString: (str: string) => {
|
|
23
|
-
method:
|
|
24
|
+
method: OpenApiStringMethod;
|
|
24
25
|
endpoint: string;
|
|
25
26
|
filename?: string;
|
|
26
27
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const
|
|
1
|
+
import { isFixedHttpMethod, parseHttpMethod } from '@mintlify/validation';
|
|
2
|
+
const parseMethod = (method) => method.toLowerCase() === 'webhook' ? 'webhook' : parseHttpMethod(method);
|
|
3
3
|
/**
|
|
4
4
|
*
|
|
5
5
|
* @param str either the openapi or api string defined in the frontmatter
|
|
@@ -11,34 +11,36 @@ const VALID_METHODS = Object.values(OpenAPIV3.HttpMethods).concat('webhook');
|
|
|
11
11
|
export const potentiallyParseOpenApiString = (str) => {
|
|
12
12
|
const components = str.trim().split(/\s+/);
|
|
13
13
|
let filename;
|
|
14
|
-
let
|
|
14
|
+
let rawMethod;
|
|
15
15
|
let endpoint;
|
|
16
16
|
if (components.length > 3) {
|
|
17
17
|
// improperly formatted openapi string
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
else if (components[0] && components[1] && components[2]) {
|
|
21
|
-
[filename,
|
|
21
|
+
[filename, rawMethod, endpoint] = components;
|
|
22
22
|
}
|
|
23
23
|
else if (components[0] && components[1]) {
|
|
24
|
-
[
|
|
24
|
+
[rawMethod, endpoint] = components;
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
27
|
// improperly formatted openapi string
|
|
28
28
|
return undefined;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
30
|
+
const method = parseMethod(rawMethod);
|
|
31
|
+
if (method === undefined) {
|
|
32
|
+
// invalid http method
|
|
33
|
+
return undefined;
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
method
|
|
39
|
-
endpoint
|
|
40
|
-
|
|
41
|
-
}
|
|
35
|
+
if (!isFixedHttpMethod(method) &&
|
|
36
|
+
method !== 'webhook' &&
|
|
37
|
+
(rawMethod !== method || !endpoint.startsWith('/'))) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (method === 'query' && !endpoint.startsWith('/')) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return { method, endpoint, filename };
|
|
42
44
|
};
|
|
43
45
|
/**
|
|
44
46
|
*
|
|
@@ -50,29 +52,25 @@ export const potentiallyParseOpenApiString = (str) => {
|
|
|
50
52
|
export const parseOpenApiString = (str) => {
|
|
51
53
|
const components = str.trim().split(/\s+/);
|
|
52
54
|
let filename;
|
|
53
|
-
let
|
|
55
|
+
let rawMethod;
|
|
54
56
|
let endpoint;
|
|
55
57
|
if (components.length > 3) {
|
|
56
58
|
throw new Error('improperly formatted openapi string');
|
|
57
59
|
}
|
|
58
60
|
else if (components[0] && components[1] && components[2]) {
|
|
59
|
-
[filename,
|
|
61
|
+
[filename, rawMethod, endpoint] = components;
|
|
60
62
|
}
|
|
61
63
|
else if (components[0] && components[1]) {
|
|
62
|
-
[
|
|
64
|
+
[rawMethod, endpoint] = components;
|
|
63
65
|
}
|
|
64
66
|
else {
|
|
65
67
|
throw new Error('improperly formatted openapi string');
|
|
66
68
|
}
|
|
67
|
-
method =
|
|
68
|
-
if (
|
|
69
|
+
const method = parseMethod(rawMethod);
|
|
70
|
+
if (method === undefined) {
|
|
69
71
|
throw new Error('invalid http method');
|
|
70
72
|
}
|
|
71
|
-
return {
|
|
72
|
-
method: method,
|
|
73
|
-
endpoint,
|
|
74
|
-
filename,
|
|
75
|
-
};
|
|
73
|
+
return { method, endpoint, filename };
|
|
76
74
|
};
|
|
77
75
|
/**
|
|
78
76
|
* same as above, but for openapi-schema frontmatter
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type OpenAPIV3_2 } from '@mintlify/validation';
|
|
2
|
+
import type { OpenApiValidateError } from './validate.js';
|
|
3
|
+
type PathItemSources = Partial<Pick<OpenAPIV3_2.Document, 'paths' | 'webhooks' | 'components'>>;
|
|
4
|
+
export declare const validateQuerystringParameters: (document: PathItemSources) => OpenApiValidateError[];
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { getPathItemOperations, isFixedHttpMethod } from '@mintlify/validation';
|
|
13
|
+
const escapePointer = (segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
14
|
+
const componentName = (ref, section) => { var _a; return (_a = new RegExp(`^#/components/${section}/(.+)$`).exec(ref)) === null || _a === void 0 ? void 0 : _a[1]; };
|
|
15
|
+
const resolveParameter = (parameter, components, seen = new Set()) => {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!('$ref' in parameter))
|
|
18
|
+
return parameter;
|
|
19
|
+
const name = componentName(parameter.$ref, 'parameters');
|
|
20
|
+
if (name === undefined || seen.has(name))
|
|
21
|
+
return undefined;
|
|
22
|
+
const resolved = (_a = components === null || components === void 0 ? void 0 : components.parameters) === null || _a === void 0 ? void 0 : _a[name];
|
|
23
|
+
return resolved ? resolveParameter(resolved, components, seen.add(name)) : undefined;
|
|
24
|
+
};
|
|
25
|
+
const parameterLocation = (parameter, components) => { var _a; return (_a = resolveParameter(parameter, components)) === null || _a === void 0 ? void 0 : _a.in; };
|
|
26
|
+
const querystringNames = (parameters, components) => new Set(parameters.flatMap((parameter) => {
|
|
27
|
+
const resolved = resolveParameter(parameter, components);
|
|
28
|
+
return (resolved === null || resolved === void 0 ? void 0 : resolved.in) === 'querystring' ? [resolved.name] : [];
|
|
29
|
+
}));
|
|
30
|
+
const resolvePathItem = (pathItem, components, seen = new Set()) => {
|
|
31
|
+
var _a;
|
|
32
|
+
if (!('$ref' in pathItem) || typeof pathItem.$ref !== 'string')
|
|
33
|
+
return pathItem;
|
|
34
|
+
const { $ref, summary, description } = pathItem, siblings = __rest(pathItem, ["$ref", "summary", "description"]);
|
|
35
|
+
const name = componentName($ref, 'pathItems');
|
|
36
|
+
const target = name !== undefined && !seen.has(name) ? (_a = components === null || components === void 0 ? void 0 : components.pathItems) === null || _a === void 0 ? void 0 : _a[name] : undefined;
|
|
37
|
+
const resolvedTarget = target && name !== undefined ? resolvePathItem(target, components, seen.add(name)) : {};
|
|
38
|
+
return Object.assign(Object.assign(Object.assign({}, resolvedTarget), siblings), { summary, description });
|
|
39
|
+
};
|
|
40
|
+
export const validateQuerystringParameters = (document) => {
|
|
41
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
42
|
+
const errors = [];
|
|
43
|
+
const pathItems = [
|
|
44
|
+
...Object.entries((_a = document.paths) !== null && _a !== void 0 ? _a : {}).map(([path, item]) => [`/paths/${escapePointer(path)}`, item]),
|
|
45
|
+
...Object.entries((_b = document.webhooks) !== null && _b !== void 0 ? _b : {}).map(([name, item]) => [
|
|
46
|
+
`/webhooks/${escapePointer(name)}`,
|
|
47
|
+
item,
|
|
48
|
+
]),
|
|
49
|
+
...Object.entries((_d = (_c = document.components) === null || _c === void 0 ? void 0 : _c.pathItems) !== null && _d !== void 0 ? _d : {}).map(([name, item]) => [
|
|
50
|
+
`/components/pathItems/${escapePointer(name)}`,
|
|
51
|
+
item,
|
|
52
|
+
]),
|
|
53
|
+
];
|
|
54
|
+
for (const [location, rawPathItem] of pathItems) {
|
|
55
|
+
if (!rawPathItem)
|
|
56
|
+
continue;
|
|
57
|
+
const pathItem = resolvePathItem(rawPathItem, document.components);
|
|
58
|
+
const pathLocations = ((_e = pathItem.parameters) !== null && _e !== void 0 ? _e : []).map((parameter) => parameterLocation(parameter, document.components));
|
|
59
|
+
for (const [method, operation] of getPathItemOperations(pathItem)) {
|
|
60
|
+
const operationLocations = ((_f = operation.parameters) !== null && _f !== void 0 ? _f : []).map((parameter) => parameterLocation(parameter, document.components));
|
|
61
|
+
const locations = [...pathLocations, ...operationLocations];
|
|
62
|
+
const operationPointer = isFixedHttpMethod(method)
|
|
63
|
+
? escapePointer(method)
|
|
64
|
+
: `additionalOperations/${escapePointer(method)}`;
|
|
65
|
+
if (locations.includes('querystring') && locations.includes('query')) {
|
|
66
|
+
errors.push({
|
|
67
|
+
path: `${location}/${operationPointer}/parameters`,
|
|
68
|
+
message: 'in: querystring cannot be combined with in: query parameters on the same operation',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const names = querystringNames([...((_g = pathItem.parameters) !== null && _g !== void 0 ? _g : []), ...((_h = operation.parameters) !== null && _h !== void 0 ? _h : [])], document.components);
|
|
72
|
+
if (names.size > 1) {
|
|
73
|
+
errors.push({
|
|
74
|
+
path: `${location}/${operationPointer}/parameters`,
|
|
75
|
+
message: `Only one in: querystring parameter may apply to an operation; found ${[...names].join(', ')}`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return errors;
|
|
81
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DocumentV3, OpenAPIV3_2 } from '@mintlify/validation';
|
|
2
|
+
import type { OpenAPI } from 'openapi-types';
|
|
3
|
+
export type OpenApiTag = OpenAPIV3_2.TagObject & {
|
|
4
|
+
'x-group'?: string;
|
|
5
|
+
};
|
|
6
|
+
export type OpenApiTagGroup = {
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
key?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const isOpenApi32: (schema: OpenAPI.Document | DocumentV3) => boolean;
|
|
12
|
+
export declare function validateOpenApiTagTree(tags?: readonly OpenApiTag[]): string[];
|
|
13
|
+
export declare function createOpenApiTagResolver(schema: OpenAPI.Document | DocumentV3): (operationTags?: readonly string[]) => OpenApiTagGroup[];
|
|
14
|
+
export declare function createOpenApiBadgeResolver(schema: OpenAPI.Document | DocumentV3): (operationTags?: readonly string[]) => string | undefined;
|
|
15
|
+
export declare function findOpenApiNavGroup<T>(nav: T, tagPath: readonly OpenApiTagGroup[], findGroup: (nav: T, name?: string, key?: string) => T): T;
|