@mintlify/previewing 4.0.1152 → 4.0.1154
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/__tests__/generate-dependent-snippets.test.d.ts +1 -0
- package/dist/__tests__/generate-dependent-snippets.test.js +60 -0
- package/dist/__tests__/importCache.test.js +45 -1
- package/dist/__tests__/resolve-imports-from-import-closure.test.d.ts +1 -0
- package/dist/__tests__/resolve-imports-from-import-closure.test.js +80 -0
- package/dist/__tests__/resolve-page-imports.test.d.ts +1 -0
- package/dist/__tests__/resolve-page-imports.test.js +48 -0
- package/dist/__tests__/shouldRegenerateNavForPage.test.js +8 -1
- package/dist/local-preview/listener/generateDependentSnippets.js +47 -59
- package/dist/local-preview/listener/generatePagesWithImports.js +26 -27
- package/dist/local-preview/listener/importCache.d.ts +2 -0
- package/dist/local-preview/listener/importCache.js +38 -0
- package/dist/local-preview/listener/index.d.ts +2 -1
- package/dist/local-preview/listener/index.js +46 -6
- package/dist/local-preview/listener/resolve-imports-from-import-closure.d.ts +3 -0
- package/dist/local-preview/listener/resolve-imports-from-import-closure.js +98 -0
- package/dist/local-preview/listener/resolve-page-imports.d.ts +3 -0
- package/dist/local-preview/listener/resolve-page-imports.js +4 -0
- package/dist/local-preview/listener/utils.d.ts +1 -0
- package/dist/local-preview/listener/utils.js +21 -9
- package/dist/local-preview/run.js +2 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { findAndRemoveImports, hasImports, getFileCategory, validate, stringifyTree, isMintIgnored, replaceVariables, } from '@mintlify/common';
|
|
3
|
-
import { createPage, MintConfigUpdater, DocsConfigUpdater, preparseMdxTree, prebuild, } from '@mintlify/prebuild';
|
|
3
|
+
import { createPage, getFileListSync, MintConfigUpdater, DocsConfigUpdater, preparseMdxTree, prebuild, } from '@mintlify/prebuild';
|
|
4
4
|
import Chalk from 'chalk';
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
6
|
import { promises as _promises } from 'fs';
|
|
@@ -17,11 +17,29 @@ import { getDocsState } from './getDocsState.js';
|
|
|
17
17
|
import { initializeImportCache, updateImportCacheForFile, removeFromImportCache, getImportedFilesFromCache, syncImportedFileLocations, } from './importCache.js';
|
|
18
18
|
import { hasTrackedReferencedFile, refreshTrackedReferencedFiles } from './referencedFiles.js';
|
|
19
19
|
import { regenerateAllSnippets } from './regenerateAllSnippets.js';
|
|
20
|
-
import {
|
|
20
|
+
import { resolveImportsFromImportClosure } from './resolve-imports-from-import-closure.js';
|
|
21
|
+
import { resolvePageImports } from './resolve-page-imports.js';
|
|
21
22
|
import { updateCustomLanguages, updateGeneratedNav, updateOpenApiFiles, upsertOpenApiFile, } from './update.js';
|
|
22
|
-
import { getCurrentVariables, getMintIgnoreGlobs, handleParseError, isFileSizeValid, isJsonValid, shouldRegenerateNavForPage, suppressParseError, } from './utils.js';
|
|
23
|
+
import { getCurrentVariables, getMintIgnoreGlobs, handleParseError, isFileSizeValid, isJsonValid, seedFrontmatterHashForPage, shouldRegenerateNavForPage, suppressParseError, } from './utils.js';
|
|
23
24
|
const { readFile } = _promises;
|
|
24
25
|
const frontmatterHashes = new Map();
|
|
26
|
+
const SLOW_UPDATE_LOG_THRESHOLD_MS = 1000;
|
|
27
|
+
const getElapsedMs = (startTime) => {
|
|
28
|
+
return Number((process.hrtime.bigint() - startTime) / 1000000n);
|
|
29
|
+
};
|
|
30
|
+
const formatElapsedSeconds = (durationMs) => {
|
|
31
|
+
return `${(durationMs / 1000).toFixed(2)}s`;
|
|
32
|
+
};
|
|
33
|
+
const shouldLogUpdateTiming = (durationMs) => {
|
|
34
|
+
return process.env.DEV_TIMING === 'true' || durationMs >= SLOW_UPDATE_LOG_THRESHOLD_MS;
|
|
35
|
+
};
|
|
36
|
+
const addUpdateTimingLog = (filename, category, durationMs) => {
|
|
37
|
+
if (!shouldLogUpdateTiming(durationMs)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const categoryLabel = category == null ? 'uncategorized' : category;
|
|
41
|
+
addChangeLog(_jsx(InfoLog, { message: `processed ${filename} in ${formatElapsedSeconds(durationMs)} (${categoryLabel})` }));
|
|
42
|
+
};
|
|
25
43
|
const listener = (triggerRefresh, options = {}) => {
|
|
26
44
|
const mintIgnoreGlobs = getMintIgnoreGlobs();
|
|
27
45
|
chokidar
|
|
@@ -47,7 +65,9 @@ const onAddEvent = async (filename, triggerRefresh, options) => {
|
|
|
47
65
|
return;
|
|
48
66
|
}
|
|
49
67
|
try {
|
|
68
|
+
const startTime = process.hrtime.bigint();
|
|
50
69
|
const category = await onUpdateEvent(filename, triggerRefresh, options);
|
|
70
|
+
addUpdateTimingLog(filename, category, getElapsedMs(startTime));
|
|
51
71
|
if (category !== undefined) {
|
|
52
72
|
addChangeLog(_jsx(AddedLog, { filename: filename }));
|
|
53
73
|
}
|
|
@@ -61,7 +81,9 @@ const onChangeEvent = async (filename, triggerRefresh, options) => {
|
|
|
61
81
|
return;
|
|
62
82
|
}
|
|
63
83
|
try {
|
|
84
|
+
const startTime = process.hrtime.bigint();
|
|
64
85
|
const category = await onUpdateEvent(filename, triggerRefresh, options);
|
|
86
|
+
addUpdateTimingLog(filename, category, getElapsedMs(startTime));
|
|
65
87
|
if (category !== undefined) {
|
|
66
88
|
addChangeLog(_jsx(EditedLog, { filename: filename }));
|
|
67
89
|
}
|
|
@@ -77,6 +99,7 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
|
|
|
77
99
|
try {
|
|
78
100
|
const importedFiles = getImportedFilesFromCache();
|
|
79
101
|
const potentialCategory = getFileCategory(filename, { importedFiles });
|
|
102
|
+
frontmatterHashes.delete(filename);
|
|
80
103
|
if (hasTrackedReferencedFile(filename)) {
|
|
81
104
|
try {
|
|
82
105
|
const { mintConfig, openApiFiles, docsConfig } = await getDocsState(handleParseError);
|
|
@@ -136,6 +159,7 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
|
|
|
136
159
|
await fse.emptyDir(NEXT_PROPS_PATH);
|
|
137
160
|
const prebuildResult = await prebuild(CMD_EXEC_PATH, options);
|
|
138
161
|
await initializeImportCache(CMD_EXEC_PATH, prebuildResult?.fileImportsMap);
|
|
162
|
+
await initializeFrontmatterHashCache();
|
|
139
163
|
await refreshTrackedReferencedFiles();
|
|
140
164
|
}
|
|
141
165
|
catch (err) {
|
|
@@ -193,6 +217,21 @@ const validateConfigFiles = async () => {
|
|
|
193
217
|
console.error('⚠️ Error validating configuration files:', error);
|
|
194
218
|
}
|
|
195
219
|
};
|
|
220
|
+
const initializeFrontmatterHashCache = async () => {
|
|
221
|
+
frontmatterHashes.clear();
|
|
222
|
+
const importedFiles = getImportedFilesFromCache();
|
|
223
|
+
const pageFilenames = getFileListSync(CMD_EXEC_PATH).filter((file) => {
|
|
224
|
+
return getFileCategory(file, { importedFiles }) === 'page';
|
|
225
|
+
});
|
|
226
|
+
await Promise.all(pageFilenames.map(async (filename) => {
|
|
227
|
+
try {
|
|
228
|
+
const filePath = pathUtil.join(CMD_EXEC_PATH, filename);
|
|
229
|
+
const contentStr = (await readFile(filePath)).toString();
|
|
230
|
+
seedFrontmatterHashForPage(filename, contentStr, frontmatterHashes, suppressParseError);
|
|
231
|
+
}
|
|
232
|
+
catch { }
|
|
233
|
+
}));
|
|
234
|
+
};
|
|
196
235
|
/**
|
|
197
236
|
* This function is called when a file is added or changed
|
|
198
237
|
* @param filename
|
|
@@ -244,7 +283,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
244
283
|
const tree = await preparseMdxTree(contentStr, CMD_EXEC_PATH, filePath, suppressParseError);
|
|
245
284
|
const importsResponse = await findAndRemoveImports(tree);
|
|
246
285
|
if (hasImports(importsResponse)) {
|
|
247
|
-
contentStr = stringifyTree(await
|
|
286
|
+
contentStr = stringifyTree(await resolvePageImports({ ...importsResponse, filename }));
|
|
248
287
|
}
|
|
249
288
|
const { pageContent } = await createPage(filename, contentStr, CMD_EXEC_PATH, [], [], handleParseError);
|
|
250
289
|
await fse.outputFile(targetPath, pageContent, {
|
|
@@ -262,7 +301,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
262
301
|
const tree = await preparseMdxTree(contentStr, CMD_EXEC_PATH, filePath, handleParseError);
|
|
263
302
|
const importsResponse = await findAndRemoveImports(tree);
|
|
264
303
|
if (hasImports(importsResponse)) {
|
|
265
|
-
contentStr = stringifyTree(await
|
|
304
|
+
contentStr = stringifyTree(await resolveImportsFromImportClosure({ ...importsResponse, filename }));
|
|
266
305
|
}
|
|
267
306
|
await fse.outputFile(targetPath, contentStr, {
|
|
268
307
|
flag: 'w',
|
|
@@ -311,6 +350,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
311
350
|
await fse.emptyDir(NEXT_PROPS_PATH);
|
|
312
351
|
const prebuildResult = await prebuild(CMD_EXEC_PATH, options);
|
|
313
352
|
await initializeImportCache(CMD_EXEC_PATH, prebuildResult?.fileImportsMap);
|
|
353
|
+
await initializeFrontmatterHashCache();
|
|
314
354
|
await refreshTrackedReferencedFiles();
|
|
315
355
|
}
|
|
316
356
|
catch (err) {
|
|
@@ -377,5 +417,5 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
377
417
|
triggerRefresh();
|
|
378
418
|
return category;
|
|
379
419
|
};
|
|
380
|
-
export { initializeImportCache };
|
|
420
|
+
export { initializeFrontmatterHashCache, initializeImportCache };
|
|
381
421
|
export default listener;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { findAndRemoveImports, hasImports, optionallyAddLeadingSlash, optionallyRemoveLeadingSlash, replaceVariables, resolveAllImports as baseResolveAllImports, resolveImportPath, topologicalSort, } from '@mintlify/common';
|
|
2
|
+
import { preparseMdxTree } from '@mintlify/prebuild';
|
|
3
|
+
import fse from 'fs-extra';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../../constants.js';
|
|
6
|
+
import { getCurrentVariables, handleParseError } from './utils.js';
|
|
7
|
+
const getImportSourcePath = async (filename) => {
|
|
8
|
+
const generatedPath = join(NEXT_PUBLIC_PATH, filename);
|
|
9
|
+
if (await fse.pathExists(generatedPath)) {
|
|
10
|
+
return {
|
|
11
|
+
baseDir: NEXT_PUBLIC_PATH,
|
|
12
|
+
path: generatedPath,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const originalPath = join(CMD_EXEC_PATH, filename);
|
|
16
|
+
if (await fse.pathExists(originalPath)) {
|
|
17
|
+
return {
|
|
18
|
+
baseDir: CMD_EXEC_PATH,
|
|
19
|
+
path: originalPath,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
const parseSnippetWithImports = async ({ filename, variables, snippetsByFilename, }) => {
|
|
25
|
+
const normalizedFilename = optionallyAddLeadingSlash(filename);
|
|
26
|
+
if (snippetsByFilename.has(normalizedFilename)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const importSource = await getImportSourcePath(optionallyRemoveLeadingSlash(normalizedFilename));
|
|
30
|
+
if (importSource == null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const content = replaceVariables((await fse.readFile(importSource.path)).toString(), variables);
|
|
34
|
+
const tree = await preparseMdxTree(content, importSource.baseDir, importSource.path, handleParseError);
|
|
35
|
+
const importData = await findAndRemoveImports(tree);
|
|
36
|
+
const fileWithImports = {
|
|
37
|
+
filename: normalizedFilename,
|
|
38
|
+
...importData,
|
|
39
|
+
};
|
|
40
|
+
snippetsByFilename.set(normalizedFilename, fileWithImports);
|
|
41
|
+
await Promise.all(Object.keys(importData.importMap).map(async (source) => {
|
|
42
|
+
const resolvedPath = resolveImportPath(source, normalizedFilename);
|
|
43
|
+
if (resolvedPath == null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await parseSnippetWithImports({
|
|
47
|
+
filename: resolvedPath,
|
|
48
|
+
variables,
|
|
49
|
+
snippetsByFilename,
|
|
50
|
+
});
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
53
|
+
const resolveSnippetImports = async (snippetsByFilename) => {
|
|
54
|
+
const graph = {};
|
|
55
|
+
for (const snippet of snippetsByFilename.values()) {
|
|
56
|
+
graph[snippet.filename] = Object.keys(snippet.importMap)
|
|
57
|
+
.map((source) => resolveImportPath(source, snippet.filename))
|
|
58
|
+
.filter((resolvedPath) => {
|
|
59
|
+
return resolvedPath != null && snippetsByFilename.has(resolvedPath);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const orderedFilenames = topologicalSort(graph).reverse();
|
|
63
|
+
const resolvedSnippets = [];
|
|
64
|
+
for (const filename of orderedFilenames) {
|
|
65
|
+
const snippet = snippetsByFilename.get(filename);
|
|
66
|
+
if (snippet == null) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const tree = hasImports(snippet)
|
|
70
|
+
? await baseResolveAllImports({
|
|
71
|
+
snippets: resolvedSnippets,
|
|
72
|
+
fileWithImports: snippet,
|
|
73
|
+
})
|
|
74
|
+
: snippet.tree;
|
|
75
|
+
resolvedSnippets.push({ filename: snippet.filename, tree });
|
|
76
|
+
}
|
|
77
|
+
return resolvedSnippets;
|
|
78
|
+
};
|
|
79
|
+
export const resolveImportsFromImportClosure = async (fileWithImports) => {
|
|
80
|
+
const variables = await getCurrentVariables();
|
|
81
|
+
const snippetsByFilename = new Map();
|
|
82
|
+
await Promise.all(Object.keys(fileWithImports.importMap).map(async (source) => {
|
|
83
|
+
const resolvedPath = resolveImportPath(source, fileWithImports.filename);
|
|
84
|
+
if (resolvedPath == null) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await parseSnippetWithImports({
|
|
88
|
+
filename: resolvedPath,
|
|
89
|
+
variables,
|
|
90
|
+
snippetsByFilename,
|
|
91
|
+
});
|
|
92
|
+
}));
|
|
93
|
+
const snippets = await resolveSnippetImports(snippetsByFilename);
|
|
94
|
+
return await baseResolveAllImports({
|
|
95
|
+
snippets,
|
|
96
|
+
fileWithImports,
|
|
97
|
+
});
|
|
98
|
+
};
|
|
@@ -8,6 +8,7 @@ export declare const isJsonValid: (filePath: string) => {
|
|
|
8
8
|
error?: string;
|
|
9
9
|
};
|
|
10
10
|
export declare const shouldRegenerateNavForPage: (filename: string, contentStr: string, frontmatterHashes: Map<string, string>, onError?: (message: string) => void) => Promise<boolean>;
|
|
11
|
+
export declare const seedFrontmatterHashForPage: (filename: string, contentStr: string, frontmatterHashes: Map<string, string>, onError?: (message: string) => void) => boolean;
|
|
11
12
|
export declare function normalizePathForComparison(filePath: string): string;
|
|
12
13
|
export declare const handleParseError: (message: string) => void;
|
|
13
14
|
export declare const suppressParseError: () => void;
|
|
@@ -45,16 +45,8 @@ export const isJsonValid = (filePath) => {
|
|
|
45
45
|
};
|
|
46
46
|
export const shouldRegenerateNavForPage = async (filename, contentStr, frontmatterHashes, onError) => {
|
|
47
47
|
try {
|
|
48
|
-
const { attributes: currentFrontmatter } = parseFrontmatter(contentStr);
|
|
49
48
|
const prevFrontmatterHash = frontmatterHashes.get(filename);
|
|
50
|
-
const currentFrontmatterHash =
|
|
51
|
-
.createHash('md5')
|
|
52
|
-
.update(JSON.stringify(currentFrontmatter))
|
|
53
|
-
.digest('hex');
|
|
54
|
-
if (!prevFrontmatterHash) {
|
|
55
|
-
frontmatterHashes.set(filename, currentFrontmatterHash);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
49
|
+
const currentFrontmatterHash = getFrontmatterHash(contentStr);
|
|
58
50
|
if (currentFrontmatterHash !== prevFrontmatterHash) {
|
|
59
51
|
frontmatterHashes.set(filename, currentFrontmatterHash);
|
|
60
52
|
return true;
|
|
@@ -73,6 +65,26 @@ export const shouldRegenerateNavForPage = async (filename, contentStr, frontmatt
|
|
|
73
65
|
return true;
|
|
74
66
|
}
|
|
75
67
|
};
|
|
68
|
+
export const seedFrontmatterHashForPage = (filename, contentStr, frontmatterHashes, onError) => {
|
|
69
|
+
try {
|
|
70
|
+
frontmatterHashes.set(filename, getFrontmatterHash(contentStr));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const message = `Error parsing frontmatter for ${filename}, skipping initial nav cache: ${error instanceof Error ? error.message : String(error)}`;
|
|
75
|
+
if (onError) {
|
|
76
|
+
onError(message);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.warn(message);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const getFrontmatterHash = (contentStr) => {
|
|
85
|
+
const { attributes: currentFrontmatter } = parseFrontmatter(contentStr);
|
|
86
|
+
return crypto.createHash('md5').update(JSON.stringify(currentFrontmatter)).digest('hex');
|
|
87
|
+
};
|
|
76
88
|
export function normalizePathForComparison(filePath) {
|
|
77
89
|
return optionallyAddLeadingSlash(filePath).toLowerCase();
|
|
78
90
|
}
|
|
@@ -7,7 +7,7 @@ import { CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../constants.js';
|
|
|
7
7
|
import { addLog, removeLastLog } from '../logging-state.js';
|
|
8
8
|
import { LaunchLog, UpdateLog } from '../logs.js';
|
|
9
9
|
import { maybeFixMissingWindowsEnvVar } from '../util.js';
|
|
10
|
-
import listener, { initializeImportCache } from './listener/index.js';
|
|
10
|
+
import listener, { initializeFrontmatterHashCache, initializeImportCache } from './listener/index.js';
|
|
11
11
|
import { refreshTrackedReferencedFiles } from './listener/referencedFiles.js';
|
|
12
12
|
import { getLocalNetworkIp } from './network.js';
|
|
13
13
|
import { setupNext } from './setupNext.js';
|
|
@@ -145,6 +145,7 @@ export const run = async (argv) => {
|
|
|
145
145
|
process.on('SIGTERM', onExit);
|
|
146
146
|
});
|
|
147
147
|
await initializeImportCache(CMD_EXEC_PATH, argv.fileImportsMap);
|
|
148
|
+
await initializeFrontmatterHashCache();
|
|
148
149
|
await refreshTrackedReferencedFiles();
|
|
149
150
|
listener(onChange);
|
|
150
151
|
};
|