@mintlify/prebuild 1.0.1129 → 1.0.1131
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/categorizeFilePaths.d.ts +6 -1
- package/dist/prebuild/categorizeFilePaths.js +106 -16
- package/dist/prebuild/index.d.ts +2 -1
- package/dist/prebuild/index.js +5 -2
- package/dist/prebuild/update/index.d.ts +5 -1
- package/dist/prebuild/update/index.js +67 -3
- package/dist/prebuild/update/read/read-page-metadata.d.ts +15 -0
- package/dist/prebuild/update/read/read-page-metadata.js +46 -0
- package/dist/prebuild/update/write/writeFiles.js +19 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { AsyncAPIFile } from '@mintlify/common';
|
|
2
2
|
import { OpenApiFile } from '@mintlify/models';
|
|
3
3
|
import type { InvalidSpecFile } from './invalidSpecFiles.js';
|
|
4
|
-
export
|
|
4
|
+
export type PageScan = {
|
|
5
|
+
frontmatter: string;
|
|
6
|
+
rssCandidate: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const categorizeFilePaths: (contentDirectoryPath: string, mintIgnore?: string[], disableOpenApi?: boolean, lazyPages?: boolean) => Promise<{
|
|
5
9
|
contentFilenames: string[];
|
|
6
10
|
staticFilenames: string[];
|
|
7
11
|
openApiFiles: OpenApiFile[];
|
|
@@ -10,4 +14,5 @@ export declare const categorizeFilePaths: (contentDirectoryPath: string, mintIgn
|
|
|
10
14
|
snippets: string[];
|
|
11
15
|
snippetsV2: string[];
|
|
12
16
|
fileImportsMap: Map<string, Set<string>>;
|
|
17
|
+
pageScans: Map<string, PageScan>;
|
|
13
18
|
}>;
|
|
@@ -1,46 +1,135 @@
|
|
|
1
|
-
import { validate, validateAsyncApi,
|
|
1
|
+
import { validate, validateAsyncApi, extractImportSources, getFileCategory, isSnippetExtension, getAST, resolveImportPath, } from '@mintlify/common';
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { formatError } from '../errorMessages/formatError.js';
|
|
6
6
|
import { getFileList } from '../fs/index.js';
|
|
7
7
|
import { getFileExtension } from '../utils.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const MDX_IMPORT_PATTERN = /^import\s/m;
|
|
9
|
+
const IMPORT_SOURCE_PATTERN = /import\s+(?:[\w$*{},\s]+?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
10
|
+
const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
|
|
11
|
+
const MDX_SCAN_CONCURRENCY = 16;
|
|
12
|
+
const FENCE_PATTERN = /^(`{3,}|~{3,})/;
|
|
13
|
+
const extractImportSourcesFromContent = (content) => {
|
|
14
|
+
const sources = [];
|
|
15
|
+
let needsAst = false;
|
|
16
|
+
let fence;
|
|
17
|
+
for (const line of content.split('\n')) {
|
|
18
|
+
const trimmed = line.trimStart();
|
|
19
|
+
const fenceRun = FENCE_PATTERN.exec(trimmed)?.[1];
|
|
20
|
+
if (fence !== undefined) {
|
|
21
|
+
if (fenceRun !== undefined &&
|
|
22
|
+
fenceRun.charAt(0) === fence.char &&
|
|
23
|
+
fenceRun.length >= fence.length) {
|
|
24
|
+
fence = undefined;
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (fenceRun !== undefined) {
|
|
29
|
+
fence = { char: fenceRun.charAt(0), length: fenceRun.length };
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!line.startsWith('import ') && !line.startsWith('import\t')) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const matches = Array.from(line.matchAll(IMPORT_SOURCE_PATTERN));
|
|
36
|
+
if (matches.length === 0) {
|
|
37
|
+
needsAst = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
for (const match of matches) {
|
|
41
|
+
const source = match[1];
|
|
42
|
+
if (source) {
|
|
43
|
+
sources.push(source);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { sources, needsAst };
|
|
48
|
+
};
|
|
49
|
+
const getImportSources = (content, filePath, lazyPages) => {
|
|
50
|
+
if (lazyPages) {
|
|
51
|
+
if (!MDX_IMPORT_PATTERN.test(content)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const { sources, needsAst } = extractImportSourcesFromContent(content);
|
|
55
|
+
if (!needsAst) {
|
|
56
|
+
return sources;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return extractImportSources(getAST(content, filePath));
|
|
60
|
+
};
|
|
61
|
+
export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [], disableOpenApi, lazyPages) => {
|
|
62
|
+
const allFilenames = [];
|
|
63
|
+
for await (const filename of getFileList(contentDirectoryPath, contentDirectoryPath, mintIgnore)) {
|
|
64
|
+
allFilenames.push(filename);
|
|
65
|
+
}
|
|
66
|
+
const mdxCandidates = [];
|
|
11
67
|
const nonMdxFiles = [];
|
|
12
|
-
for
|
|
68
|
+
for (const filename of allFilenames) {
|
|
13
69
|
const extension = getFileExtension(filename);
|
|
14
70
|
if (isSnippetExtension(extension)) {
|
|
71
|
+
mdxCandidates.push(filename);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
nonMdxFiles.push({ filename, extension });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const importedFiles = new Set();
|
|
78
|
+
const fileImportsMap = new Map();
|
|
79
|
+
const pageScans = new Map();
|
|
80
|
+
const mdxResults = new Array(mdxCandidates.length);
|
|
81
|
+
const queue = mdxCandidates.map((filename, index) => ({ filename, index }));
|
|
82
|
+
await Promise.all(Array.from({ length: Math.min(MDX_SCAN_CONCURRENCY, queue.length) }, async () => {
|
|
83
|
+
let entry = queue.shift();
|
|
84
|
+
while (entry) {
|
|
85
|
+
const { filename, index } = entry;
|
|
15
86
|
const filePath = path.join(contentDirectoryPath, filename);
|
|
16
|
-
const content = await readFile(filePath, 'utf8');
|
|
17
87
|
try {
|
|
18
|
-
const
|
|
19
|
-
|
|
88
|
+
const content = await readFile(filePath, 'utf8');
|
|
89
|
+
const resolvedImports = new Set();
|
|
90
|
+
for (const source of getImportSources(content, filePath, lazyPages)) {
|
|
91
|
+
const resolved = resolveImportPath(source, filename);
|
|
92
|
+
if (resolved) {
|
|
93
|
+
const normalized = resolved.toLowerCase();
|
|
94
|
+
importedFiles.add(normalized);
|
|
95
|
+
resolvedImports.add(normalized);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
fileImportsMap.set(filename.toLowerCase(), resolvedImports);
|
|
99
|
+
if (lazyPages) {
|
|
100
|
+
pageScans.set(filename, {
|
|
101
|
+
frontmatter: FRONTMATTER_PATTERN.exec(content)?.[0] ?? '',
|
|
102
|
+
rssCandidate: content.includes('<Update'),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
mdxResults[index] = filename;
|
|
20
106
|
}
|
|
21
107
|
catch (error) {
|
|
22
108
|
console.error(formatError(error, filePath, contentDirectoryPath));
|
|
23
109
|
}
|
|
110
|
+
entry = queue.shift();
|
|
24
111
|
}
|
|
25
|
-
|
|
26
|
-
|
|
112
|
+
}));
|
|
113
|
+
const mdxFilenames = [];
|
|
114
|
+
for (const filename of mdxResults) {
|
|
115
|
+
if (filename !== undefined) {
|
|
116
|
+
mdxFilenames.push(filename);
|
|
27
117
|
}
|
|
28
118
|
}
|
|
29
|
-
const { importedFiles, fileImportsMap } = buildImportMap(mdxFiles);
|
|
30
119
|
const contentFilenames = [];
|
|
31
120
|
const snippets = [];
|
|
32
121
|
const snippetsV2 = [];
|
|
33
|
-
for (const
|
|
34
|
-
const category = getFileCategory(
|
|
122
|
+
for (const filename of mdxFilenames) {
|
|
123
|
+
const category = getFileCategory(filename, { importedFiles });
|
|
35
124
|
switch (category) {
|
|
36
125
|
case 'snippet':
|
|
37
|
-
snippets.push(
|
|
126
|
+
snippets.push(filename);
|
|
38
127
|
break;
|
|
39
128
|
case 'snippet-v2':
|
|
40
|
-
snippetsV2.push(
|
|
129
|
+
snippetsV2.push(filename);
|
|
41
130
|
break;
|
|
42
131
|
case 'page':
|
|
43
|
-
contentFilenames.push(
|
|
132
|
+
contentFilenames.push(filename);
|
|
44
133
|
break;
|
|
45
134
|
}
|
|
46
135
|
}
|
|
@@ -124,5 +213,6 @@ export const categorizeFilePaths = async (contentDirectoryPath, mintIgnore = [],
|
|
|
124
213
|
snippets,
|
|
125
214
|
snippetsV2,
|
|
126
215
|
fileImportsMap,
|
|
216
|
+
pageScans,
|
|
127
217
|
};
|
|
128
218
|
};
|
package/dist/prebuild/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export interface PrebuildResult {
|
|
2
2
|
fileImportsMap: Map<string, Set<string>>;
|
|
3
3
|
}
|
|
4
|
-
export declare const prebuild: (contentDirectoryPath: string, { localSchema, groups, disableOpenApi, strict, allowSourceRefs, }?: {
|
|
4
|
+
export declare const prebuild: (contentDirectoryPath: string, { localSchema, groups, disableOpenApi, strict, allowSourceRefs, lazyPages, }?: {
|
|
5
5
|
localSchema?: boolean;
|
|
6
6
|
groups?: string[];
|
|
7
7
|
disableOpenApi?: boolean;
|
|
8
8
|
strict?: boolean;
|
|
9
9
|
allowSourceRefs?: boolean;
|
|
10
|
+
lazyPages?: boolean;
|
|
10
11
|
}) => Promise<PrebuildResult | undefined>;
|
|
11
12
|
export * from './categorizeFilePaths.js';
|
|
12
13
|
export * from './getOpenApiFiles.js';
|
package/dist/prebuild/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { categorizeFilePaths } from './categorizeFilePaths.js';
|
|
|
3
3
|
import { warnInvalidSpecFiles } from './invalidSpecFiles.js';
|
|
4
4
|
import { update } from './update/index.js';
|
|
5
5
|
import { clearWarnings, checkStrictMode } from './warnings.js';
|
|
6
|
-
export const prebuild = async (contentDirectoryPath, { localSchema, groups, disableOpenApi, strict, allowSourceRefs, } = {}) => {
|
|
6
|
+
export const prebuild = async (contentDirectoryPath, { localSchema, groups, disableOpenApi, strict, allowSourceRefs, lazyPages, } = {}) => {
|
|
7
7
|
if (process.env.IS_MULTI_TENANT === 'true') {
|
|
8
8
|
console.log('Skipping prebuild in multi-tenant mode.');
|
|
9
9
|
return;
|
|
@@ -16,7 +16,7 @@ export const prebuild = async (contentDirectoryPath, { localSchema, groups, disa
|
|
|
16
16
|
throw Error('must be run in a directory where a docs.json file exists.');
|
|
17
17
|
}
|
|
18
18
|
const mintIgnore = await getMintIgnore(contentDirectoryPath);
|
|
19
|
-
const { contentFilenames, staticFilenames, openApiFiles, asyncApiFiles, invalidSpecFiles, snippets, snippetsV2, fileImportsMap, } = await categorizeFilePaths(contentDirectoryPath, mintIgnore, disableOpenApi);
|
|
19
|
+
const { contentFilenames, staticFilenames, openApiFiles, asyncApiFiles, invalidSpecFiles, snippets, snippetsV2, fileImportsMap, pageScans, } = await categorizeFilePaths(contentDirectoryPath, mintIgnore, disableOpenApi, lazyPages);
|
|
20
20
|
await update({
|
|
21
21
|
contentDirectoryPath,
|
|
22
22
|
staticFilenames,
|
|
@@ -33,6 +33,9 @@ export const prebuild = async (contentDirectoryPath, { localSchema, groups, disa
|
|
|
33
33
|
strict,
|
|
34
34
|
invalidSpecFiles,
|
|
35
35
|
allowSourceRefs,
|
|
36
|
+
lazyPages,
|
|
37
|
+
pageScans,
|
|
38
|
+
fileImportsMap,
|
|
36
39
|
});
|
|
37
40
|
// Deferred until after update so files that fail the build don't also warn
|
|
38
41
|
warnInvalidSpecFiles(invalidSpecFiles);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncAPIFile } from '@mintlify/common';
|
|
2
2
|
import type { OpenApiFile } from '@mintlify/models';
|
|
3
3
|
import type { Root } from 'mdast';
|
|
4
|
+
import type { PageScan } from '../categorizeFilePaths.js';
|
|
4
5
|
import type { InvalidSpecFile } from '../invalidSpecFiles.js';
|
|
5
6
|
type UpdateArgs = {
|
|
6
7
|
contentDirectoryPath: string;
|
|
@@ -18,8 +19,11 @@ type UpdateArgs = {
|
|
|
18
19
|
strict?: boolean;
|
|
19
20
|
invalidSpecFiles?: InvalidSpecFile[];
|
|
20
21
|
allowSourceRefs?: boolean;
|
|
22
|
+
lazyPages?: boolean;
|
|
23
|
+
pageScans?: Map<string, PageScan>;
|
|
24
|
+
fileImportsMap?: Map<string, Set<string>>;
|
|
21
25
|
};
|
|
22
|
-
export declare const update: ({ contentDirectoryPath, staticFilenames, openApiFiles, asyncApiFiles, contentFilenames, snippets, snippetV2Filenames, docsConfigPath, localSchema, groups, mintIgnore, disableOpenApi, strict, invalidSpecFiles, allowSourceRefs, }: UpdateArgs) => Promise<{
|
|
26
|
+
export declare const update: ({ contentDirectoryPath, staticFilenames, openApiFiles, asyncApiFiles, contentFilenames, snippets, snippetV2Filenames, docsConfigPath, localSchema, groups, mintIgnore, disableOpenApi, strict, invalidSpecFiles, allowSourceRefs, lazyPages, pageScans, fileImportsMap, }: UpdateArgs) => Promise<{
|
|
23
27
|
name: string;
|
|
24
28
|
$schema: string;
|
|
25
29
|
theme: "mint";
|
|
@@ -4,6 +4,7 @@ import { outputFile } from 'fs-extra';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { updateDocsConfigFile } from './docsConfig/index.js';
|
|
6
6
|
import { updateMintConfigFile } from './mintConfig/index.js';
|
|
7
|
+
import { scanPageMetadata } from './read/read-page-metadata.js';
|
|
7
8
|
import { readPageContents, readSnippetsV2Contents } from './read/readContent.js';
|
|
8
9
|
import { resolveImportsAndWriteFiles } from './resolveImportsAndWriteFiles.js';
|
|
9
10
|
import { updateFavicons } from './updateFavicons.js';
|
|
@@ -12,7 +13,52 @@ import { writeAsyncApiFiles } from './write/writeAsyncApiFiles.js';
|
|
|
12
13
|
import { writeFiles, writeFile } from './write/writeFiles.js';
|
|
13
14
|
import { writeOpenApiData } from './write/writeOpenApiData.js';
|
|
14
15
|
import { writeRssFiles } from './write/writeRssFiles.js';
|
|
15
|
-
|
|
16
|
+
const expandRssCandidates = (rssCandidateFilenames, contentFilenames, fileImportsMap, pageScans) => {
|
|
17
|
+
const carriers = new Set();
|
|
18
|
+
for (const [filename, scan] of pageScans) {
|
|
19
|
+
if (scan.rssCandidate) {
|
|
20
|
+
carriers.add(filename.toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
let changed = carriers.size > 0;
|
|
24
|
+
while (changed) {
|
|
25
|
+
changed = false;
|
|
26
|
+
for (const [filename, imports] of fileImportsMap) {
|
|
27
|
+
if (carriers.has(filename))
|
|
28
|
+
continue;
|
|
29
|
+
for (const imported of imports) {
|
|
30
|
+
if (carriers.has(imported)) {
|
|
31
|
+
carriers.add(filename);
|
|
32
|
+
changed = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const candidates = new Set(rssCandidateFilenames);
|
|
39
|
+
for (const filename of contentFilenames) {
|
|
40
|
+
if (carriers.has(filename.toLowerCase())) {
|
|
41
|
+
candidates.add(filename);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...candidates];
|
|
45
|
+
};
|
|
46
|
+
const filterToImportClosure = (snippetV2Filenames, rootFilenames, fileImportsMap) => {
|
|
47
|
+
const needed = new Set();
|
|
48
|
+
const stack = rootFilenames.map((filename) => filename.toLowerCase());
|
|
49
|
+
let current = stack.pop();
|
|
50
|
+
while (current !== undefined) {
|
|
51
|
+
for (const imported of fileImportsMap.get(current) ?? []) {
|
|
52
|
+
if (!needed.has(imported)) {
|
|
53
|
+
needed.add(imported);
|
|
54
|
+
stack.push(imported);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
current = stack.pop();
|
|
58
|
+
}
|
|
59
|
+
return snippetV2Filenames.filter((filename) => needed.has(filename.toLowerCase()));
|
|
60
|
+
};
|
|
61
|
+
export const update = async ({ contentDirectoryPath, staticFilenames, openApiFiles, asyncApiFiles, contentFilenames, snippets, snippetV2Filenames, docsConfigPath, localSchema, groups, mintIgnore, disableOpenApi, strict, invalidSpecFiles, allowSourceRefs, lazyPages, pageScans, fileImportsMap, }) => {
|
|
16
62
|
const mintConfigResult = await updateMintConfigFile(contentDirectoryPath, openApiFiles, localSchema, strict, invalidSpecFiles);
|
|
17
63
|
// we used the original mint config without openapi pages injected
|
|
18
64
|
// because we will do it in `updateDocsConfigFile`, this will avoid duplicated openapi pages
|
|
@@ -28,14 +74,32 @@ export const update = async ({ contentDirectoryPath, staticFilenames, openApiFil
|
|
|
28
74
|
invalidSpecFiles,
|
|
29
75
|
allowSourceRefs,
|
|
30
76
|
});
|
|
77
|
+
let contentFilenamesToParse = contentFilenames;
|
|
78
|
+
if (lazyPages) {
|
|
79
|
+
const { rssCandidateFilenames } = await scanPageMetadata({
|
|
80
|
+
contentDirectoryPath,
|
|
81
|
+
openApiFiles: newOpenApiFiles,
|
|
82
|
+
asyncApiFiles: newAsyncApiFiles,
|
|
83
|
+
contentFilenames,
|
|
84
|
+
pagesAcc,
|
|
85
|
+
pageScans,
|
|
86
|
+
});
|
|
87
|
+
contentFilenamesToParse =
|
|
88
|
+
fileImportsMap && pageScans
|
|
89
|
+
? expandRssCandidates(rssCandidateFilenames, contentFilenames, fileImportsMap, pageScans)
|
|
90
|
+
: rssCandidateFilenames;
|
|
91
|
+
}
|
|
31
92
|
const pagePromises = readPageContents({
|
|
32
93
|
contentDirectoryPath,
|
|
33
94
|
openApiFiles: newOpenApiFiles,
|
|
34
95
|
asyncApiFiles: newAsyncApiFiles,
|
|
35
|
-
contentFilenames,
|
|
96
|
+
contentFilenames: contentFilenamesToParse,
|
|
36
97
|
pagesAcc,
|
|
37
98
|
});
|
|
38
|
-
const
|
|
99
|
+
const snippetV2FilenamesToParse = lazyPages && fileImportsMap
|
|
100
|
+
? filterToImportClosure(snippetV2Filenames, contentFilenamesToParse, fileImportsMap)
|
|
101
|
+
: snippetV2Filenames;
|
|
102
|
+
const snippetV2Promises = readSnippetsV2Contents(contentDirectoryPath, snippetV2FilenamesToParse, newDocsConfig.variables ?? undefined);
|
|
39
103
|
const [snippetV2Contents, { mdxFilesWithNoImports, filesWithImports }] = await Promise.all([
|
|
40
104
|
snippetV2Promises,
|
|
41
105
|
pagePromises,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AsyncAPIFile } from '@mintlify/common';
|
|
2
|
+
import type { DecoratedNavigationPage, OpenApiFile } from '@mintlify/models';
|
|
3
|
+
import type { PageScan } from '../../categorizeFilePaths.js';
|
|
4
|
+
type ScanPageMetadataArgs = {
|
|
5
|
+
contentDirectoryPath: string;
|
|
6
|
+
openApiFiles: OpenApiFile[];
|
|
7
|
+
asyncApiFiles: AsyncAPIFile[];
|
|
8
|
+
contentFilenames: string[];
|
|
9
|
+
pagesAcc: Record<string, DecoratedNavigationPage>;
|
|
10
|
+
pageScans?: Map<string, PageScan>;
|
|
11
|
+
};
|
|
12
|
+
export declare const scanPageMetadata: ({ contentDirectoryPath, openApiFiles, asyncApiFiles, contentFilenames, pagesAcc, pageScans, }: ScanPageMetadataArgs) => Promise<{
|
|
13
|
+
rssCandidateFilenames: string[];
|
|
14
|
+
}>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getDecoratedNavPageAndSlug, parseFrontmatter } from '@mintlify/common';
|
|
2
|
+
import { promises as _promises } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { preserveAutoGeneratedMetadata } from '../preserveAutoGeneratedMetadata.js';
|
|
5
|
+
const { readFile } = _promises;
|
|
6
|
+
const SCAN_CONCURRENCY = 16;
|
|
7
|
+
const hasRssFrontmatter = (contentStr) => {
|
|
8
|
+
try {
|
|
9
|
+
return parseFrontmatter(contentStr).attributes.rss === true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const scanPageMetadata = async ({ contentDirectoryPath, openApiFiles, asyncApiFiles, contentFilenames, pagesAcc, pageScans, }) => {
|
|
16
|
+
const rssCandidateFilenames = [];
|
|
17
|
+
const queue = [...contentFilenames];
|
|
18
|
+
const scanFile = async (filename) => {
|
|
19
|
+
const scan = pageScans?.get(filename);
|
|
20
|
+
const contentStr = scan?.frontmatter ?? (await readFile(join(contentDirectoryPath, filename), 'utf8'));
|
|
21
|
+
const { slug, pageMetadata } = getDecoratedNavPageAndSlug(filename, contentStr, openApiFiles, asyncApiFiles);
|
|
22
|
+
preserveAutoGeneratedMetadata(contentStr, slug, pageMetadata, pagesAcc);
|
|
23
|
+
const rssCandidate = scan
|
|
24
|
+
? scan.rssCandidate || hasRssFrontmatter(contentStr)
|
|
25
|
+
: contentStr.includes('<Update') || hasRssFrontmatter(contentStr);
|
|
26
|
+
if (rssCandidate) {
|
|
27
|
+
rssCandidateFilenames.push(filename);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
await Promise.all(Array.from({ length: Math.min(SCAN_CONCURRENCY, queue.length) }, async () => {
|
|
31
|
+
let filename = queue.shift();
|
|
32
|
+
while (filename !== undefined) {
|
|
33
|
+
try {
|
|
34
|
+
await scanFile(filename);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const errorMessage = error instanceof Error
|
|
38
|
+
? error.message
|
|
39
|
+
: 'Unknown error occurred reading page metadata.';
|
|
40
|
+
console.error(`${join(contentDirectoryPath, filename)}: ${errorMessage}`);
|
|
41
|
+
}
|
|
42
|
+
filename = queue.shift();
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
return { rssCandidateFilenames };
|
|
46
|
+
};
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import fse from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
const isUnchangedFile = async (sourcePath, targetPath) => {
|
|
4
|
+
try {
|
|
5
|
+
const [sourceStat, targetStat] = await Promise.all([
|
|
6
|
+
fse.stat(sourcePath),
|
|
7
|
+
fse.stat(targetPath),
|
|
8
|
+
]);
|
|
9
|
+
return (sourceStat.isFile() &&
|
|
10
|
+
targetStat.isFile() &&
|
|
11
|
+
sourceStat.size === targetStat.size &&
|
|
12
|
+
Math.abs(sourceStat.mtimeMs - targetStat.mtimeMs) < 2);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
3
18
|
export const writeFiles = (contentDirectoryPath, targetDirectoryPath, filenames) => {
|
|
4
19
|
const filePromises = [];
|
|
5
20
|
filenames.forEach((filename) => {
|
|
6
21
|
filePromises.push((async () => {
|
|
7
22
|
const sourcePath = path.join(contentDirectoryPath, filename);
|
|
8
23
|
const targetPath = path.join(targetDirectoryPath, filename);
|
|
24
|
+
if (await isUnchangedFile(sourcePath, targetPath)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
9
27
|
await fse.remove(targetPath);
|
|
10
|
-
await fse.copy(sourcePath, targetPath);
|
|
28
|
+
await fse.copy(sourcePath, targetPath, { preserveTimestamps: true });
|
|
11
29
|
})());
|
|
12
30
|
});
|
|
13
31
|
return filePromises;
|