@mintlify/prebuild 1.0.1237 → 1.0.1239
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 +72 -54
- package/dist/prebuild/update/docsConfig/generateAsyncApiFromDocsConfig.js +34 -18
- package/dist/prebuild/update/docsConfig/generateOpenApiFromDocsConfig.js +37 -21
- package/dist/prebuild/update/docsConfig/generateSdkDivisions.d.ts +39 -3
- package/dist/prebuild/update/docsConfig/generateSdkDivisions.js +432 -48
- package/dist/prebuild/update/docsConfig/index.d.ts +11 -2
- package/dist/prebuild/update/docsConfig/index.js +53 -3
- package/dist/prebuild/update/index.d.ts +72 -54
- package/dist/prebuild/update/index.js +20 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseFrontmatter, parseSdkTargetFromMetadata, potentiallyParseSdkString, } from '@mintlify/common';
|
|
2
|
+
import { SDK_FORMATS, SDK_SYMBOL_KINDS, } from '@mintlify/models';
|
|
3
|
+
import { generateSdkReference, renderSdkTarget, } from '@mintlify/scraping';
|
|
2
4
|
import { Unzip, UnzipInflate } from 'fflate';
|
|
3
5
|
import fse from 'fs-extra';
|
|
6
|
+
import yaml from 'js-yaml';
|
|
4
7
|
import os from 'node:os';
|
|
5
8
|
import path from 'node:path';
|
|
6
9
|
import { getGeneratedRouteKey } from './generatedRouteCollisions.js';
|
|
@@ -10,9 +13,108 @@ const REMOTE_ARTIFACT_LABEL = 'SDK artifact';
|
|
|
10
13
|
const MAX_REMOTE_SDK_ARTIFACT_BYTES = 50 * 1024 * 1024;
|
|
11
14
|
const MAX_EXTRACTED_SDK_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
12
15
|
const REMOTE_SDK_ARTIFACT_TIMEOUT_MS = 30_000;
|
|
13
|
-
|
|
16
|
+
function isSdkConfig(value) {
|
|
17
|
+
if (typeof value !== 'object' || value === null)
|
|
18
|
+
return false;
|
|
19
|
+
const record = value;
|
|
20
|
+
return typeof record.format === 'string' && typeof record.source === 'string';
|
|
21
|
+
}
|
|
22
|
+
function collectPageSlugs(value, slugs = [], atRoot = false) {
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
slugs.push(value.replace(/^\//, '').replace(/\.mdx?$/, ''));
|
|
25
|
+
return slugs;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (const entry of value)
|
|
29
|
+
collectPageSlugs(entry, slugs);
|
|
30
|
+
return slugs;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== 'object' || value === null)
|
|
33
|
+
return slugs;
|
|
34
|
+
const node = value;
|
|
35
|
+
// Pages under a nested sdk owner belong to that owner's skip-bulk decision.
|
|
36
|
+
if (!atRoot && 'sdk' in node && isSdkConfig(node.sdk))
|
|
37
|
+
return slugs;
|
|
38
|
+
if ('root' in node)
|
|
39
|
+
collectPageSlugs(node.root, slugs);
|
|
40
|
+
if ('pages' in node)
|
|
41
|
+
collectPageSlugs(node.pages, slugs);
|
|
42
|
+
if ('groups' in node)
|
|
43
|
+
collectPageSlugs(node.groups, slugs);
|
|
44
|
+
for (const key of ['tabs', 'anchors', 'dropdowns', 'versions', 'languages', 'products', 'menu']) {
|
|
45
|
+
if (key in node)
|
|
46
|
+
collectPageSlugs(node[key], slugs);
|
|
47
|
+
}
|
|
48
|
+
return slugs;
|
|
49
|
+
}
|
|
50
|
+
function hasExplicitSdkPages(node, pageMetadataBySlug, inherited) {
|
|
51
|
+
if (!pageMetadataBySlug)
|
|
52
|
+
return false;
|
|
53
|
+
return collectPageSlugs(node, [], true).some((slug) => {
|
|
54
|
+
const metadata = pageMetadataBySlug[slug] ?? pageMetadataBySlug[`/${slug}`];
|
|
55
|
+
return parseSdkTargetFromMetadata({ sdk: metadata?.sdk }, inherited) !== undefined;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function navigationHasSdkConfig(value) {
|
|
59
|
+
if (Array.isArray(value))
|
|
60
|
+
return value.some(navigationHasSdkConfig);
|
|
61
|
+
if (typeof value !== 'object' || value === null)
|
|
62
|
+
return false;
|
|
63
|
+
if (('tab' in value || 'group' in value) &&
|
|
64
|
+
'sdk' in value &&
|
|
65
|
+
typeof value.sdk === 'object' &&
|
|
66
|
+
value.sdk !== null) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return Object.values(value).some(navigationHasSdkConfig);
|
|
70
|
+
}
|
|
71
|
+
function toGeneratedPage(page, config, directory, referenceSlugs, autogeneratedBySdk) {
|
|
72
|
+
assertSafeSlug(page.slug, config);
|
|
73
|
+
const slug = path.posix.join(directory, page.slug);
|
|
74
|
+
const sdk = page.target
|
|
75
|
+
? {
|
|
76
|
+
format: config.format,
|
|
77
|
+
source: config.source,
|
|
78
|
+
kind: page.target.kind,
|
|
79
|
+
name: page.target.name,
|
|
80
|
+
...(page.target.parent ? { parent: page.target.parent } : {}),
|
|
81
|
+
}
|
|
82
|
+
: undefined;
|
|
83
|
+
return {
|
|
84
|
+
slug,
|
|
85
|
+
title: page.title,
|
|
86
|
+
...(page.description ? { description: page.description } : {}),
|
|
87
|
+
...(page.tag ? { tag: page.tag } : {}),
|
|
88
|
+
content: prefixInternalLinks(stripLeadingDescription(page.content, page.description), directory, referenceSlugs),
|
|
89
|
+
format: config.format,
|
|
90
|
+
source: config.source,
|
|
91
|
+
...(sdk ? { sdk } : {}),
|
|
92
|
+
...(autogeneratedBySdk ? { autogeneratedBySdk: true } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function expandSdkGroups(reference, directory) {
|
|
96
|
+
const pageBySlug = new Map(reference.pages.map((page) => [page.slug, page]));
|
|
97
|
+
const groups = reference.groups.map((group) => ({
|
|
98
|
+
group: group.group,
|
|
99
|
+
pages: group.pages.map((slug) => path.posix.join(directory, slug)),
|
|
100
|
+
}));
|
|
101
|
+
const orphanedSlugs = reference.pages
|
|
102
|
+
.filter((page) => !page.target?.parent)
|
|
103
|
+
.map((page) => page.slug)
|
|
104
|
+
.filter((slug) => !reference.groups.some((group) => group.pages.includes(slug)));
|
|
105
|
+
if (orphanedSlugs.length > 0 && pageBySlug.size > 0) {
|
|
106
|
+
groups.push({
|
|
107
|
+
group: 'Reference',
|
|
108
|
+
pages: orphanedSlugs.map((slug) => path.posix.join(directory, slug)),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return groups;
|
|
112
|
+
}
|
|
113
|
+
export async function generateSdkDivisionsFromDocsConfig(docsConfig, loadReference, options = {}) {
|
|
14
114
|
const references = new Map();
|
|
15
115
|
const pagesByRouteKey = new Map();
|
|
116
|
+
const inheritedSdkBySlug = {};
|
|
117
|
+
const { pageMetadataBySlug } = options;
|
|
16
118
|
function load(config) {
|
|
17
119
|
const key = `${config.format}:${config.source}`;
|
|
18
120
|
let pending = references.get(key);
|
|
@@ -22,57 +124,104 @@ export async function generateSdkDivisionsFromDocsConfig(docsConfig, loadReferen
|
|
|
22
124
|
}
|
|
23
125
|
return pending;
|
|
24
126
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
127
|
+
function rememberGenerated(generatedPage) {
|
|
128
|
+
const routeKey = getGeneratedRouteKey(generatedPage.slug);
|
|
129
|
+
const existingPage = pagesByRouteKey.get(routeKey);
|
|
130
|
+
if (existingPage &&
|
|
131
|
+
(existingPage.slug !== generatedPage.slug ||
|
|
132
|
+
existingPage.format !== generatedPage.format ||
|
|
133
|
+
existingPage.source !== generatedPage.source)) {
|
|
134
|
+
throw new Error(`Multiple SDK references generate colliding routes "${existingPage.slug}" and "${generatedPage.slug}"`);
|
|
135
|
+
}
|
|
136
|
+
pagesByRouteKey.set(routeKey, generatedPage);
|
|
137
|
+
}
|
|
138
|
+
async function materializeBulk(config) {
|
|
139
|
+
const directory = config.directory ?? DEFAULT_OUTPUT_DIR;
|
|
140
|
+
const reference = await load(config);
|
|
141
|
+
const topLevelPages = reference.pages.filter((page) => !page.target?.parent);
|
|
142
|
+
const referenceSlugs = new Set(topLevelPages.map((page) => page.slug));
|
|
143
|
+
for (const page of topLevelPages) {
|
|
144
|
+
rememberGenerated(toGeneratedPage(page, config, directory, referenceSlugs, true));
|
|
145
|
+
}
|
|
146
|
+
return { groups: expandSdkGroups({ ...reference, pages: topLevelPages }, directory) };
|
|
147
|
+
}
|
|
148
|
+
async function processPages(pages, inherited) {
|
|
149
|
+
if (!Array.isArray(pages))
|
|
150
|
+
return processNode(pages, inherited);
|
|
151
|
+
return Promise.all(pages.map((entry) => {
|
|
152
|
+
if (typeof entry === 'string') {
|
|
153
|
+
const slug = entry.replace(/^\//, '').replace(/\.mdx?$/, '');
|
|
154
|
+
if (inherited)
|
|
155
|
+
inheritedSdkBySlug[slug] = inherited;
|
|
156
|
+
return entry;
|
|
157
|
+
}
|
|
158
|
+
return processNode(entry, inherited);
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
async function processNode(value, inherited) {
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
return Promise.all(value.map((entry) => processNode(entry, inherited)));
|
|
164
|
+
}
|
|
165
|
+
if (typeof value === 'string') {
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
28
168
|
if (typeof value !== 'object' || value === null)
|
|
29
169
|
return value;
|
|
30
170
|
const node = value;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
171
|
+
const ownSdk = 'sdk' in node && isSdkConfig(node.sdk) ? node.sdk : undefined;
|
|
172
|
+
const nextInherited = ownSdk ?? inherited;
|
|
173
|
+
const isTab = 'tab' in node && typeof node.tab === 'string';
|
|
174
|
+
const isGroup = 'group' in node && typeof node.group === 'string';
|
|
175
|
+
if (isGroup && typeof node.root === 'string' && nextInherited) {
|
|
176
|
+
const slug = node.root.replace(/^\//, '').replace(/\.mdx?$/, '');
|
|
177
|
+
inheritedSdkBySlug[slug] = nextInherited;
|
|
178
|
+
}
|
|
179
|
+
if (ownSdk && (isTab || isGroup)) {
|
|
180
|
+
const skipBulk = hasExplicitSdkPages(node, pageMetadataBySlug, ownSdk);
|
|
181
|
+
if (!skipBulk) {
|
|
182
|
+
const { groups } = await materializeBulk(ownSdk);
|
|
183
|
+
if (isTab) {
|
|
184
|
+
const { sdk: _sdk, ...tab } = node;
|
|
185
|
+
const existingGroups = Array.isArray(tab.groups)
|
|
186
|
+
? (await processNode(tab.groups, ownSdk))
|
|
187
|
+
: [];
|
|
188
|
+
const existingPages = Array.isArray(tab.pages)
|
|
189
|
+
? (await processPages(tab.pages, ownSdk))
|
|
190
|
+
: [];
|
|
191
|
+
const restEntries = await Promise.all(Object.entries(tab)
|
|
192
|
+
.filter(([key]) => key !== 'groups' && key !== 'pages')
|
|
193
|
+
.map(async ([key, entry]) => [key, await processNode(entry, ownSdk)]));
|
|
194
|
+
return {
|
|
195
|
+
...Object.fromEntries(restEntries),
|
|
196
|
+
groups: [
|
|
197
|
+
...existingGroups,
|
|
198
|
+
...(existingPages.length > 0 ? [{ group: 'Overview', pages: existingPages }] : []),
|
|
199
|
+
...groups,
|
|
200
|
+
],
|
|
201
|
+
};
|
|
61
202
|
}
|
|
62
|
-
|
|
203
|
+
const { sdk: _sdk, ...group } = node;
|
|
204
|
+
const processedPages = Array.isArray(group.pages)
|
|
205
|
+
? (await processPages(group.pages, ownSdk))
|
|
206
|
+
: [];
|
|
207
|
+
return {
|
|
208
|
+
...group,
|
|
209
|
+
pages: [...processedPages, ...groups],
|
|
210
|
+
};
|
|
63
211
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (orphanedSlugs.length > 0 && pageBySlug.size > 0) {
|
|
68
|
-
groups.push({
|
|
69
|
-
group: 'Reference',
|
|
70
|
-
pages: orphanedSlugs.map((slug) => path.posix.join(directory, slug)),
|
|
71
|
-
});
|
|
212
|
+
if (isTab) {
|
|
213
|
+
const { sdk: _sdk, ...tab } = node;
|
|
214
|
+
return processNode(tab, ownSdk);
|
|
72
215
|
}
|
|
73
|
-
|
|
216
|
+
const { sdk: _sdk, ...group } = node;
|
|
217
|
+
return processNode(group, ownSdk);
|
|
74
218
|
}
|
|
75
|
-
return Object.fromEntries(await Promise.all(Object.entries(node).map(async ([key, entry]) => [
|
|
219
|
+
return Object.fromEntries(await Promise.all(Object.entries(node).map(async ([key, entry]) => [
|
|
220
|
+
key,
|
|
221
|
+
key === 'pages'
|
|
222
|
+
? await processPages(entry, nextInherited)
|
|
223
|
+
: await processNode(entry, nextInherited),
|
|
224
|
+
])));
|
|
76
225
|
}
|
|
77
226
|
const navigation = (await processNode(docsConfig.navigation));
|
|
78
227
|
const generatedPages = [...pagesByRouteKey.values()].sort((left, right) => left.slug.localeCompare(right.slug));
|
|
@@ -83,6 +232,7 @@ export async function generateSdkDivisionsFromDocsConfig(docsConfig, loadReferen
|
|
|
83
232
|
title: page.title,
|
|
84
233
|
...(page.description ? { description: page.description } : {}),
|
|
85
234
|
...(page.tag ? { tag: page.tag } : {}),
|
|
235
|
+
...(page.sdk ? { sdk: page.sdk } : {}),
|
|
86
236
|
},
|
|
87
237
|
]));
|
|
88
238
|
return {
|
|
@@ -90,8 +240,121 @@ export async function generateSdkDivisionsFromDocsConfig(docsConfig, loadReferen
|
|
|
90
240
|
pagesAcc,
|
|
91
241
|
generatedPages,
|
|
92
242
|
generatedRoutes: generatedPages.map(({ slug }) => slug),
|
|
243
|
+
inheritedSdkBySlug,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export async function composeSdkFrontmatterPage(args) {
|
|
247
|
+
const target = parseSdkTargetFromMetadata(args.metadata, args.inherited);
|
|
248
|
+
if (!target)
|
|
249
|
+
return undefined;
|
|
250
|
+
const reference = await args.loadReference({
|
|
251
|
+
format: target.format,
|
|
252
|
+
source: target.source,
|
|
253
|
+
});
|
|
254
|
+
let page;
|
|
255
|
+
try {
|
|
256
|
+
page = renderSdkTarget(reference, target);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
console.error(`unable to resolve sdk target for "${args.slug}":`, error);
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
const slug = args.slug.replace(/^\//, '').replace(/\.mdx?$/, '');
|
|
263
|
+
const slugDirectory = path.posix.dirname(slug);
|
|
264
|
+
const directory = args.inherited === undefined
|
|
265
|
+
? slugDirectory === '.'
|
|
266
|
+
? DEFAULT_OUTPUT_DIR
|
|
267
|
+
: slugDirectory
|
|
268
|
+
: (args.inherited.directory ?? DEFAULT_OUTPUT_DIR);
|
|
269
|
+
const referenceSlugs = new Set(reference.pages.map((entry) => entry.slug));
|
|
270
|
+
const generatedBody = prefixInternalLinks(stripLeadingDescription(page.content, page.description), directory, referenceSlugs);
|
|
271
|
+
const authorBody = stripFrontmatter(args.authorContent).trim();
|
|
272
|
+
const content = [authorBody, generatedBody].filter(Boolean).join('\n\n');
|
|
273
|
+
return {
|
|
274
|
+
slug,
|
|
275
|
+
title: typeof args.metadata.title === 'string' && args.metadata.title.length > 0
|
|
276
|
+
? args.metadata.title
|
|
277
|
+
: page.title,
|
|
278
|
+
description: typeof args.metadata.description === 'string' && args.metadata.description.length > 0
|
|
279
|
+
? args.metadata.description
|
|
280
|
+
: page.description,
|
|
281
|
+
tag: typeof args.metadata.tag === 'string' && args.metadata.tag.length > 0
|
|
282
|
+
? args.metadata.tag
|
|
283
|
+
: page.tag,
|
|
284
|
+
content,
|
|
285
|
+
generatedBody,
|
|
286
|
+
format: target.format,
|
|
287
|
+
source: target.source,
|
|
288
|
+
sdk: target,
|
|
93
289
|
};
|
|
94
290
|
}
|
|
291
|
+
function stripFrontmatter(content) {
|
|
292
|
+
if (!content.startsWith('---'))
|
|
293
|
+
return content;
|
|
294
|
+
const end = content.indexOf('\n---', 3);
|
|
295
|
+
if (end === -1)
|
|
296
|
+
return content;
|
|
297
|
+
return content.slice(end + 4).replace(/^\n+/, '');
|
|
298
|
+
}
|
|
299
|
+
function isSdkSymbolKind(value) {
|
|
300
|
+
for (const kind of SDK_SYMBOL_KINDS) {
|
|
301
|
+
if (kind === value)
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
function isSdkFormatName(value) {
|
|
307
|
+
for (const format of SDK_FORMATS) {
|
|
308
|
+
if (format === value)
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
export function isSdkFrontmatterValue(value) {
|
|
314
|
+
if (typeof value === 'string')
|
|
315
|
+
return potentiallyParseSdkString(value) !== undefined;
|
|
316
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
317
|
+
return false;
|
|
318
|
+
if (!('kind' in value) || !('name' in value))
|
|
319
|
+
return false;
|
|
320
|
+
if (typeof value.kind !== 'string' || !isSdkSymbolKind(value.kind))
|
|
321
|
+
return false;
|
|
322
|
+
if (typeof value.name !== 'string' || value.name.length === 0)
|
|
323
|
+
return false;
|
|
324
|
+
if ('format' in value &&
|
|
325
|
+
value.format !== undefined &&
|
|
326
|
+
(typeof value.format !== 'string' || !isSdkFormatName(value.format))) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if ('source' in value &&
|
|
330
|
+
value.source !== undefined &&
|
|
331
|
+
(typeof value.source !== 'string' || value.source.length === 0)) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
export async function readContainedMarkdown(contentRoot, slug) {
|
|
337
|
+
for (const extension of ['.mdx', '.md']) {
|
|
338
|
+
const filePath = path.resolve(contentRoot, `${slug}${extension}`);
|
|
339
|
+
const relativePath = path.relative(contentRoot, filePath);
|
|
340
|
+
if (relativePath.startsWith(`..${path.sep}`) ||
|
|
341
|
+
relativePath === '..' ||
|
|
342
|
+
path.isAbsolute(relativePath)) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (!(await fse.pathExists(filePath)))
|
|
346
|
+
continue;
|
|
347
|
+
const realFilePath = await fse.realpath(filePath);
|
|
348
|
+
const realRelative = path.relative(contentRoot, realFilePath);
|
|
349
|
+
if (realRelative.startsWith(`..${path.sep}`) ||
|
|
350
|
+
realRelative === '..' ||
|
|
351
|
+
path.isAbsolute(realRelative)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
return { content: await fse.readFile(realFilePath, 'utf8'), extension };
|
|
355
|
+
}
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
95
358
|
export const isRemoteSdkSource = (source) => {
|
|
96
359
|
try {
|
|
97
360
|
return new URL(source).protocol === 'https:';
|
|
@@ -252,12 +515,27 @@ function frontmatter(page) {
|
|
|
252
515
|
`title: ${JSON.stringify(page.title)}`,
|
|
253
516
|
...(page.description ? [`description: ${JSON.stringify(page.description)}`] : []),
|
|
254
517
|
...(page.tag ? [`tag: ${JSON.stringify(page.tag)}`] : []),
|
|
518
|
+
...(page.sdk ? [`sdk: ${JSON.stringify(page.sdk)}`] : []),
|
|
255
519
|
];
|
|
256
520
|
return `---\n${fields.join('\n')}\n---\n\n`;
|
|
257
521
|
}
|
|
258
|
-
|
|
522
|
+
/** Everything after this marker in a composed `_props` page is regenerated on each run. */
|
|
523
|
+
const SDK_GENERATED_MARKER = '{/* mintlify:sdk-generated */}';
|
|
524
|
+
function stripSdkGeneratedSection(body) {
|
|
525
|
+
const markerIndex = body.indexOf(SDK_GENERATED_MARKER);
|
|
526
|
+
return (markerIndex === -1 ? body : body.slice(0, markerIndex)).trim();
|
|
527
|
+
}
|
|
528
|
+
function stringifyMarkdownFrontmatter(attributes) {
|
|
529
|
+
const cleaned = {};
|
|
530
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
531
|
+
if (value !== undefined)
|
|
532
|
+
cleaned[key] = value;
|
|
533
|
+
}
|
|
534
|
+
return `---\n${yaml.dump(cleaned, { lineWidth: -1 }).trimEnd()}\n---\n\n`;
|
|
535
|
+
}
|
|
536
|
+
function getContainedSdkPagePath(targetDir, page, extension = '.mdx') {
|
|
259
537
|
const propsDirectory = path.resolve(targetDir ?? '', 'src', '_props');
|
|
260
|
-
const pagePath = path.resolve(propsDirectory, `${page}
|
|
538
|
+
const pagePath = path.resolve(propsDirectory, `${page}${extension}`);
|
|
261
539
|
const relativePath = path.relative(propsDirectory, pagePath);
|
|
262
540
|
if (relativePath.startsWith(`..${path.sep}`) ||
|
|
263
541
|
relativePath === '..' ||
|
|
@@ -266,6 +544,112 @@ function getContainedSdkPagePath(targetDir, page) {
|
|
|
266
544
|
}
|
|
267
545
|
return pagePath;
|
|
268
546
|
}
|
|
547
|
+
export async function writeSdkFrontmatterPages({ contentDirectoryPath, inheritedSdkBySlug, pageMetadataBySlug, loadReference, targetDir, }) {
|
|
548
|
+
const references = new Map();
|
|
549
|
+
const load = (config) => {
|
|
550
|
+
const key = `${config.format}:${config.source}`;
|
|
551
|
+
let pending = references.get(key);
|
|
552
|
+
if (!pending) {
|
|
553
|
+
pending = loadReference(config);
|
|
554
|
+
references.set(key, pending);
|
|
555
|
+
}
|
|
556
|
+
return pending;
|
|
557
|
+
};
|
|
558
|
+
const slugs = new Set([...Object.keys(pageMetadataBySlug), ...Object.keys(inheritedSdkBySlug)]);
|
|
559
|
+
const contentRoot = await fse.realpath(contentDirectoryPath);
|
|
560
|
+
await Promise.all([...slugs].map(async (rawSlug) => {
|
|
561
|
+
const slug = rawSlug.replace(/^\//, '').replace(/\.mdx?$/, '');
|
|
562
|
+
const metadata = pageMetadataBySlug[slug] ?? pageMetadataBySlug[`/${slug}`];
|
|
563
|
+
const inherited = inheritedSdkBySlug[slug] ?? inheritedSdkBySlug[`/${slug}`];
|
|
564
|
+
if (!metadata || parseSdkTargetFromMetadata({ sdk: metadata.sdk }, inherited) === undefined) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const authorFile = await readContainedMarkdown(contentRoot, slug);
|
|
568
|
+
if (!authorFile)
|
|
569
|
+
return;
|
|
570
|
+
const { content: authorContent, extension } = authorFile;
|
|
571
|
+
let title;
|
|
572
|
+
let description;
|
|
573
|
+
let tag;
|
|
574
|
+
try {
|
|
575
|
+
const attributes = parseFrontmatter(authorContent).attributes;
|
|
576
|
+
title = attributes.title;
|
|
577
|
+
description = attributes.description;
|
|
578
|
+
tag = attributes.tag;
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
// keep generated title/description/tag when author frontmatter cannot be parsed
|
|
582
|
+
}
|
|
583
|
+
const composed = await composeSdkFrontmatterPage({
|
|
584
|
+
slug,
|
|
585
|
+
authorContent,
|
|
586
|
+
metadata: {
|
|
587
|
+
sdk: metadata.sdk,
|
|
588
|
+
title,
|
|
589
|
+
description,
|
|
590
|
+
tag,
|
|
591
|
+
},
|
|
592
|
+
loadReference: load,
|
|
593
|
+
inherited,
|
|
594
|
+
});
|
|
595
|
+
if (!composed)
|
|
596
|
+
return;
|
|
597
|
+
const pagePath = getContainedSdkPagePath(targetDir, composed.slug, extension);
|
|
598
|
+
if (!pagePath)
|
|
599
|
+
return;
|
|
600
|
+
let attributes = {};
|
|
601
|
+
let processedBody = stripSdkGeneratedSection(stripFrontmatter(authorContent));
|
|
602
|
+
let usedProcessedOutput = false;
|
|
603
|
+
if (await fse.pathExists(pagePath)) {
|
|
604
|
+
try {
|
|
605
|
+
const existing = await fse.readFile(pagePath, 'utf8');
|
|
606
|
+
attributes = parseFrontmatter(existing).attributes;
|
|
607
|
+
processedBody = stripSdkGeneratedSection(stripFrontmatter(existing));
|
|
608
|
+
usedProcessedOutput = true;
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
try {
|
|
612
|
+
attributes = parseFrontmatter(authorContent).attributes;
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
// keep generated title/description/tag when author frontmatter cannot be parsed
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
try {
|
|
621
|
+
attributes = parseFrontmatter(authorContent).attributes;
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
// keep generated title/description/tag when author frontmatter cannot be parsed
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Raw author content with unresolved imports must not shadow the lazy
|
|
628
|
+
// rendering path; those pages are composed once their processed output exists.
|
|
629
|
+
if (!usedProcessedOutput && /^import\s/m.test(processedBody)) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (composed.sdk)
|
|
633
|
+
attributes.sdk = composed.sdk;
|
|
634
|
+
if (typeof attributes.title !== 'string' || attributes.title.length === 0) {
|
|
635
|
+
attributes.title = composed.title;
|
|
636
|
+
}
|
|
637
|
+
if ((typeof attributes.description !== 'string' || attributes.description.length === 0) &&
|
|
638
|
+
composed.description) {
|
|
639
|
+
attributes.description = composed.description;
|
|
640
|
+
}
|
|
641
|
+
if ((typeof attributes.tag !== 'string' || attributes.tag.length === 0) && composed.tag) {
|
|
642
|
+
attributes.tag = composed.tag;
|
|
643
|
+
}
|
|
644
|
+
const generatedBody = composed.generatedBody ?? composed.content;
|
|
645
|
+
const content = [processedBody, `${SDK_GENERATED_MARKER}\n\n${generatedBody}`]
|
|
646
|
+
.filter(Boolean)
|
|
647
|
+
.join('\n\n');
|
|
648
|
+
await fse.outputFile(pagePath, `${stringifyMarkdownFrontmatter(attributes)}${content}\n`, {
|
|
649
|
+
flag: 'w',
|
|
650
|
+
});
|
|
651
|
+
}));
|
|
652
|
+
}
|
|
269
653
|
export async function writeSdkArtifacts(result, targetDir) {
|
|
270
654
|
const propsDirectory = path.resolve(targetDir ?? '', 'src', '_props');
|
|
271
655
|
const manifestPath = path.join(propsDirectory, 'sdk-data.json');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncAPIFile } from '@mintlify/common';
|
|
2
2
|
import { OpenApiFile, DecoratedNavigationPage } from '@mintlify/models';
|
|
3
|
+
import type { SdkFormat } from '@mintlify/scraping';
|
|
3
4
|
import { DocsConfig } from '@mintlify/validation';
|
|
4
5
|
import type { InvalidSpecFile } from '../../invalidSpecFiles.js';
|
|
5
6
|
export declare function updateDocsConfigFile({ contentDirectoryPath, openApiFiles, asyncApiFiles, docsConfig, localSchema, disableOpenApi, strict, invalidSpecFiles, allowSourceRefs, }: {
|
|
@@ -21,11 +22,19 @@ export declare function updateDocsConfigFile({ contentDirectoryPath, openApiFile
|
|
|
21
22
|
content: string;
|
|
22
23
|
filePath: string;
|
|
23
24
|
}[];
|
|
25
|
+
inheritedSdkBySlug: Record<string, {
|
|
26
|
+
format: SdkFormat;
|
|
27
|
+
source: string;
|
|
28
|
+
directory?: string;
|
|
29
|
+
}>;
|
|
30
|
+
pageMetadataBySlug: Record<string, {
|
|
31
|
+
sdk?: unknown;
|
|
32
|
+
}>;
|
|
24
33
|
}>;
|
|
25
34
|
export { generateOpenApiDivisions } from './generateOpenApiDivisions.js';
|
|
26
35
|
export { generateOpenApiFromDocsConfig } from './generateOpenApiFromDocsConfig.js';
|
|
27
|
-
export { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, writeSdkArtifacts, } from './generateSdkDivisions.js';
|
|
28
|
-
export type { GeneratedSdkPage, GenerateSdkDivisionsResult, SdkReferenceLoader, } from './generateSdkDivisions.js';
|
|
36
|
+
export { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, writeSdkArtifacts, writeSdkFrontmatterPages, composeSdkFrontmatterPage, isSdkFrontmatterValue, navigationHasSdkConfig, } from './generateSdkDivisions.js';
|
|
37
|
+
export type { GeneratedSdkPage, GenerateSdkDivisionsResult, GenerateSdkDivisionsOptions, SdkReferenceLoader, } from './generateSdkDivisions.js';
|
|
29
38
|
export { getOpenApiFilesFromConfig } from '../read/getOpenApiFilesFromConfig.js';
|
|
30
39
|
export { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
31
40
|
export { generateAsyncApiFromDocsConfig } from './generateAsyncApiFromDocsConfig.js';
|
|
@@ -1,12 +1,59 @@
|
|
|
1
|
+
import { parseFrontmatter } from '@mintlify/common';
|
|
2
|
+
import fse from 'fs-extra';
|
|
1
3
|
import { getConfigPath } from '../../../utils.js';
|
|
2
4
|
import { DocsConfigUpdater } from '../ConfigUpdater.js';
|
|
3
5
|
import { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
4
6
|
import { assertNoGeneratedRouteCollisions } from './generatedRouteCollisions.js';
|
|
5
7
|
import { generateGraphqlDivisionsFromDocsConfig, loadGraphqlSdlSource, writeGraphqlArtifacts, } from './generateGraphqlDivisions.js';
|
|
6
8
|
import { generateOpenApiDivisions } from './generateOpenApiDivisions.js';
|
|
7
|
-
import { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, writeSdkArtifacts, } from './generateSdkDivisions.js';
|
|
9
|
+
import { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, readContainedMarkdown, writeSdkArtifacts, } from './generateSdkDivisions.js';
|
|
8
10
|
import { getCustomLanguages } from './getCustomLanguages.js';
|
|
9
11
|
const NOT_CORRECT_PATH_ERROR = 'must be run in a directory where a docs.json file exists.';
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function collectNavigationPageSlugs(value, slugs = new Set()) {
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
slugs.add(value.replace(/^\//, '').replace(/\.mdx?$/, ''));
|
|
18
|
+
return slugs;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
value.forEach((entry) => collectNavigationPageSlugs(entry, slugs));
|
|
22
|
+
return slugs;
|
|
23
|
+
}
|
|
24
|
+
if (!isPlainObject(value))
|
|
25
|
+
return slugs;
|
|
26
|
+
if ('root' in value)
|
|
27
|
+
collectNavigationPageSlugs(value.root, slugs);
|
|
28
|
+
if ('pages' in value)
|
|
29
|
+
collectNavigationPageSlugs(value.pages, slugs);
|
|
30
|
+
if ('groups' in value)
|
|
31
|
+
collectNavigationPageSlugs(value.groups, slugs);
|
|
32
|
+
for (const key of ['tabs', 'anchors', 'dropdowns', 'versions', 'languages', 'products', 'menu']) {
|
|
33
|
+
if (key in value)
|
|
34
|
+
collectNavigationPageSlugs(value[key], slugs);
|
|
35
|
+
}
|
|
36
|
+
return slugs;
|
|
37
|
+
}
|
|
38
|
+
async function scanSdkFrontmatterMetadata(contentDirectoryPath, navigation) {
|
|
39
|
+
const contentRoot = await fse.realpath(contentDirectoryPath);
|
|
40
|
+
const metadataBySlug = {};
|
|
41
|
+
await Promise.all([...collectNavigationPageSlugs(navigation)].map(async (slug) => {
|
|
42
|
+
try {
|
|
43
|
+
const file = await readContainedMarkdown(contentRoot, slug);
|
|
44
|
+
if (file === undefined)
|
|
45
|
+
return;
|
|
46
|
+
const { attributes } = parseFrontmatter(file.content);
|
|
47
|
+
if (attributes.sdk !== undefined) {
|
|
48
|
+
metadataBySlug[slug] = { sdk: attributes.sdk };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore unreadable/invalid frontmatter during scan
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
return metadataBySlug;
|
|
56
|
+
}
|
|
10
57
|
export async function updateDocsConfigFile({ contentDirectoryPath, openApiFiles, asyncApiFiles, docsConfig, localSchema, disableOpenApi, strict, invalidSpecFiles, allowSourceRefs, }) {
|
|
11
58
|
const configPath = await getConfigPath(contentDirectoryPath, 'docs');
|
|
12
59
|
if (configPath == null && docsConfig == null) {
|
|
@@ -32,7 +79,8 @@ export async function updateDocsConfigFile({ contentDirectoryPath, openApiFiles,
|
|
|
32
79
|
flushWrites: async () => { },
|
|
33
80
|
};
|
|
34
81
|
const { newDocsConfig: docsConfigWithAsyncApiPages, pagesAcc: pagesAccWithAsyncApiPages, asyncApiFiles: newAsyncApiFiles, flushWrites: flushAsyncApiWrites, } = await generateAsyncApiDivisions(docsConfigWithOpenApiPages, asyncApiFiles, undefined, localSchema, pagesAccWithGraphqlPages, true, true);
|
|
35
|
-
const
|
|
82
|
+
const pageMetadataBySlug = await scanSdkFrontmatterMetadata(contentDirectoryPath, docsConfigWithAsyncApiPages.navigation);
|
|
83
|
+
const sdkResult = await generateSdkDivisionsFromDocsConfig(docsConfigWithAsyncApiPages, (config) => loadSdkReferenceSource(config, contentDirectoryPath), { pageMetadataBySlug });
|
|
36
84
|
const { newDocsConfig: docsConfigWithSdkPages, pagesAcc: pagesAccWithSdkPages } = sdkResult;
|
|
37
85
|
const sdkRoutes = { label: 'SDK', pages: pagesAccWithSdkPages };
|
|
38
86
|
assertNoGeneratedRouteCollisions({ label: 'GraphQL', pages: pagesAccWithGraphqlPages }, sdkRoutes);
|
|
@@ -54,11 +102,13 @@ export async function updateDocsConfigFile({ contentDirectoryPath, openApiFiles,
|
|
|
54
102
|
newOpenApiFiles,
|
|
55
103
|
newAsyncApiFiles,
|
|
56
104
|
customLanguages,
|
|
105
|
+
inheritedSdkBySlug: sdkResult.inheritedSdkBySlug,
|
|
106
|
+
pageMetadataBySlug,
|
|
57
107
|
};
|
|
58
108
|
}
|
|
59
109
|
export { generateOpenApiDivisions } from './generateOpenApiDivisions.js';
|
|
60
110
|
export { generateOpenApiFromDocsConfig } from './generateOpenApiFromDocsConfig.js';
|
|
61
|
-
export { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, writeSdkArtifacts, } from './generateSdkDivisions.js';
|
|
111
|
+
export { generateSdkDivisionsFromDocsConfig, loadSdkReferenceSource, writeSdkArtifacts, writeSdkFrontmatterPages, composeSdkFrontmatterPage, isSdkFrontmatterValue, navigationHasSdkConfig, } from './generateSdkDivisions.js';
|
|
62
112
|
export { getOpenApiFilesFromConfig } from '../read/getOpenApiFilesFromConfig.js';
|
|
63
113
|
export { generateAsyncApiDivisions } from './generateAsyncApiDivisions.js';
|
|
64
114
|
export { generateAsyncApiFromDocsConfig } from './generateAsyncApiFromDocsConfig.js';
|