@mintlify/prebuild 1.0.1186 → 1.0.1188
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/prebuild/update/ConfigUpdater.d.ts +90 -0
- package/dist/prebuild/update/docsConfig/generateAsyncApiDivisions.d.ts +2 -1
- package/dist/prebuild/update/docsConfig/generateAsyncApiDivisions.js +22 -2
- package/dist/prebuild/update/docsConfig/generateAsyncApiFromDocsConfig.d.ts +1 -0
- package/dist/prebuild/update/docsConfig/generateAsyncApiFromDocsConfig.js +2 -1
- package/dist/prebuild/update/docsConfig/generateGraphqlDivisions.d.ts +27 -0
- package/dist/prebuild/update/docsConfig/generateGraphqlDivisions.js +259 -0
- package/dist/prebuild/update/docsConfig/generateOpenApiDivisions.d.ts +2 -1
- package/dist/prebuild/update/docsConfig/generateOpenApiDivisions.js +21 -1
- package/dist/prebuild/update/docsConfig/generateOpenApiFromDocsConfig.d.ts +1 -0
- package/dist/prebuild/update/docsConfig/generateOpenApiFromDocsConfig.js +4 -1
- package/dist/prebuild/update/docsConfig/generatedRouteCollisions.d.ts +8 -0
- package/dist/prebuild/update/docsConfig/generatedRouteCollisions.js +10 -0
- package/dist/prebuild/update/docsConfig/index.d.ts +3 -0
- package/dist/prebuild/update/docsConfig/index.js +23 -7
- package/dist/prebuild/update/docsConfig/loadRemoteGraphqlSdl.d.ts +18 -0
- package/dist/prebuild/update/docsConfig/loadRemoteGraphqlSdl.js +217 -0
- package/dist/prebuild/update/index.d.ts +90 -0
- package/dist/prebuild/update/preserveAutoGeneratedMetadata.js +5 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -6,6 +6,7 @@ export declare const generateAsyncApiFromDocsConfig: (navigation: NavigationConf
|
|
|
6
6
|
writeFiles: boolean;
|
|
7
7
|
targetDir?: string;
|
|
8
8
|
localSchema?: boolean;
|
|
9
|
+
writeFile?: (filename: string, content: string) => void | Promise<void>;
|
|
9
10
|
}) => Promise<{
|
|
10
11
|
newNav: NavigationConfig;
|
|
11
12
|
newAsyncApiFiles: AsyncAPIFile[];
|
|
@@ -4,7 +4,7 @@ import { divisions } from '@mintlify/validation';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
const DEFAULT_OUTPUT_DIR = 'api-reference';
|
|
6
6
|
export const generateAsyncApiFromDocsConfig = async (navigation, asyncApiFiles, pagesAcc, opts) => {
|
|
7
|
-
const { overwrite, writeFiles, targetDir, localSchema } = opts;
|
|
7
|
+
const { overwrite, writeFiles, targetDir, localSchema, writeFile } = opts;
|
|
8
8
|
const newAsyncApiFiles = [];
|
|
9
9
|
async function processAsyncApiInNav(nav) {
|
|
10
10
|
let outputDir = DEFAULT_OUTPUT_DIR;
|
|
@@ -52,6 +52,7 @@ export const generateAsyncApiFromDocsConfig = async (navigation, asyncApiFiles,
|
|
|
52
52
|
outDirBasePath: path.join(targetDir ?? '', 'src', '_props'),
|
|
53
53
|
overwrite,
|
|
54
54
|
localSchema,
|
|
55
|
+
writeFile,
|
|
55
56
|
});
|
|
56
57
|
Object.entries(pagesAccFromGeneratedAsyncApiPages).forEach(([key, value]) => {
|
|
57
58
|
pagesAcc[key] = value;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DecoratedNavigationPage, GraphqlReference, GraphqlTarget } from '@mintlify/models';
|
|
2
|
+
import type { DocsConfig } from '@mintlify/validation';
|
|
3
|
+
export type GeneratedGraphqlPage = {
|
|
4
|
+
slug: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
deprecated?: boolean;
|
|
8
|
+
tag: string;
|
|
9
|
+
target: GraphqlTarget;
|
|
10
|
+
};
|
|
11
|
+
export type GenerateGraphqlDivisionsResult = {
|
|
12
|
+
navigation: DocsConfig['navigation'];
|
|
13
|
+
newDocsConfig: DocsConfig;
|
|
14
|
+
pagesAcc: Record<string, DecoratedNavigationPage>;
|
|
15
|
+
generatedPages: GeneratedGraphqlPage[];
|
|
16
|
+
generatedRoutes: string[];
|
|
17
|
+
parsedReferences: Record<string, GraphqlReference>;
|
|
18
|
+
};
|
|
19
|
+
export type GraphqlSourceLoader = (source: string) => Promise<string>;
|
|
20
|
+
export declare const getGraphqlRouteKey: (route: string) => string;
|
|
21
|
+
/** Transform GraphQL navigation without reading or writing the filesystem. */
|
|
22
|
+
export declare function generateGraphqlDivisionsFromDocsConfig(docsConfig: DocsConfig, loadSource: GraphqlSourceLoader): Promise<GenerateGraphqlDivisionsResult>;
|
|
23
|
+
export declare function generateGraphqlDivisions(docsConfig: DocsConfig, contentDirectoryPath: string, targetDir?: string): Promise<GenerateGraphqlDivisionsResult>;
|
|
24
|
+
export declare function loadGraphqlSdlSource(source: string, contentDirectoryPath: string): Promise<string>;
|
|
25
|
+
export declare const isRemoteGraphqlSource: (source: string) => boolean;
|
|
26
|
+
export declare function writeGraphqlArtifacts(result: GenerateGraphqlDivisionsResult, targetDir?: string): Promise<void>;
|
|
27
|
+
export { loadRemoteGraphqlSdl } from './loadRemoteGraphqlSdl.js';
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { parseGraphqlSdl } from '@mintlify/scraping';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import { open } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { getGeneratedRouteKey } from './generatedRouteCollisions.js';
|
|
6
|
+
import { loadRemoteGraphqlSdl, MAX_GRAPHQL_SCHEMA_BYTES } from './loadRemoteGraphqlSdl.js';
|
|
7
|
+
const DEFAULT_OUTPUT_DIR = 'graphql-reference';
|
|
8
|
+
const byName = (left, right) => left.name.localeCompare(right.name);
|
|
9
|
+
export const getGraphqlRouteKey = getGeneratedRouteKey;
|
|
10
|
+
function graphqlConfig(config) {
|
|
11
|
+
return typeof config === 'string'
|
|
12
|
+
? { source: config, directory: DEFAULT_OUTPUT_DIR }
|
|
13
|
+
: { source: config.source, directory: config.directory ?? DEFAULT_OUTPUT_DIR };
|
|
14
|
+
}
|
|
15
|
+
function makePage(source, directory, kind, entry) {
|
|
16
|
+
const section = kind === 'query' ? 'queries' : kind === 'mutation' ? 'mutations' : 'types';
|
|
17
|
+
return {
|
|
18
|
+
slug: path.posix.join(directory, section, entry.name),
|
|
19
|
+
title: entry.name,
|
|
20
|
+
...('description' in entry && entry.description ? { description: entry.description } : {}),
|
|
21
|
+
...(kind !== 'type' && 'deprecationReason' in entry && entry.deprecationReason != null
|
|
22
|
+
? { deprecated: true }
|
|
23
|
+
: {}),
|
|
24
|
+
tag: kind === 'type' && 'kind' in entry ? entry.kind.toUpperCase() : kind.toUpperCase(),
|
|
25
|
+
target: { source, kind, name: entry.name },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function generatePages(reference, source, directory) {
|
|
29
|
+
const roots = new Set([reference.queryType, reference.mutationType, reference.subscriptionType].filter((name) => name !== undefined));
|
|
30
|
+
const typeByName = new Map(reference.types.map((type) => [type.name, type]));
|
|
31
|
+
const queryType = reference.queryType ? typeByName.get(reference.queryType) : undefined;
|
|
32
|
+
const mutationType = reference.mutationType ? typeByName.get(reference.mutationType) : undefined;
|
|
33
|
+
const queryFields = reference.queryType
|
|
34
|
+
? queryType?.kind === 'object'
|
|
35
|
+
? queryType.fields
|
|
36
|
+
: []
|
|
37
|
+
: [];
|
|
38
|
+
const mutationFields = reference.mutationType
|
|
39
|
+
? mutationType?.kind === 'object'
|
|
40
|
+
? mutationType.fields
|
|
41
|
+
: []
|
|
42
|
+
: [];
|
|
43
|
+
return {
|
|
44
|
+
queries: queryFields
|
|
45
|
+
.slice()
|
|
46
|
+
.sort(byName)
|
|
47
|
+
.map((field) => makePage(source, directory, 'query', field)),
|
|
48
|
+
mutations: mutationFields
|
|
49
|
+
.slice()
|
|
50
|
+
.sort(byName)
|
|
51
|
+
.map((field) => makePage(source, directory, 'mutation', field)),
|
|
52
|
+
types: reference.types
|
|
53
|
+
.filter((type) => !roots.has(type.name) &&
|
|
54
|
+
(type.kind === 'object' ||
|
|
55
|
+
type.kind === 'interface' ||
|
|
56
|
+
type.kind === 'union' ||
|
|
57
|
+
type.kind === 'enum'))
|
|
58
|
+
.slice()
|
|
59
|
+
.sort(byName)
|
|
60
|
+
.map((type) => makePage(source, directory, 'type', type)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function mdxStub(page) {
|
|
64
|
+
const fields = [
|
|
65
|
+
`title: ${JSON.stringify(page.title)}`,
|
|
66
|
+
...(page.description ? [`description: ${JSON.stringify(page.description)}`] : []),
|
|
67
|
+
...(page.deprecated ? ['deprecated: true'] : []),
|
|
68
|
+
`tag: ${JSON.stringify(page.tag)}`,
|
|
69
|
+
`graphql: ${JSON.stringify(page.target)}`,
|
|
70
|
+
];
|
|
71
|
+
return `---\n${fields.join('\n')}\n---\n`;
|
|
72
|
+
}
|
|
73
|
+
/** Transform GraphQL navigation without reading or writing the filesystem. */
|
|
74
|
+
export async function generateGraphqlDivisionsFromDocsConfig(docsConfig, loadSource) {
|
|
75
|
+
const references = new Map();
|
|
76
|
+
const parsedReferences = new Map();
|
|
77
|
+
const pagesByRouteKey = new Map();
|
|
78
|
+
async function load(source) {
|
|
79
|
+
let pending = references.get(source);
|
|
80
|
+
if (!pending) {
|
|
81
|
+
pending = loadSource(source).then((sdl) => {
|
|
82
|
+
const reference = parseGraphqlSdl(sdl);
|
|
83
|
+
parsedReferences.set(source, reference);
|
|
84
|
+
return reference;
|
|
85
|
+
});
|
|
86
|
+
references.set(source, pending);
|
|
87
|
+
}
|
|
88
|
+
return pending;
|
|
89
|
+
}
|
|
90
|
+
async function processNode(value) {
|
|
91
|
+
if (Array.isArray(value))
|
|
92
|
+
return Promise.all(value.map(processNode));
|
|
93
|
+
if (typeof value !== 'object' || value === null)
|
|
94
|
+
return value;
|
|
95
|
+
const node = value;
|
|
96
|
+
if ('tab' in node && 'graphql' in node) {
|
|
97
|
+
const { graphql, ...tab } = node;
|
|
98
|
+
const { source, directory } = graphqlConfig(graphql);
|
|
99
|
+
const generated = generatePages(await load(source), source, directory);
|
|
100
|
+
const groups = [
|
|
101
|
+
{ group: 'Queries', pages: generated.queries.map(({ slug }) => slug) },
|
|
102
|
+
{ group: 'Mutations', pages: generated.mutations.map(({ slug }) => slug) },
|
|
103
|
+
{ group: 'Types', pages: generated.types.map(({ slug }) => slug) },
|
|
104
|
+
].filter(({ pages }) => pages.length > 0);
|
|
105
|
+
for (const page of [...generated.queries, ...generated.mutations, ...generated.types]) {
|
|
106
|
+
const routeKey = getGraphqlRouteKey(page.slug);
|
|
107
|
+
const existingPage = pagesByRouteKey.get(routeKey);
|
|
108
|
+
if (existingPage &&
|
|
109
|
+
(existingPage.slug !== page.slug ||
|
|
110
|
+
existingPage.target.source !== page.target.source ||
|
|
111
|
+
existingPage.target.kind !== page.target.kind ||
|
|
112
|
+
existingPage.target.name !== page.target.name)) {
|
|
113
|
+
throw new Error(`Multiple GraphQL references generate colliding routes "${existingPage.slug}" and "${page.slug}"`);
|
|
114
|
+
}
|
|
115
|
+
pagesByRouteKey.set(routeKey, page);
|
|
116
|
+
}
|
|
117
|
+
return { ...tab, groups: [...(tab.groups ?? []), ...groups] };
|
|
118
|
+
}
|
|
119
|
+
return Object.fromEntries(await Promise.all(Object.entries(node).map(async ([key, entry]) => [key, await processNode(entry)])));
|
|
120
|
+
}
|
|
121
|
+
const navigation = await processNode(docsConfig.navigation);
|
|
122
|
+
const generatedPages = [...pagesByRouteKey.values()].sort((left, right) => getGeneratedRouteKey(left.slug).localeCompare(getGeneratedRouteKey(right.slug)) ||
|
|
123
|
+
left.slug.localeCompare(right.slug));
|
|
124
|
+
const pagesAcc = Object.fromEntries(generatedPages.map((page) => [
|
|
125
|
+
page.slug,
|
|
126
|
+
{
|
|
127
|
+
href: `/${page.slug}`,
|
|
128
|
+
title: page.title,
|
|
129
|
+
...(page.description ? { description: page.description } : {}),
|
|
130
|
+
...(page.deprecated ? { deprecated: true } : {}),
|
|
131
|
+
tag: page.tag,
|
|
132
|
+
graphql: page.target,
|
|
133
|
+
},
|
|
134
|
+
]));
|
|
135
|
+
return {
|
|
136
|
+
navigation: navigation,
|
|
137
|
+
newDocsConfig: { ...docsConfig, navigation: navigation },
|
|
138
|
+
pagesAcc,
|
|
139
|
+
generatedPages,
|
|
140
|
+
generatedRoutes: generatedPages.map(({ slug }) => slug),
|
|
141
|
+
parsedReferences: Object.fromEntries([...parsedReferences].sort(([left], [right]) => left.localeCompare(right))),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export async function generateGraphqlDivisions(docsConfig, contentDirectoryPath, targetDir) {
|
|
145
|
+
const result = await generateGraphqlDivisionsFromDocsConfig(docsConfig, (source) => loadGraphqlSdlSource(source, contentDirectoryPath));
|
|
146
|
+
await writeGraphqlArtifacts(result, targetDir);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
export async function loadGraphqlSdlSource(source, contentDirectoryPath) {
|
|
150
|
+
if (path.isAbsolute(source) || path.win32.isAbsolute(source)) {
|
|
151
|
+
throw new Error(`Unable to load GraphQL schema ${source}: path must be relative`);
|
|
152
|
+
}
|
|
153
|
+
let sourceUrl;
|
|
154
|
+
try {
|
|
155
|
+
sourceUrl = new URL(source);
|
|
156
|
+
}
|
|
157
|
+
catch { }
|
|
158
|
+
if (sourceUrl) {
|
|
159
|
+
if (sourceUrl.protocol === 'https:')
|
|
160
|
+
return loadRemoteGraphqlSdl(sourceUrl.href);
|
|
161
|
+
throw new Error(`Unable to load GraphQL schema ${source}: URL must use HTTPS`);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const contentRoot = await fse.realpath(contentDirectoryPath);
|
|
165
|
+
const candidatePath = path.resolve(contentRoot, source);
|
|
166
|
+
const relativePath = path.relative(contentRoot, candidatePath);
|
|
167
|
+
if (relativePath === '..' ||
|
|
168
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
169
|
+
path.isAbsolute(relativePath)) {
|
|
170
|
+
throw new Error('path escapes the content directory');
|
|
171
|
+
}
|
|
172
|
+
const realSourcePath = await fse.realpath(candidatePath);
|
|
173
|
+
const realRelativePath = path.relative(contentRoot, realSourcePath);
|
|
174
|
+
if (realRelativePath === '..' ||
|
|
175
|
+
realRelativePath.startsWith(`..${path.sep}`) ||
|
|
176
|
+
path.isAbsolute(realRelativePath)) {
|
|
177
|
+
throw new Error('path resolves outside the content directory');
|
|
178
|
+
}
|
|
179
|
+
const schemaFile = await open(realSourcePath, 'r');
|
|
180
|
+
try {
|
|
181
|
+
const schemaStats = await schemaFile.stat();
|
|
182
|
+
if (schemaStats.size > MAX_GRAPHQL_SCHEMA_BYTES) {
|
|
183
|
+
throw new Error(`schema exceeds the ${MAX_GRAPHQL_SCHEMA_BYTES} byte limit`);
|
|
184
|
+
}
|
|
185
|
+
const decoder = new TextDecoder();
|
|
186
|
+
let schema = '';
|
|
187
|
+
let byteLength = 0;
|
|
188
|
+
for await (const chunk of schemaFile.createReadStream({ autoClose: false })) {
|
|
189
|
+
byteLength += chunk.length;
|
|
190
|
+
if (byteLength > MAX_GRAPHQL_SCHEMA_BYTES) {
|
|
191
|
+
throw new Error(`schema exceeds the ${MAX_GRAPHQL_SCHEMA_BYTES} byte limit`);
|
|
192
|
+
}
|
|
193
|
+
schema += decoder.decode(chunk, { stream: true });
|
|
194
|
+
}
|
|
195
|
+
return schema + decoder.decode();
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await schemaFile.close();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const detail = error instanceof Error ? `: ${error.message}` : '';
|
|
203
|
+
throw new Error(`Unable to load GraphQL schema ${source}${detail}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export const isRemoteGraphqlSource = (source) => {
|
|
207
|
+
try {
|
|
208
|
+
return new URL(source).protocol === 'https:';
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
function getContainedGraphqlPagePath(targetDir, page) {
|
|
215
|
+
const propsDirectory = path.resolve(targetDir ?? '', 'src', '_props');
|
|
216
|
+
const pagePath = path.resolve(propsDirectory, `${page}.mdx`);
|
|
217
|
+
const relativePath = path.relative(propsDirectory, pagePath);
|
|
218
|
+
if (relativePath.startsWith(`..${path.sep}`) ||
|
|
219
|
+
relativePath === '..' ||
|
|
220
|
+
path.isAbsolute(relativePath)) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
return pagePath;
|
|
224
|
+
}
|
|
225
|
+
export async function writeGraphqlArtifacts(result, targetDir) {
|
|
226
|
+
const propsDirectory = path.resolve(targetDir ?? '', 'src', '_props');
|
|
227
|
+
const artifactPath = path.join(propsDirectory, 'graphql-data.json');
|
|
228
|
+
try {
|
|
229
|
+
const previousArtifacts = JSON.parse(await fse.readFile(artifactPath, 'utf8'));
|
|
230
|
+
if (previousArtifacts.pages != null &&
|
|
231
|
+
typeof previousArtifacts.pages === 'object' &&
|
|
232
|
+
!Array.isArray(previousArtifacts.pages)) {
|
|
233
|
+
await Promise.all(Object.keys(previousArtifacts.pages).flatMap((page) => {
|
|
234
|
+
const pagePath = getContainedGraphqlPagePath(targetDir, page);
|
|
235
|
+
return pagePath ? [fse.remove(pagePath)] : [];
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch { }
|
|
240
|
+
await fse.remove(artifactPath);
|
|
241
|
+
if (Object.keys(result.parsedReferences).length === 0 && result.generatedRoutes.length === 0) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const artifacts = {
|
|
245
|
+
references: result.parsedReferences,
|
|
246
|
+
pages: Object.fromEntries(result.generatedPages.map((page) => {
|
|
247
|
+
const directory = path.posix.dirname(path.posix.dirname(page.slug));
|
|
248
|
+
const routes = Object.fromEntries(result.generatedPages
|
|
249
|
+
.filter((generatedPage) => generatedPage.target.source === page.target.source &&
|
|
250
|
+
generatedPage.target.kind === 'type' &&
|
|
251
|
+
path.posix.dirname(path.posix.dirname(generatedPage.slug)) === directory)
|
|
252
|
+
.map((generatedPage) => [generatedPage.target.name, `/${generatedPage.slug}`]));
|
|
253
|
+
return [page.slug, { target: page.target, routes }];
|
|
254
|
+
})),
|
|
255
|
+
};
|
|
256
|
+
await fse.outputFile(artifactPath, JSON.stringify(artifacts), { flag: 'w' });
|
|
257
|
+
await Promise.all(result.generatedPages.map((page) => fse.outputFile(path.join(targetDir ?? '', 'src', '_props', `${page.slug}.mdx`), mdxStub(page), { flag: 'w' })));
|
|
258
|
+
}
|
|
259
|
+
export { loadRemoteGraphqlSdl } from './loadRemoteGraphqlSdl.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { OpenApiFile, DecoratedNavigationPage } from '@mintlify/models';
|
|
2
2
|
import { DocsConfig } from '@mintlify/validation';
|
|
3
3
|
import type { InvalidSpecFile } from '../../invalidSpecFiles.js';
|
|
4
|
-
export declare const generateOpenApiDivisions: (docsConfig: DocsConfig, openApiFiles: OpenApiFile[], targetDir?: string, localSchema?: boolean, invalidSpecFiles?: InvalidSpecFile[]) => Promise<{
|
|
4
|
+
export declare const generateOpenApiDivisions: (docsConfig: DocsConfig, openApiFiles: OpenApiFile[], targetDir?: string, localSchema?: boolean, invalidSpecFiles?: InvalidSpecFile[], graphqlPages?: Record<string, DecoratedNavigationPage>, deferWrites?: boolean) => Promise<{
|
|
5
5
|
newDocsConfig: DocsConfig;
|
|
6
6
|
pagesAcc: Record<string, DecoratedNavigationPage>;
|
|
7
7
|
openApiFiles: OpenApiFile[];
|
|
8
|
+
flushWrites: () => Promise<void>;
|
|
8
9
|
}>;
|
|
@@ -1,19 +1,39 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
1
2
|
import { getOpenApiFilesFromConfig } from '../read/getOpenApiFilesFromConfig.js';
|
|
3
|
+
import { assertNoGeneratedRouteCollisions } from './generatedRouteCollisions.js';
|
|
2
4
|
import { generateOpenApiFromDocsConfig } from './generateOpenApiFromDocsConfig.js';
|
|
3
|
-
export const generateOpenApiDivisions = async (docsConfig, openApiFiles, targetDir, localSchema, invalidSpecFiles) => {
|
|
5
|
+
export const generateOpenApiDivisions = async (docsConfig, openApiFiles, targetDir, localSchema, invalidSpecFiles, graphqlPages, deferWrites = false) => {
|
|
4
6
|
const openapiFilesFromDocsConfig = await getOpenApiFilesFromConfig('docs', docsConfig, localSchema);
|
|
5
7
|
openApiFiles.push(...openapiFilesFromDocsConfig);
|
|
6
8
|
const pagesAcc = {};
|
|
9
|
+
const writes = [];
|
|
10
|
+
const planWrites = deferWrites || (graphqlPages != null && Object.keys(graphqlPages).length > 0);
|
|
7
11
|
const { newNav, newOpenApiFiles } = await generateOpenApiFromDocsConfig(docsConfig.navigation, openApiFiles, pagesAcc, {
|
|
8
12
|
overwrite: true,
|
|
9
13
|
writeFiles: true,
|
|
10
14
|
targetDir,
|
|
11
15
|
localSchema,
|
|
12
16
|
invalidSpecFiles,
|
|
17
|
+
...(planWrites
|
|
18
|
+
? {
|
|
19
|
+
writeFile: (filename, content) => {
|
|
20
|
+
writes.push({ filename, content });
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
: {}),
|
|
13
24
|
});
|
|
25
|
+
const flushWrites = async () => {
|
|
26
|
+
await Promise.all(writes.map(({ filename, content }) => fse.outputFile(filename, content)));
|
|
27
|
+
};
|
|
28
|
+
if (graphqlPages) {
|
|
29
|
+
assertNoGeneratedRouteCollisions({ label: 'GraphQL', pages: graphqlPages }, { label: 'OpenAPI', pages: pagesAcc });
|
|
30
|
+
}
|
|
31
|
+
if (!deferWrites)
|
|
32
|
+
await flushWrites();
|
|
14
33
|
return {
|
|
15
34
|
newDocsConfig: { ...docsConfig, navigation: newNav },
|
|
16
35
|
pagesAcc,
|
|
17
36
|
openApiFiles: [...openApiFiles, ...newOpenApiFiles],
|
|
37
|
+
flushWrites,
|
|
18
38
|
};
|
|
19
39
|
};
|
|
@@ -7,6 +7,7 @@ export declare const generateOpenApiFromDocsConfig: (navigation: NavigationConfi
|
|
|
7
7
|
targetDir?: string;
|
|
8
8
|
localSchema?: boolean;
|
|
9
9
|
invalidSpecFiles?: InvalidSpecFile[];
|
|
10
|
+
writeFile?: (filename: string, content: string) => void | Promise<void>;
|
|
10
11
|
}) => Promise<{
|
|
11
12
|
newNav: NavigationConfig;
|
|
12
13
|
newOpenApiFiles: OpenApiFile[];
|
|
@@ -6,7 +6,7 @@ import * as path from 'path';
|
|
|
6
6
|
import { findInvalidSpecFile } from '../../invalidSpecFiles.js';
|
|
7
7
|
const DEFAULT_OUTPUT_DIR = 'api-reference';
|
|
8
8
|
export const generateOpenApiFromDocsConfig = async (navigation, openApiFiles, pagesAcc, opts) => {
|
|
9
|
-
const { overwrite, writeFiles, targetDir, localSchema, invalidSpecFiles } = opts;
|
|
9
|
+
const { overwrite, writeFiles, targetDir, localSchema, invalidSpecFiles, writeFile } = opts;
|
|
10
10
|
const newOpenApiFiles = [];
|
|
11
11
|
const openApiFilePromises = new Map();
|
|
12
12
|
async function processOpenApiInNav(nav) {
|
|
@@ -55,6 +55,7 @@ export const generateOpenApiFromDocsConfig = async (navigation, openApiFiles, pa
|
|
|
55
55
|
outDirBasePath: path.join(targetDir ?? '', 'src', '_props'),
|
|
56
56
|
overwrite,
|
|
57
57
|
localSchema,
|
|
58
|
+
writeFile,
|
|
58
59
|
});
|
|
59
60
|
Object.entries(pagesAccFromGeneratedOpenApiPages).forEach(([key, value]) => {
|
|
60
61
|
pagesAcc[key] = value;
|
|
@@ -173,6 +174,7 @@ export const generateOpenApiFromDocsConfig = async (navigation, openApiFiles, pa
|
|
|
173
174
|
outDirBasePath: path.join(targetDir ?? '', 'src', '_props'),
|
|
174
175
|
overwrite,
|
|
175
176
|
localSchema,
|
|
177
|
+
writeFile,
|
|
176
178
|
}, findNavGroup);
|
|
177
179
|
}
|
|
178
180
|
else {
|
|
@@ -192,6 +194,7 @@ export const generateOpenApiFromDocsConfig = async (navigation, openApiFiles, pa
|
|
|
192
194
|
outDirBasePath: path.join(targetDir ?? '', 'src', '_props'),
|
|
193
195
|
overwrite,
|
|
194
196
|
localSchema,
|
|
197
|
+
writeFile,
|
|
195
198
|
}, findNavGroup);
|
|
196
199
|
}
|
|
197
200
|
await Promise.all(writePromises);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DecoratedNavigationPage } from '@mintlify/models';
|
|
2
|
+
export declare const getGeneratedRouteKey: (route: string) => string;
|
|
3
|
+
type GeneratedRoutes = {
|
|
4
|
+
label: string;
|
|
5
|
+
pages: Record<string, DecoratedNavigationPage>;
|
|
6
|
+
};
|
|
7
|
+
export declare function assertNoGeneratedRouteCollisions(first: GeneratedRoutes, second: GeneratedRoutes): void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const getGeneratedRouteKey = (route) => route.replaceAll('\\', '/').normalize('NFC').toLowerCase();
|
|
2
|
+
export function assertNoGeneratedRouteCollisions(first, second) {
|
|
3
|
+
const firstRoutes = new Map(Object.keys(first.pages).map((route) => [getGeneratedRouteKey(route), route]));
|
|
4
|
+
for (const secondRoute of Object.keys(second.pages)) {
|
|
5
|
+
const firstRoute = firstRoutes.get(getGeneratedRouteKey(secondRoute));
|
|
6
|
+
if (firstRoute) {
|
|
7
|
+
throw new Error(`Generated ${first.label} route "${firstRoute}" collides with generated ${second.label} route "${secondRoute}"`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -28,3 +28,6 @@ export { getOpenApiFilesFromConfig } from '../read/getOpenApiFilesFromConfig.js'
|
|
|
28
28
|
export { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
29
29
|
export { generateAsyncApiFromDocsConfig } from './generateAsyncApiFromDocsConfig.js';
|
|
30
30
|
export { getCustomLanguages } from './getCustomLanguages.js';
|
|
31
|
+
export { generateGraphqlDivisions, generateGraphqlDivisionsFromDocsConfig, getGraphqlRouteKey, isRemoteGraphqlSource, loadGraphqlSdlSource, loadRemoteGraphqlSdl, writeGraphqlArtifacts, } from './generateGraphqlDivisions.js';
|
|
32
|
+
export { assertNoGeneratedRouteCollisions, getGeneratedRouteKey, } from './generatedRouteCollisions.js';
|
|
33
|
+
export type { GeneratedGraphqlPage, GenerateGraphqlDivisionsResult, GraphqlSourceLoader, } from './generateGraphqlDivisions.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getConfigPath } from '../../../utils.js';
|
|
2
2
|
import { DocsConfigUpdater } from '../ConfigUpdater.js';
|
|
3
3
|
import { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
4
|
+
import { generateGraphqlDivisionsFromDocsConfig, loadGraphqlSdlSource, writeGraphqlArtifacts, } from './generateGraphqlDivisions.js';
|
|
4
5
|
import { generateOpenApiDivisions } from './generateOpenApiDivisions.js';
|
|
5
6
|
import { getCustomLanguages } from './getCustomLanguages.js';
|
|
6
7
|
const NOT_CORRECT_PATH_ERROR = 'must be run in a directory where a docs.json file exists.';
|
|
@@ -18,14 +19,27 @@ export async function updateDocsConfigFile({ contentDirectoryPath, openApiFiles,
|
|
|
18
19
|
throw Error(NOT_CORRECT_PATH_ERROR);
|
|
19
20
|
}
|
|
20
21
|
const customLanguages = await getCustomLanguages({ config: docsConfig, contentDirectoryPath });
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const graphqlResult = await generateGraphqlDivisionsFromDocsConfig(docsConfig, (source) => loadGraphqlSdlSource(source, contentDirectoryPath));
|
|
23
|
+
const { newDocsConfig: docsConfigWithGraphqlPages, pagesAcc: pagesAccWithGraphqlPages } = graphqlResult;
|
|
24
|
+
const { newDocsConfig: docsConfigWithOpenApiPages, pagesAcc: pagesAccWithOpenApiPages, openApiFiles: newOpenApiFiles, flushWrites: flushOpenApiWrites, } = !disableOpenApi
|
|
25
|
+
? await generateOpenApiDivisions(docsConfigWithGraphqlPages, openApiFiles, undefined, localSchema, invalidSpecFiles, pagesAccWithGraphqlPages, true)
|
|
26
|
+
: {
|
|
27
|
+
newDocsConfig: docsConfigWithGraphqlPages,
|
|
28
|
+
pagesAcc: {},
|
|
29
|
+
openApiFiles: openApiFiles,
|
|
30
|
+
flushWrites: async () => { },
|
|
31
|
+
};
|
|
32
|
+
const { newDocsConfig: docsConfigWithAsyncApiPages, pagesAcc: pagesAccWithAsyncApiPages, asyncApiFiles: newAsyncApiFiles, flushWrites: flushAsyncApiWrites, } = await generateAsyncApiDivisions(docsConfigWithOpenApiPages, asyncApiFiles, undefined, localSchema, pagesAccWithGraphqlPages, true, true);
|
|
33
|
+
const pagesAcc = {
|
|
34
|
+
...pagesAccWithOpenApiPages,
|
|
35
|
+
...pagesAccWithAsyncApiPages,
|
|
36
|
+
...pagesAccWithGraphqlPages,
|
|
37
|
+
};
|
|
38
|
+
await Promise.all([flushOpenApiWrites(), flushAsyncApiWrites()]);
|
|
39
|
+
await writeGraphqlArtifacts(graphqlResult);
|
|
40
|
+
await DocsConfigUpdater.writeConfigFile(docsConfigWithAsyncApiPages);
|
|
27
41
|
return {
|
|
28
|
-
docsConfig:
|
|
42
|
+
docsConfig: docsConfigWithAsyncApiPages,
|
|
29
43
|
pagesAcc,
|
|
30
44
|
newOpenApiFiles,
|
|
31
45
|
newAsyncApiFiles,
|
|
@@ -38,3 +52,5 @@ export { getOpenApiFilesFromConfig } from '../read/getOpenApiFilesFromConfig.js'
|
|
|
38
52
|
export { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
39
53
|
export { generateAsyncApiFromDocsConfig } from './generateAsyncApiFromDocsConfig.js';
|
|
40
54
|
export { getCustomLanguages } from './getCustomLanguages.js';
|
|
55
|
+
export { generateGraphqlDivisions, generateGraphqlDivisionsFromDocsConfig, getGraphqlRouteKey, isRemoteGraphqlSource, loadGraphqlSdlSource, loadRemoteGraphqlSdl, writeGraphqlArtifacts, } from './generateGraphqlDivisions.js';
|
|
56
|
+
export { assertNoGeneratedRouteCollisions, getGeneratedRouteKey, } from './generatedRouteCollisions.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { request } from 'node:https';
|
|
2
|
+
import type { LookupFunction } from 'node:net';
|
|
3
|
+
export declare const MAX_GRAPHQL_SCHEMA_BYTES: number;
|
|
4
|
+
export type ResolvedAddress = {
|
|
5
|
+
address: string;
|
|
6
|
+
family: 4 | 6;
|
|
7
|
+
};
|
|
8
|
+
type ResolveHostname = (hostname: string) => Promise<ResolvedAddress[]>;
|
|
9
|
+
type HttpsRequest = typeof request;
|
|
10
|
+
export declare const isGlobalIpAddress: (address: string) => boolean;
|
|
11
|
+
export declare function createPinnedLookup(resolved: ResolvedAddress): LookupFunction;
|
|
12
|
+
export declare function loadRemoteGraphqlSdl(source: string, options?: {
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
maxBytes?: number;
|
|
15
|
+
resolveHostname?: ResolveHostname;
|
|
16
|
+
requestImpl?: HttpsRequest;
|
|
17
|
+
}): Promise<string>;
|
|
18
|
+
export {};
|