@mintlify/previewing 4.0.1533 → 4.0.1535
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__/export-search.test.d.ts +1 -0
- package/dist/__tests__/export-search.test.js +209 -0
- package/dist/local-preview/export-search.d.ts +22 -0
- package/dist/local-preview/export-search.js +160 -0
- package/dist/local-preview/export.js +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { MINT_EXPORT_SEARCH_INDEX_PATH } from '@mintlify/common';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { extractSearchDocuments, getSearchablePages, writeSearchIndex, } from '../local-preview/export-search.js';
|
|
6
|
+
const page = (title, body, description = '') => `<!DOCTYPE html><html><head>
|
|
7
|
+
<meta name="description" content="${description}"/></head><body>
|
|
8
|
+
<nav><a href="/other">Sidebar link</a></nav>
|
|
9
|
+
<h1 id="page-title">${title}</h1>
|
|
10
|
+
<div id="content" data-page-title="${title}">${body}</div>
|
|
11
|
+
<footer>Footer text</footer>
|
|
12
|
+
</body></html>`;
|
|
13
|
+
const nav = (value) => value;
|
|
14
|
+
const config = (value) => value;
|
|
15
|
+
describe('extractSearchDocuments', () => {
|
|
16
|
+
it('splits content into sections at headings with ids', () => {
|
|
17
|
+
const documents = extractSearchDocuments(page('Configuration', '<p>Intro text.</p><h2 id="install"><a href="#install"></a>Install</h2><p>Run <code>npm i</code>.</p>' +
|
|
18
|
+
'<h2 id="_R_abc_">Card title</h2><p>Card body.</p><h3 id="flags">Flags</h3><ul><li>--port</li><li>--host</li></ul>' +
|
|
19
|
+
'<button>Copy</button><script>ignored()</script><span aria-hidden="true">hidden</span>', 'Configure Conduit'), '/guides/configuration', { breadcrumbs: ['Guides'], version: 'v2' });
|
|
20
|
+
expect(documents).toEqual([
|
|
21
|
+
{
|
|
22
|
+
id: '/guides/configuration#0.0',
|
|
23
|
+
page: '/guides/configuration',
|
|
24
|
+
title: 'Configuration',
|
|
25
|
+
header: 'Configuration',
|
|
26
|
+
content: 'Configure Conduit Intro text.',
|
|
27
|
+
breadcrumbs: ['Guides'],
|
|
28
|
+
version: 'v2',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: '/guides/configuration#1.0',
|
|
32
|
+
page: '/guides/configuration',
|
|
33
|
+
title: 'Configuration',
|
|
34
|
+
header: 'Install',
|
|
35
|
+
hash: 'install',
|
|
36
|
+
content: 'Run npm i. Card title Card body.',
|
|
37
|
+
breadcrumbs: ['Guides'],
|
|
38
|
+
version: 'v2',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: '/guides/configuration#2.0',
|
|
42
|
+
page: '/guides/configuration',
|
|
43
|
+
title: 'Configuration',
|
|
44
|
+
header: 'Flags',
|
|
45
|
+
hash: 'flags',
|
|
46
|
+
content: '--port --host',
|
|
47
|
+
breadcrumbs: ['Guides'],
|
|
48
|
+
version: 'v2',
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
it('splits long sections into uniquely identified, overlapping chunks so their full text stays searchable', () => {
|
|
53
|
+
const words = Array.from({ length: 900 }, (_, i) => `w${i}`).join(' ');
|
|
54
|
+
const body = `<h2 id="install">Install</h2><p>${words} lastword</p>` +
|
|
55
|
+
'<h2 id="install-1">Install</h2><p>Again.</p>';
|
|
56
|
+
const documents = extractSearchDocuments(page('Long', body), '/long').slice(1);
|
|
57
|
+
expect(documents.map((document) => [document.id, document.hash])).toEqual([
|
|
58
|
+
['/long#1.0', 'install'],
|
|
59
|
+
['/long#1.1', 'install'],
|
|
60
|
+
['/long#1.2', 'install'],
|
|
61
|
+
['/long#2.0', 'install-1'],
|
|
62
|
+
]);
|
|
63
|
+
expect(documents.every((document) => document.content.length <= 2000)).toBe(true);
|
|
64
|
+
expect(documents[2]?.content.endsWith('lastword')).toBe(true);
|
|
65
|
+
expect(documents[1]?.content.split(' ')[0]).toBe(documents[0]?.content.split(' ').at(-1));
|
|
66
|
+
expect(documents[2]?.content.split(' ')[0]).toBe(documents[1]?.content.split(' ').at(-1));
|
|
67
|
+
});
|
|
68
|
+
it('falls back to the content area on API pages and keeps table cells apart', () => {
|
|
69
|
+
const html = `<html><head><meta name="description" content="List users"/></head><body>
|
|
70
|
+
<div id="content-area"><h1 id="page-title">Get users</h1>
|
|
71
|
+
<h2 id="params">Params</h2><table><tr><th>Name</th><th>Type</th></tr><tr><td>limit</td><td>integer</td></tr></table>
|
|
72
|
+
</div></body></html>`;
|
|
73
|
+
expect(extractSearchDocuments(html, '/api/users')).toEqual([
|
|
74
|
+
expect.objectContaining({ header: 'Get users', content: 'List users Get users' }),
|
|
75
|
+
expect.objectContaining({ hash: 'params', content: 'Name Type limit integer' }),
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
it('returns nothing for pages without a content region', () => {
|
|
79
|
+
expect(extractSearchDocuments('<html><body><p>404</p></body></html>', '/missing')).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('getSearchablePages', () => {
|
|
83
|
+
it('tags pages with breadcrumbs, language and version', () => {
|
|
84
|
+
const pages = getSearchablePages(nav({
|
|
85
|
+
languages: [
|
|
86
|
+
{
|
|
87
|
+
language: 'en',
|
|
88
|
+
versions: [
|
|
89
|
+
{
|
|
90
|
+
version: 'v2',
|
|
91
|
+
groups: [{ group: 'Start', pages: [{ href: '/quickstart', title: 'Quickstart' }] }],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
language: 'fr',
|
|
97
|
+
groups: [{ group: 'Début', pages: [{ href: '/fr/quickstart', title: 'Démarrage' }] }],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}), config({
|
|
101
|
+
navigation: {
|
|
102
|
+
languages: [
|
|
103
|
+
{
|
|
104
|
+
language: 'en',
|
|
105
|
+
versions: [
|
|
106
|
+
{
|
|
107
|
+
version: 'v2',
|
|
108
|
+
tabs: [{ tab: 'Docs', groups: [{ group: 'Start', pages: ['quickstart'] }] }],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{ language: 'fr', groups: [{ group: 'Début', pages: ['fr/quickstart'] }] },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}), undefined);
|
|
116
|
+
expect(pages.get('/quickstart')).toEqual({
|
|
117
|
+
breadcrumbs: ['Docs', 'Start'],
|
|
118
|
+
locale: 'en',
|
|
119
|
+
version: 'v2',
|
|
120
|
+
});
|
|
121
|
+
expect(pages.get('/fr/quickstart')).toEqual({ breadcrumbs: ['Début'], locale: 'fr' });
|
|
122
|
+
});
|
|
123
|
+
it('leaves out pages that hosted search leaves out', () => {
|
|
124
|
+
const generatedNav = nav({
|
|
125
|
+
groups: [
|
|
126
|
+
{
|
|
127
|
+
group: 'Guides',
|
|
128
|
+
pages: [
|
|
129
|
+
{ href: '/visible', title: 'Visible' },
|
|
130
|
+
{ href: '/hidden', title: 'Hidden', hidden: true },
|
|
131
|
+
{ href: '/noindex', title: 'Noindex', noindex: true },
|
|
132
|
+
{ href: '/unsearchable', title: 'Unsearchable', searchable: false },
|
|
133
|
+
{ href: '/admin', title: 'Admin', groups: ['admin'] },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
expect([...getSearchablePages(generatedNav, undefined, undefined).keys()]).toEqual([
|
|
139
|
+
'/visible',
|
|
140
|
+
]);
|
|
141
|
+
expect([...getSearchablePages(generatedNav, undefined, ['admin']).keys()]).toEqual([
|
|
142
|
+
'/visible',
|
|
143
|
+
'/admin',
|
|
144
|
+
]);
|
|
145
|
+
expect([
|
|
146
|
+
...getSearchablePages(generatedNav, config({ seo: { indexing: 'all' } }), undefined).keys(),
|
|
147
|
+
]).toEqual(['/visible', '/hidden']);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe('writeSearchIndex', () => {
|
|
151
|
+
let exportDir;
|
|
152
|
+
beforeEach(async () => {
|
|
153
|
+
exportDir = await fse.mkdtemp(path.join(os.tmpdir(), 'mint-export-search-test-'));
|
|
154
|
+
});
|
|
155
|
+
afterEach(async () => {
|
|
156
|
+
await fse.remove(exportDir);
|
|
157
|
+
});
|
|
158
|
+
it('indexes each searchable exported page once and links /index pages to their parent', async () => {
|
|
159
|
+
await fse.outputFile(path.join(exportDir, 'index', 'index.html'), page('Introduction', '<p>Welcome.</p>'));
|
|
160
|
+
await fse.outputFile(path.join(exportDir, 'quickstart', 'index.html'), page('Quickstart', '<p>Install.</p>'));
|
|
161
|
+
await fse.outputFile(path.join(exportDir, 'secret', 'index.html'), page('Secret', '<p>Hidden.</p>'));
|
|
162
|
+
const count = await writeSearchIndex(exportDir, {
|
|
163
|
+
generatedNav: nav({
|
|
164
|
+
groups: [
|
|
165
|
+
{
|
|
166
|
+
group: 'Getting started',
|
|
167
|
+
pages: [
|
|
168
|
+
{ href: '/index', title: 'Introduction' },
|
|
169
|
+
{ href: '/quickstart', title: 'Quickstart' },
|
|
170
|
+
{ href: '/missing', title: 'Missing' },
|
|
171
|
+
{ href: '/secret', title: 'Secret', noindex: true },
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}),
|
|
176
|
+
docsConfig: config({
|
|
177
|
+
navigation: {
|
|
178
|
+
groups: [
|
|
179
|
+
{ group: 'Getting started', pages: ['index', 'quickstart', 'missing', 'secret'] },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
groups: undefined,
|
|
184
|
+
});
|
|
185
|
+
const documents = await fse.readJson(path.join(exportDir, MINT_EXPORT_SEARCH_INDEX_PATH));
|
|
186
|
+
expect(count).toBe(2);
|
|
187
|
+
expect(documents).toEqual([
|
|
188
|
+
expect.objectContaining({
|
|
189
|
+
page: '/',
|
|
190
|
+
title: 'Introduction',
|
|
191
|
+
breadcrumbs: ['Getting started'],
|
|
192
|
+
}),
|
|
193
|
+
expect.objectContaining({
|
|
194
|
+
page: '/quickstart',
|
|
195
|
+
title: 'Quickstart',
|
|
196
|
+
breadcrumbs: ['Getting started'],
|
|
197
|
+
}),
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
it('writes an empty index when there is no generated navigation', async () => {
|
|
201
|
+
const count = await writeSearchIndex(exportDir, {
|
|
202
|
+
generatedNav: undefined,
|
|
203
|
+
docsConfig: undefined,
|
|
204
|
+
groups: undefined,
|
|
205
|
+
});
|
|
206
|
+
expect(count).toBe(0);
|
|
207
|
+
expect(await fse.readJson(path.join(exportDir, MINT_EXPORT_SEARCH_INDEX_PATH))).toEqual([]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DecoratedNavigationConfig, DocsConfig } from '@mintlify/validation';
|
|
2
|
+
type PageSearchInfo = {
|
|
3
|
+
breadcrumbs?: string[];
|
|
4
|
+
locale?: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
};
|
|
7
|
+
export type SearchDocument = PageSearchInfo & {
|
|
8
|
+
id: string;
|
|
9
|
+
page: string;
|
|
10
|
+
title: string;
|
|
11
|
+
header: string;
|
|
12
|
+
hash?: string;
|
|
13
|
+
content: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function extractSearchDocuments(html: string, page: string, { breadcrumbs, locale, version }?: PageSearchInfo): SearchDocument[];
|
|
16
|
+
export declare function getSearchablePages(generatedNav: DecoratedNavigationConfig, docsConfig: Pick<DocsConfig, 'navigation' | 'seo'> | undefined, groups: string[] | undefined): Map<string, PageSearchInfo>;
|
|
17
|
+
export declare function writeSearchIndex(exportDir: string, { generatedNav, docsConfig, groups, }: {
|
|
18
|
+
generatedNav: DecoratedNavigationConfig | undefined;
|
|
19
|
+
docsConfig: Pick<DocsConfig, 'navigation' | 'seo'> | undefined;
|
|
20
|
+
groups: string[] | undefined;
|
|
21
|
+
}): Promise<number>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { WILDCARD_GROUP, checkNavAccess, decoratedNavChildren, decoratedNavDivisions, generatePathToBreadcrumbsMapForDocsConfig, generatePathToHiddenFromSearchDict, generatePathToLanguageDict, generatePathToVersionDict, isPage, MINT_EXPORT_SEARCH_INDEX_PATH, optionallyRemoveLeadingSlash, } from '@mintlify/common';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import { fromHtml } from 'hast-util-from-html';
|
|
4
|
+
import { toText } from 'hast-util-to-text';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
const MAX_CONTENT_LENGTH = 2000;
|
|
7
|
+
const SKIPPED_TAGS = new Set(['script', 'style', 'svg', 'button', 'noscript', 'template']);
|
|
8
|
+
const SECTION_HEADINGS = new Set(['h2', 'h3']);
|
|
9
|
+
const isElement = (node) => node.type === 'element';
|
|
10
|
+
const getProperty = (element, name) => {
|
|
11
|
+
const value = element.properties[name];
|
|
12
|
+
return typeof value === 'string' ? value : undefined;
|
|
13
|
+
};
|
|
14
|
+
const isHidden = (element) => SKIPPED_TAGS.has(element.tagName) || element.properties.ariaHidden === 'true';
|
|
15
|
+
const REACT_GENERATED_ID = /^(_R_|«|:)/;
|
|
16
|
+
const normalizeWhitespace = (text) => text
|
|
17
|
+
.replace(/[\u200b\u200c\u200d\ufeff]/g, '')
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.trim();
|
|
20
|
+
function splitContent(text) {
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let current = '';
|
|
23
|
+
for (const word of text.split(' ')) {
|
|
24
|
+
if (current && current.length + word.length + 1 > MAX_CONTENT_LENGTH) {
|
|
25
|
+
chunks.push(current);
|
|
26
|
+
current = `${current.slice(current.lastIndexOf(' ') + 1)} ${word}`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
current = current ? `${current} ${word}` : word;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [...chunks, current];
|
|
33
|
+
}
|
|
34
|
+
function findElement(node, predicate) {
|
|
35
|
+
for (const child of node.children) {
|
|
36
|
+
if (!isElement(child))
|
|
37
|
+
continue;
|
|
38
|
+
if (predicate(child))
|
|
39
|
+
return child;
|
|
40
|
+
const found = findElement(child, predicate);
|
|
41
|
+
if (found)
|
|
42
|
+
return found;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function collectSections(content, pageTitle) {
|
|
47
|
+
const sections = [{ header: pageTitle, text: [] }];
|
|
48
|
+
const visit = (node) => {
|
|
49
|
+
if (node.type === 'text') {
|
|
50
|
+
sections[sections.length - 1]?.text.push(node.value);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!isElement(node) || isHidden(node))
|
|
54
|
+
return;
|
|
55
|
+
const hash = getProperty(node, 'id');
|
|
56
|
+
if (SECTION_HEADINGS.has(node.tagName) && hash && !REACT_GENERATED_ID.test(hash)) {
|
|
57
|
+
sections.push({ header: normalizeWhitespace(toText(node)), hash, text: [] });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const isBlock = /^(p|div|li|tr|td|th|pre|h[1-6]|blockquote|section|table|ul|ol)$/.test(node.tagName);
|
|
61
|
+
if (isBlock)
|
|
62
|
+
sections[sections.length - 1]?.text.push(' ');
|
|
63
|
+
node.children.forEach(visit);
|
|
64
|
+
if (isBlock)
|
|
65
|
+
sections[sections.length - 1]?.text.push(' ');
|
|
66
|
+
};
|
|
67
|
+
content.children.forEach(visit);
|
|
68
|
+
return sections;
|
|
69
|
+
}
|
|
70
|
+
const findById = (tree, id) => findElement(tree, (element) => getProperty(element, 'id') === id);
|
|
71
|
+
function getPageTitle(tree, content, page) {
|
|
72
|
+
const frontmatterTitle = getProperty(content, 'dataPageTitle');
|
|
73
|
+
if (frontmatterTitle)
|
|
74
|
+
return frontmatterTitle;
|
|
75
|
+
const heading = findById(tree, 'page-title');
|
|
76
|
+
return heading ? normalizeWhitespace(toText(heading)) : page;
|
|
77
|
+
}
|
|
78
|
+
function getPageDescription(tree) {
|
|
79
|
+
const meta = findElement(tree, (element) => element.tagName === 'meta' && getProperty(element, 'name') === 'description');
|
|
80
|
+
return meta && getProperty(meta, 'content');
|
|
81
|
+
}
|
|
82
|
+
export function extractSearchDocuments(html, page, { breadcrumbs, locale, version } = {}) {
|
|
83
|
+
const tree = fromHtml(html);
|
|
84
|
+
const content = findById(tree, 'content') ?? findById(tree, 'content-area');
|
|
85
|
+
if (!content)
|
|
86
|
+
return [];
|
|
87
|
+
const title = getPageTitle(tree, content, page);
|
|
88
|
+
const description = getPageDescription(tree);
|
|
89
|
+
return collectSections(content, title).flatMap((section, index) => {
|
|
90
|
+
const text = section.text.join('');
|
|
91
|
+
const searchableText = index === 0 && description ? `${description} ${text}` : text;
|
|
92
|
+
return splitContent(normalizeWhitespace(searchableText)).map((chunk, part) => ({
|
|
93
|
+
id: `${page}#${index}.${part}`,
|
|
94
|
+
page,
|
|
95
|
+
title,
|
|
96
|
+
header: section.header,
|
|
97
|
+
...(section.hash && { hash: section.hash }),
|
|
98
|
+
content: chunk,
|
|
99
|
+
...(breadcrumbs?.length && { breadcrumbs }),
|
|
100
|
+
...(locale && { locale }),
|
|
101
|
+
...(version && { version }),
|
|
102
|
+
}));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function collectNavPages(node, pages = []) {
|
|
106
|
+
if (isPage(node))
|
|
107
|
+
pages.push(node);
|
|
108
|
+
for (const child of [...decoratedNavChildren(node), ...decoratedNavDivisions(node)]) {
|
|
109
|
+
collectNavPages(child, pages);
|
|
110
|
+
}
|
|
111
|
+
return pages;
|
|
112
|
+
}
|
|
113
|
+
export function getSearchablePages(generatedNav, docsConfig, groups) {
|
|
114
|
+
const hiddenFromSearch = docsConfig?.seo?.indexing === 'all'
|
|
115
|
+
? new Map()
|
|
116
|
+
: generatePathToHiddenFromSearchDict(generatedNav);
|
|
117
|
+
const breadcrumbs = docsConfig?.navigation
|
|
118
|
+
? generatePathToBreadcrumbsMapForDocsConfig(docsConfig.navigation)
|
|
119
|
+
: new Map();
|
|
120
|
+
const languages = generatePathToLanguageDict(generatedNav);
|
|
121
|
+
const versions = generatePathToVersionDict(generatedNav);
|
|
122
|
+
const userGroups = new Set([...(groups ?? []), WILDCARD_GROUP]);
|
|
123
|
+
const pages = new Map();
|
|
124
|
+
for (const navPage of collectNavPages(generatedNav)) {
|
|
125
|
+
const slug = optionallyRemoveLeadingSlash(navPage.href);
|
|
126
|
+
if (pages.has(navPage.href) ||
|
|
127
|
+
hiddenFromSearch.get(slug) ||
|
|
128
|
+
navPage.noindex ||
|
|
129
|
+
navPage.searchable === false ||
|
|
130
|
+
!checkNavAccess(navPage, userGroups, false, false)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const locale = languages.get(slug);
|
|
134
|
+
const version = versions.get(slug);
|
|
135
|
+
pages.set(navPage.href, {
|
|
136
|
+
breadcrumbs: breadcrumbs.get(slug),
|
|
137
|
+
...(locale && { locale }),
|
|
138
|
+
...(version && { version }),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return pages;
|
|
142
|
+
}
|
|
143
|
+
const toPagePath = (route) => route.replace(/\/index$/, '') || '/';
|
|
144
|
+
export async function writeSearchIndex(exportDir, { generatedNav, docsConfig, groups, }) {
|
|
145
|
+
const searchablePages = generatedNav
|
|
146
|
+
? getSearchablePages(generatedNav, docsConfig, groups)
|
|
147
|
+
: new Map();
|
|
148
|
+
const indexedPages = new Set();
|
|
149
|
+
const documents = [];
|
|
150
|
+
for (const [route, info] of searchablePages) {
|
|
151
|
+
const page = toPagePath(route);
|
|
152
|
+
const htmlPath = path.join(exportDir, route, 'index.html');
|
|
153
|
+
if (indexedPages.has(page) || !(await fse.pathExists(htmlPath)))
|
|
154
|
+
continue;
|
|
155
|
+
indexedPages.add(page);
|
|
156
|
+
documents.push(...extractSearchDocuments(await fse.readFile(htmlPath, 'utf8'), page, info));
|
|
157
|
+
}
|
|
158
|
+
await fse.outputJson(path.join(exportDir, MINT_EXPORT_SEARCH_INDEX_PATH), documents);
|
|
159
|
+
return documents.length;
|
|
160
|
+
}
|
|
@@ -15,6 +15,7 @@ import { ErrorLog, SpinnerLog, SuccessLog, WarningLog } from '../logs.js';
|
|
|
15
15
|
import { localizeAssets } from './export-assets.js';
|
|
16
16
|
import { applyBasePathToExport, findBasePathConflict, normalizeExportBasePath, } from './export-base-path.js';
|
|
17
17
|
import { generateExportLlmsTxt } from './export-llms.js';
|
|
18
|
+
import { writeSearchIndex } from './export-search.js';
|
|
18
19
|
import { getGroupFilteredRoutes } from './getGroupFilteredRoutes.js';
|
|
19
20
|
import { setupNext } from './setupNext.js';
|
|
20
21
|
import { silentUpdateClient } from './update.js';
|
|
@@ -208,6 +209,12 @@ async function generateExportArchive(routes, outputPath, groups, basePath) {
|
|
|
208
209
|
await bundleStaticAssets(exportDir, groups, basePath);
|
|
209
210
|
await applyBasePathToExport(exportDir, basePath);
|
|
210
211
|
await writeLlmsTxt(exportDir, routes, basePath);
|
|
212
|
+
addLog(_jsx(SpinnerLog, { message: "building search index..." }));
|
|
213
|
+
const [docsConfig, generatedNav] = await Promise.all([
|
|
214
|
+
fse.readJson(path.join(NEXT_PROPS_PATH, 'docs.json')).catch(() => undefined),
|
|
215
|
+
fse.readJson(path.join(NEXT_PROPS_PATH, 'generatedDocsNav.json')).catch(() => undefined),
|
|
216
|
+
]);
|
|
217
|
+
await writeSearchIndex(exportDir, { generatedNav, docsConfig, groups });
|
|
211
218
|
addLog(_jsx(SpinnerLog, { message: "bundling external assets..." }));
|
|
212
219
|
const { failed } = await localizeAssets(exportDir);
|
|
213
220
|
addLog(_jsx(SpinnerLog, { message: "creating zip archive..." }));
|