@mintlify/prebuild 1.0.1284 → 1.0.1286

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.
@@ -1,16 +1,23 @@
1
- import { AsyncAPIFile } from '@mintlify/common';
1
+ import { AsyncAPIFile, type DiscoveredOverlay, type OverlayRegistry } from '@mintlify/common';
2
2
  import { OpenApiFile } from '@mintlify/models';
3
+ import { type ExplicitOverlayConfig } from '@mintlify/validation';
3
4
  import type { InvalidSpecFile } from './invalidSpecFiles.js';
4
5
  export type PageScan = {
5
6
  frontmatter: string;
6
7
  rssCandidate: boolean;
7
8
  };
8
- export declare const categorizeFilePaths: (contentDirectoryPath: string, mintIgnore?: string[], disableOpenApi?: boolean, lazyPages?: boolean) => Promise<{
9
+ export type CategorizeOverlayOptions = {
10
+ explicitOverlays?: ExplicitOverlayConfig;
11
+ localSchema?: boolean;
12
+ };
13
+ export declare const categorizeFilePaths: (contentDirectoryPath: string, mintIgnore?: string[], disableOpenApi?: boolean, lazyPages?: boolean, overlayOptions?: CategorizeOverlayOptions) => Promise<{
9
14
  contentFilenames: string[];
10
15
  staticFilenames: string[];
11
16
  openApiFiles: OpenApiFile[];
12
17
  asyncApiFiles: AsyncAPIFile[];
13
18
  invalidSpecFiles: InvalidSpecFile[];
19
+ overlayFiles: DiscoveredOverlay[];
20
+ overlayRegistry: OverlayRegistry;
14
21
  snippets: string[];
15
22
  snippetsV2: string[];
16
23
  fileImportsMap: Map<string, Set<string>>;
@@ -1,4 +1,5 @@
1
- import { validate, validateAsyncApi, extractImportSources, getFileCategory, isSnippetExtension, getAST, resolveImportPath, } from '@mintlify/common';
1
+ import { validate, validateAsyncApi, applyOverlays, createOverlayRegistry, extractImportSources, getFileCategory, isSnippetExtension, getAST, parseLoadedOverlayDocument, resolveExtends, resolveOverlaysForSpec, normalizeOverlayKey, resolveImportPath, } from '@mintlify/common';
2
+ import { isOverlayDocument, validatePathWithinCwd, } from '@mintlify/validation';
2
3
  import { readFile } from 'fs/promises';
3
4
  import yaml from 'js-yaml';
4
5
  import * as path from 'path';
@@ -61,7 +62,7 @@ const getImportSources = (content, filePath) => {
61
62
  return sources;
62
63
  }
63
64
  };
64
- export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [], disableOpenApi, lazyPages) => {
65
+ export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [], disableOpenApi, lazyPages, overlayOptions) => {
65
66
  const allFilenames = [];
66
67
  for await (const filename of getFileList(contentDirectoryPath, contentDirectoryPath, mintIgnore)) {
67
68
  allFilenames.push(filename);
@@ -140,6 +141,10 @@ export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [],
140
141
  const openApiFiles = [];
141
142
  const asyncApiFiles = [];
142
143
  const invalidSpecFiles = [];
144
+ const overlayFiles = [];
145
+ const openApiCandidates = [];
146
+ // Overlays can only be resolved once every file is known, so OpenAPI candidates are
147
+ // buffered and processed after the walk.
143
148
  for (const { filename, extension } of nonMdxFiles) {
144
149
  switch (extension) {
145
150
  case 'json':
@@ -161,27 +166,32 @@ export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [],
161
166
  }
162
167
  if (!obj || typeof obj !== 'object')
163
168
  break;
164
- const isOpenApi = Object.keys(obj).includes('openapi');
165
- const isAsyncApi = Object.keys(obj).includes('asyncapi');
166
- const fileName = path.parse(filename).name;
167
- if (isOpenApi && !disableOpenApi) {
169
+ if (isOverlayDocument(obj)) {
170
+ const location = normalizeOverlayKey(filename);
168
171
  try {
169
- const { schema: openApiDocument } = await validate(obj);
170
- if (openApiDocument) {
171
- openApiFiles.push({
172
- filename: fileName,
173
- spec: obj,
174
- originalFileLocation: filename,
175
- });
176
- }
172
+ const document = parseLoadedOverlayDocument(obj, location);
173
+ overlayFiles.push({
174
+ location,
175
+ document,
176
+ resolvedExtends: document.extends == undefined
177
+ ? undefined
178
+ : resolveExtends(location, document.extends),
179
+ });
177
180
  }
178
181
  catch (error) {
179
182
  invalidSpecFiles.push({
180
183
  originalFileLocation: filename,
181
184
  reason: error instanceof Error ? error.message : String(error),
182
- kind: 'openapi',
185
+ kind: 'overlay',
183
186
  });
184
187
  }
188
+ break;
189
+ }
190
+ const isOpenApi = Object.keys(obj).includes('openapi');
191
+ const isAsyncApi = Object.keys(obj).includes('asyncapi');
192
+ const fileName = path.parse(filename).name;
193
+ if (isOpenApi && !disableOpenApi) {
194
+ openApiCandidates.push({ filename, fileName, obj, str });
185
195
  }
186
196
  if (isAsyncApi) {
187
197
  try {
@@ -207,12 +217,48 @@ export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [],
207
217
  staticFilenames.push(filename);
208
218
  }
209
219
  }
220
+ const overlayRegistry = createOverlayRegistry({
221
+ explicit: overlayOptions?.explicitOverlays,
222
+ discovered: overlayFiles,
223
+ localSchema: overlayOptions?.localSchema,
224
+ loadLocalDocument: async (normalizedPath) => {
225
+ const { resolvedPath } = validatePathWithinCwd(normalizedPath, contentDirectoryPath);
226
+ const contents = await readFile(resolvedPath, 'utf8');
227
+ return yaml.load(contents);
228
+ },
229
+ });
230
+ for (const { filename, fileName, obj } of openApiCandidates) {
231
+ try {
232
+ const overlays = await resolveOverlaysForSpec(overlayRegistry, filename);
233
+ const overlaid = overlays.length > 0 ? applyOverlays(obj, overlays) : obj;
234
+ if (overlaid == undefined || typeof overlaid !== 'object') {
235
+ throw new Error('Applying overlays produced an invalid OpenAPI document');
236
+ }
237
+ const { schema: openApiDocument } = await validate(overlaid);
238
+ if (openApiDocument) {
239
+ openApiFiles.push({
240
+ filename: fileName,
241
+ spec: overlaid,
242
+ originalFileLocation: filename,
243
+ });
244
+ }
245
+ }
246
+ catch (error) {
247
+ invalidSpecFiles.push({
248
+ originalFileLocation: filename,
249
+ reason: error instanceof Error ? error.message : String(error),
250
+ kind: 'openapi',
251
+ });
252
+ }
253
+ }
210
254
  return {
211
255
  contentFilenames,
212
256
  staticFilenames,
213
257
  openApiFiles,
214
258
  asyncApiFiles,
215
259
  invalidSpecFiles,
260
+ overlayFiles,
261
+ overlayRegistry,
216
262
  snippets,
217
263
  snippetsV2,
218
264
  fileImportsMap,
@@ -0,0 +1,12 @@
1
+ import { type DiscoveredOverlay, type OverlayRegistry } from '@mintlify/common';
2
+ import { type ExplicitOverlayConfig } from '@mintlify/validation';
3
+ /** Walks a docs directory and returns every valid OpenAPI Overlay document. */
4
+ export declare const discoverOverlayFiles: (contentDirectoryPath: string, mintIgnore?: string[]) => Promise<DiscoveredOverlay[]>;
5
+ /** Builds a filesystem-backed overlay registry for a docs directory. */
6
+ export declare const buildOverlayRegistryForDirectory: (contentDirectoryPath: string, { explicitOverlays, mintIgnore, localSchema, fetchRemoteDocument, }?: {
7
+ explicitOverlays?: ExplicitOverlayConfig;
8
+ mintIgnore?: string[];
9
+ localSchema?: boolean;
10
+ /** SSRF-safe fetch override for overlay URLs; supplied by server environments. */
11
+ fetchRemoteDocument?: (url: string) => Promise<unknown>;
12
+ }) => Promise<OverlayRegistry>;
@@ -0,0 +1,56 @@
1
+ import { createOverlayRegistry, normalizeOverlayKey, parseLoadedOverlayDocument, resolveExtends, } from '@mintlify/common';
2
+ import { isOverlayDocument, validatePathWithinCwd, } from '@mintlify/validation';
3
+ import { readFile } from 'fs/promises';
4
+ import yaml from 'js-yaml';
5
+ import * as path from 'path';
6
+ import { getFileList } from '../fs/index.js';
7
+ import { getFileExtension } from '../utils.js';
8
+ import { addWarning } from './warnings.js';
9
+ /** Walks a docs directory and returns every valid OpenAPI Overlay document. */
10
+ export const discoverOverlayFiles = async (contentDirectoryPath, mintIgnore = []) => {
11
+ const overlayFiles = [];
12
+ for await (const filename of getFileList(contentDirectoryPath, contentDirectoryPath, mintIgnore)) {
13
+ const extension = getFileExtension(filename);
14
+ if (extension !== 'json' && extension !== 'yaml' && extension !== 'yml')
15
+ continue;
16
+ let obj;
17
+ try {
18
+ obj = yaml.load(await readFile(path.join(contentDirectoryPath, filename), 'utf8'));
19
+ }
20
+ catch {
21
+ continue;
22
+ }
23
+ if (!isOverlayDocument(obj))
24
+ continue;
25
+ const location = normalizeOverlayKey(filename);
26
+ try {
27
+ const document = parseLoadedOverlayDocument(obj, location);
28
+ overlayFiles.push({
29
+ location,
30
+ document,
31
+ resolvedExtends: document.extends == undefined ? undefined : resolveExtends(location, document.extends),
32
+ });
33
+ }
34
+ catch (error) {
35
+ addWarning({
36
+ type: 'openapi',
37
+ message: `Error validating OpenAPI Overlay file ${filename}: ${error}`,
38
+ });
39
+ }
40
+ }
41
+ return overlayFiles;
42
+ };
43
+ /** Builds a filesystem-backed overlay registry for a docs directory. */
44
+ export const buildOverlayRegistryForDirectory = async (contentDirectoryPath, { explicitOverlays, mintIgnore, localSchema, fetchRemoteDocument, } = {}) => {
45
+ const discovered = await discoverOverlayFiles(contentDirectoryPath, mintIgnore);
46
+ return createOverlayRegistry({
47
+ explicit: explicitOverlays,
48
+ discovered,
49
+ fetchRemoteDocument,
50
+ localSchema,
51
+ loadLocalDocument: async (normalizedPath) => {
52
+ const { resolvedPath } = validatePathWithinCwd(normalizedPath, contentDirectoryPath);
53
+ return yaml.load(await readFile(resolvedPath, 'utf8'));
54
+ },
55
+ });
56
+ };
@@ -1,6 +1,11 @@
1
1
  import { OpenApiFile } from '@mintlify/models';
2
+ import { type ExplicitOverlayConfig } from '@mintlify/validation';
2
3
  export interface GetOpenApiFilesOptions {
3
4
  mintIgnore?: string[];
4
5
  disableOpenApi?: boolean;
6
+ explicitOverlays?: ExplicitOverlayConfig;
7
+ localSchema?: boolean;
8
+ /** SSRF-safe fetch override for overlay URLs; supplied by server environments. */
9
+ fetchRemoteDocument?: (url: string) => Promise<unknown>;
5
10
  }
6
- export declare const getOpenApiFiles: (contentDirectoryPath: string, { mintIgnore, disableOpenApi }?: GetOpenApiFilesOptions) => Promise<OpenApiFile[]>;
11
+ export declare const getOpenApiFiles: (contentDirectoryPath: string, { mintIgnore, disableOpenApi, explicitOverlays, localSchema, fetchRemoteDocument, }?: GetOpenApiFilesOptions) => Promise<OpenApiFile[]>;
@@ -1,15 +1,18 @@
1
- import { validate } from '@mintlify/common';
1
+ import { applyOverlays, createOverlayRegistry, normalizeOverlayKey, parseLoadedOverlayDocument, resolveExtends, resolveOverlaysForSpec, validate, } from '@mintlify/common';
2
+ import { isOverlayDocument, validatePathWithinCwd, } from '@mintlify/validation';
2
3
  import { readFile } from 'fs/promises';
3
4
  import yaml from 'js-yaml';
4
5
  import * as path from 'path';
5
6
  import { getFileList } from '../fs/index.js';
6
7
  import { getFileExtension } from '../utils.js';
7
8
  import { addWarning } from './warnings.js';
8
- export const getOpenApiFiles = async (contentDirectoryPath, { mintIgnore = [], disableOpenApi = false } = {}) => {
9
+ export const getOpenApiFiles = async (contentDirectoryPath, { mintIgnore = [], disableOpenApi = false, explicitOverlays, localSchema, fetchRemoteDocument, } = {}) => {
9
10
  if (disableOpenApi)
10
11
  return [];
11
12
  const allFiles = getFileList(contentDirectoryPath, contentDirectoryPath, mintIgnore);
12
13
  const openApiFiles = [];
14
+ const overlayFiles = [];
15
+ const candidates = [];
13
16
  for await (const filename of allFiles) {
14
17
  const extension = getFileExtension(filename);
15
18
  if (extension !== 'json' && extension !== 'yaml' && extension !== 'yml')
@@ -19,14 +22,51 @@ export const getOpenApiFiles = async (contentDirectoryPath, { mintIgnore = [], d
19
22
  const obj = yaml.load(str);
20
23
  if (!obj || typeof obj !== 'object')
21
24
  continue;
25
+ if (isOverlayDocument(obj)) {
26
+ const location = normalizeOverlayKey(filename);
27
+ try {
28
+ const document = parseLoadedOverlayDocument(obj, location);
29
+ overlayFiles.push({
30
+ location,
31
+ document,
32
+ resolvedExtends: document.extends == undefined ? undefined : resolveExtends(location, document.extends),
33
+ });
34
+ }
35
+ catch (error) {
36
+ addWarning({
37
+ type: 'openapi',
38
+ message: `Error validating OpenAPI Overlay file ${filename}: ${error}`,
39
+ });
40
+ }
41
+ continue;
42
+ }
22
43
  if (!Object.keys(obj).includes('openapi'))
23
44
  continue;
45
+ candidates.push({ filename, obj });
46
+ }
47
+ const overlayRegistry = createOverlayRegistry({
48
+ explicit: explicitOverlays,
49
+ discovered: overlayFiles,
50
+ localSchema,
51
+ fetchRemoteDocument,
52
+ loadLocalDocument: async (normalizedPath) => {
53
+ const { resolvedPath } = validatePathWithinCwd(normalizedPath, contentDirectoryPath);
54
+ const contents = await readFile(resolvedPath, 'utf8');
55
+ return yaml.load(contents);
56
+ },
57
+ });
58
+ for (const { filename, obj } of candidates) {
24
59
  try {
25
- const { schema: openApiDocument } = await validate(obj);
60
+ const overlays = await resolveOverlaysForSpec(overlayRegistry, filename);
61
+ const overlaid = overlays.length > 0 ? applyOverlays(obj, overlays) : obj;
62
+ if (overlaid == undefined || typeof overlaid !== 'object') {
63
+ throw new Error('Applying overlays produced an invalid OpenAPI document');
64
+ }
65
+ const { schema: openApiDocument } = await validate(overlaid);
26
66
  if (openApiDocument) {
27
67
  openApiFiles.push({
28
68
  filename: path.parse(filename).name,
29
- spec: obj,
69
+ spec: overlaid,
30
70
  originalFileLocation: filename,
31
71
  });
32
72
  }
@@ -1,3 +1,10 @@
1
+ import { type ExplicitOverlayConfig } from '@mintlify/validation';
2
+ /**
3
+ * Collects explicit overlay config from the raw docs.json/mint.json, resolving config
4
+ * $refs first so overlays declared in referenced fragments are included. Unreadable
5
+ * config only warns (regular validation reports it); conflicting overlay lists throw.
6
+ */
7
+ export declare const readExplicitOverlayConfig: (configPath: string | null | undefined) => Promise<ExplicitOverlayConfig | undefined>;
1
8
  export interface PrebuildResult {
2
9
  fileImportsMap: Map<string, Set<string>>;
3
10
  }
@@ -11,6 +18,7 @@ export declare const prebuild: (contentDirectoryPath: string, { localSchema, gro
11
18
  lazyPages?: boolean;
12
19
  }) => Promise<PrebuildResult | undefined>;
13
20
  export * from './categorizeFilePaths.js';
21
+ export * from './discoverOverlayFiles.js';
14
22
  export * from './getOpenApiFiles.js';
15
23
  export * from './invalidSpecFiles.js';
16
24
  export * from '../createPage/index.js';
@@ -1,8 +1,39 @@
1
+ import { collectOpenApiOverlayConfig } from '@mintlify/validation';
2
+ import { readFile } from 'fs/promises';
3
+ import * as path from 'path';
1
4
  import { getConfigPath, getMintIgnore } from '../utils.js';
2
5
  import { categorizeFilePaths } from './categorizeFilePaths.js';
3
6
  import { warnInvalidSpecFiles } from './invalidSpecFiles.js';
7
+ import { resolveFileRefs } from './resolveRefs.js';
4
8
  import { update } from './update/index.js';
5
- import { clearWarnings, checkStrictMode } from './warnings.js';
9
+ import { addWarning, clearWarnings, checkStrictMode } from './warnings.js';
10
+ /**
11
+ * Collects explicit overlay config from the raw docs.json/mint.json, resolving config
12
+ * $refs first so overlays declared in referenced fragments are included. Unreadable
13
+ * config only warns (regular validation reports it); conflicting overlay lists throw.
14
+ */
15
+ export const readExplicitOverlayConfig = async (configPath) => {
16
+ if (configPath == null)
17
+ return undefined;
18
+ let rawConfig;
19
+ try {
20
+ rawConfig = JSON.parse(await readFile(configPath, 'utf8'));
21
+ }
22
+ catch (error) {
23
+ addWarning({
24
+ type: 'openapi',
25
+ message: `Error reading OpenAPI overlay configuration from ${configPath}: ${error instanceof Error ? error.message : String(error)}`,
26
+ });
27
+ return undefined;
28
+ }
29
+ try {
30
+ rawConfig = (await resolveFileRefs(rawConfig, path.dirname(configPath))).resolved;
31
+ }
32
+ catch {
33
+ // Broken $refs are reported by regular config validation; collect from the root file.
34
+ }
35
+ return collectOpenApiOverlayConfig(rawConfig);
36
+ };
6
37
  export const prebuild = async (contentDirectoryPath, { localSchema, groups, disableOpenApi, disablePrefetch, strict, allowSourceRefs, lazyPages, } = {}) => {
7
38
  if (process.env.IS_MULTI_TENANT === 'true') {
8
39
  console.log('Skipping prebuild in multi-tenant mode.');
@@ -16,7 +47,11 @@ export const prebuild = async (contentDirectoryPath, { localSchema, groups, disa
16
47
  throw Error('must be run in a directory where a docs.json file exists.');
17
48
  }
18
49
  const mintIgnore = await getMintIgnore(contentDirectoryPath);
19
- const { contentFilenames, staticFilenames, openApiFiles, asyncApiFiles, invalidSpecFiles, snippets, snippetsV2, fileImportsMap, pageScans, } = await categorizeFilePaths(contentDirectoryPath, mintIgnore, disableOpenApi, lazyPages);
50
+ const explicitOverlays = await readExplicitOverlayConfig(docsConfigPath ?? mintConfigPath);
51
+ const { contentFilenames, staticFilenames, openApiFiles, asyncApiFiles, invalidSpecFiles, overlayRegistry, snippets, snippetsV2, fileImportsMap, pageScans, } = await categorizeFilePaths(contentDirectoryPath, mintIgnore, disableOpenApi, lazyPages, {
52
+ explicitOverlays,
53
+ localSchema,
54
+ });
20
55
  await update({
21
56
  contentDirectoryPath,
22
57
  staticFilenames,
@@ -37,6 +72,7 @@ export const prebuild = async (contentDirectoryPath, { localSchema, groups, disa
37
72
  lazyPages,
38
73
  pageScans,
39
74
  fileImportsMap,
75
+ overlayRegistry,
40
76
  });
41
77
  // Deferred until after update so files that fail the build don't also warn
42
78
  warnInvalidSpecFiles(invalidSpecFiles);
@@ -45,6 +81,7 @@ export const prebuild = async (contentDirectoryPath, { localSchema, groups, disa
45
81
  return { fileImportsMap };
46
82
  };
47
83
  export * from './categorizeFilePaths.js';
84
+ export * from './discoverOverlayFiles.js';
48
85
  export * from './getOpenApiFiles.js';
49
86
  export * from './invalidSpecFiles.js';
50
87
  export * from '../createPage/index.js';
@@ -1,7 +1,7 @@
1
1
  export type InvalidSpecFile = {
2
2
  originalFileLocation: string;
3
3
  reason: string;
4
- kind: 'openapi' | 'asyncapi' | 'syntax';
4
+ kind: 'openapi' | 'asyncapi' | 'syntax' | 'overlay';
5
5
  };
6
6
  export declare const findInvalidSpecFile: (invalidSpecFiles: InvalidSpecFile[] | undefined, reference: string) => InvalidSpecFile | undefined;
7
7
  export declare const warnInvalidSpecFiles: (invalidSpecFiles: InvalidSpecFile[] | undefined) => void;
@@ -7,7 +7,11 @@ export const warnInvalidSpecFiles = (invalidSpecFiles) => {
7
7
  type: 'openapi',
8
8
  message: file.kind === 'syntax'
9
9
  ? `Error parsing ${file.originalFileLocation}: ${file.reason}`
10
- : `Error validating ${file.kind === 'asyncapi' ? 'AsyncAPI' : 'OpenAPI'} file ${file.originalFileLocation}: ${file.reason}`,
10
+ : `Error validating ${file.kind === 'asyncapi'
11
+ ? 'AsyncAPI'
12
+ : file.kind === 'overlay'
13
+ ? 'OpenAPI Overlay'
14
+ : 'OpenAPI'} file ${file.originalFileLocation}: ${file.reason}`,
11
15
  });
12
16
  }
13
17
  };