@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.
@@ -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 (pages.includes(filename)) {
19
+ if (hasFilename(filename)) {
14
20
  let extension = 1;
15
21
  filename = `${base}-${extension}`;
16
- while (pages.includes(filename)) {
22
+ while (hasFilename(filename)) {
17
23
  extension += 1;
18
24
  filename = `${base}-${extension}`;
19
25
  }
@@ -1,5 +1,5 @@
1
- import { DecoratedGroupsConfig, DecoratedPagesConfig, GroupsConfig, PagesConfig } from '@mintlify/validation';
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: GroupsConfig, groupName?: string): PagesConfig;
5
- export declare function findNavGroup(nav: DecoratedGroupsConfig, groupName?: string): DecoratedPagesConfig;
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
- export function findNavGroup(nav, groupName = DEFAULT_API_GROUP_NAME) {
4
- const group = nav.find((fileOrGroup) => typeof fileOrGroup === 'object' && 'group' in fileOrGroup && fileOrGroup.group === groupName);
5
- if (group === undefined || !('pages' in group)) {
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 { OpenAPIV3 } from 'openapi-types';
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: OpenAPIV3.Document;
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 { getTagDisplayName } from './getTagDisplayName.js';
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 Object.values(OpenAPIV3.HttpMethods)) {
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 = pathItem[method];
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 tagName = (_b = operation.tags) === null || _b === void 0 ? void 0 : _b[0];
109
- const groupName = getTagDisplayName(tagName, spec);
110
- let title = (_c = prepareStringToBeValidFilename(operation.summary)) !== null && _c !== void 0 ? _c : `${method}-${prepareStringToBeValidFilename(apiPath)}`;
111
- let folder = (_d = prepareStringToBeValidFilename(tagName)) !== null && _d !== void 0 ? _d : '';
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 page = {
133
- title: (_f = (_e = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.title) !== null && _e !== void 0 ? _e : titleTag) !== null && _f !== void 0 ? _f : slugToTitle(filenameWithoutExtension),
134
- description: (_g = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.description) !== null && _g !== void 0 ? _g : description,
135
- deprecated: (_h = xMintMetadata === null || xMintMetadata === void 0 ? void 0 : xMintMetadata.deprecated) !== null && _h !== void 0 ? _h : operation.deprecated,
136
- openapi: openapiMetaTag,
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 method of Object.values(OpenAPIV3.HttpMethods)) {
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 = pathItem[method];
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 tagName = (_j = operation.tags) === null || _j === void 0 ? void 0 : _j[0];
171
- const groupName = getTagDisplayName(tagName, spec);
172
- let title = (_k = prepareStringToBeValidFilename(operation.summary)) !== null && _k !== void 0 ? _k : `${prepareStringToBeValidFilename(webhook)}`;
173
- let folder = (_l = prepareStringToBeValidFilename(tagName)) !== null && _l !== void 0 ? _l : '';
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 page = {
190
- title: (_o = (_m = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _m === void 0 ? void 0 : _m.title) !== null && _o !== void 0 ? _o : slugToTitle(filenameWithoutExtension),
191
- description: (_q = (_p = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _p === void 0 ? void 0 : _p.description) !== null && _q !== void 0 ? _q : operation.description,
192
- deprecated: (_s = (_r = xMint === null || xMint === void 0 ? void 0 : xMint.metadata) === null || _r === void 0 ? void 0 : _r.deprecated) !== null && _s !== void 0 ? _s : operation.deprecated,
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
- const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
39
+ var _a;
39
40
  if (method !== undefined) {
40
- const lowerMethod = method.toLowerCase();
41
- if (methods.includes(lowerMethod) && lowerMethod in pathObject) {
42
- return {
43
- method: lowerMethod,
44
- operation: pathObject[lowerMethod],
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
- else {
49
- for (const method of methods) {
50
- if (method in pathObject) {
51
- return {
52
- method,
53
- operation: pathObject[method],
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;
@@ -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';
@@ -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 { OpenAPIV3 } from 'openapi-types';
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 upperMethod = components[0];
9
+ const method = parseHttpMethod(components[0]);
10
10
  const endpointStr = components[1];
11
- const method = upperMethod.toLowerCase();
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: 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 { OpenAPIV3 } from 'openapi-types';
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: OpenAPIV3.HttpMethods;
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: OpenAPIV3.HttpMethods;
24
+ method: OpenApiStringMethod;
24
25
  endpoint: string;
25
26
  filename?: string;
26
27
  };
@@ -1,5 +1,5 @@
1
- import { OpenAPIV3 } from 'openapi-types';
2
- const VALID_METHODS = Object.values(OpenAPIV3.HttpMethods).concat('webhook');
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 method;
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, method, endpoint] = components;
21
+ [filename, rawMethod, endpoint] = components;
22
22
  }
23
23
  else if (components[0] && components[1]) {
24
- [method, endpoint] = components;
24
+ [rawMethod, endpoint] = components;
25
25
  }
26
26
  else {
27
27
  // improperly formatted openapi string
28
28
  return undefined;
29
29
  }
30
- if (method) {
31
- method = method.toLowerCase();
32
- if (!VALID_METHODS.includes(method)) {
33
- // invalid http method
34
- return undefined;
35
- }
30
+ const method = parseMethod(rawMethod);
31
+ if (method === undefined) {
32
+ // invalid http method
33
+ return undefined;
36
34
  }
37
- return {
38
- method: method,
39
- endpoint,
40
- filename,
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 method;
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, method, endpoint] = components;
61
+ [filename, rawMethod, endpoint] = components;
60
62
  }
61
63
  else if (components[0] && components[1]) {
62
- [method, endpoint] = components;
64
+ [rawMethod, endpoint] = components;
63
65
  }
64
66
  else {
65
67
  throw new Error('improperly formatted openapi string');
66
68
  }
67
- method = method.toLowerCase();
68
- if (!VALID_METHODS.includes(method)) {
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;