@mintlify/previewing 4.0.1519 → 4.0.1521
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-base-path.test.d.ts +1 -0
- package/dist/__tests__/export-base-path.test.js +101 -0
- package/dist/__tests__/export.test.js +15 -1
- package/dist/local-preview/export-assets.d.ts +1 -0
- package/dist/local-preview/export-assets.js +4 -1
- package/dist/local-preview/export-base-path.d.ts +6 -0
- package/dist/local-preview/export-base-path.js +43 -0
- package/dist/local-preview/export-llms.d.ts +1 -1
- package/dist/local-preview/export-llms.js +2 -2
- package/dist/local-preview/export.d.ts +2 -1
- package/dist/local-preview/export.js +32 -13
- package/dist/local-preview/setupNext.d.ts +3 -1
- package/dist/local-preview/setupNext.js +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/export-scripts/serve.js +16 -3
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { rewriteCssUrls } from '../local-preview/export-assets.js';
|
|
5
|
+
import { applyBasePathToExport, findBasePathConflict, normalizeExportBasePath, prefixNextAssetPaths, prefixNextStaticPaths, prefixNextStaticPathsInPayload, } from '../local-preview/export-base-path.js';
|
|
6
|
+
import { generateExportLlmsTxt } from '../local-preview/export-llms.js';
|
|
7
|
+
const push = (payload) => `<script>self.__next_f.push(${JSON.stringify([1, payload])})</script>`;
|
|
8
|
+
const readPayload = (html) => [...html.matchAll(/self\.__next_f\.push\((\[1,".*?"\])\)<\/script>/g)]
|
|
9
|
+
.map((match) => {
|
|
10
|
+
const parsed = JSON.parse(match[1] ?? '');
|
|
11
|
+
return Array.isArray(parsed) && typeof parsed[1] === 'string' ? parsed[1] : '';
|
|
12
|
+
})
|
|
13
|
+
.join('');
|
|
14
|
+
describe('normalizeExportBasePath', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
[undefined, ''],
|
|
17
|
+
['', ''],
|
|
18
|
+
['/', ''],
|
|
19
|
+
['docs', '/docs'],
|
|
20
|
+
['/docs/', '/docs'],
|
|
21
|
+
['/docs/v1', '/docs/v1'],
|
|
22
|
+
])('normalizes %s to %s', (input, expected) => {
|
|
23
|
+
expect(normalizeExportBasePath(input)).toBe(expected);
|
|
24
|
+
});
|
|
25
|
+
it.each(['/docs/../etc', '/docs//v1', '/docs?x=1', '/my docs', '/do"cs'])('rejects %s', (input) => {
|
|
26
|
+
expect(() => normalizeExportBasePath(input)).toThrow('invalid base path');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('findBasePathConflict', () => {
|
|
30
|
+
it('returns the first page that starts with the base path', () => {
|
|
31
|
+
expect(findBasePathConflict(['/', '/quickstart', '/docs/intro'], '/docs')).toBe('/docs/intro');
|
|
32
|
+
expect(findBasePathConflict(['/', '/docs'], '/docs')).toBe('/docs');
|
|
33
|
+
});
|
|
34
|
+
it('ignores pages that only share a prefix, and exports without a base path', () => {
|
|
35
|
+
expect(findBasePathConflict(['/docsearch', '/guides/docs'], '/docs')).toBeUndefined();
|
|
36
|
+
expect(findBasePathConflict(['/docs/intro'], '')).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('prefixNextAssetPaths', () => {
|
|
40
|
+
it('prefixes chunk base and asset literals but not runtime concatenations', () => {
|
|
41
|
+
const source = 'let t="/_next/",r=1;e.v("/_next/static/media/worker.js");x={path:"/_next/image"};let a=r.assetPrefix+"/_next/"+p';
|
|
42
|
+
expect(prefixNextAssetPaths(source, '/docs')).toBe('let t="/docs/_next/",r=1;e.v("/docs/_next/static/media/worker.js");x={path:"/docs/_next/image"};let a=r.assetPrefix+"/_next/"+p');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('prefixNextStaticPaths', () => {
|
|
46
|
+
it('prefixes static attributes and flight chunk lists but not escaped text', () => {
|
|
47
|
+
const html = [
|
|
48
|
+
'<script src="/_next/static/chunks/a.js" async=""></script>',
|
|
49
|
+
'<link rel="stylesheet" href="/docs/_next/static/css/b.css"/>',
|
|
50
|
+
push('9:I[123,["/_next/static/chunks/a.js","/_next/static/chunks/c.js"],"default"]\n'),
|
|
51
|
+
push('b:["$","code",null,{"children":"\\"/_next/static/chunks/escaped.js\\""}]\n'),
|
|
52
|
+
].join('');
|
|
53
|
+
const result = prefixNextStaticPaths(html, '/docs');
|
|
54
|
+
expect(result).toContain('<script src="/docs/_next/static/chunks/a.js"');
|
|
55
|
+
expect(result).toContain('href="/docs/_next/static/css/b.css"');
|
|
56
|
+
const payload = readPayload(result);
|
|
57
|
+
expect(payload).toContain('9:I[123,["/docs/_next/static/chunks/a.js","/docs/_next/static/chunks/c.js"]');
|
|
58
|
+
expect(payload).toContain('\\"/_next/static/chunks/escaped.js\\"');
|
|
59
|
+
});
|
|
60
|
+
it('leaves html untouched without a base path', () => {
|
|
61
|
+
const html = '<script src="/_next/static/chunks/a.js"></script>' +
|
|
62
|
+
push('9:I[1,["/_next/static/chunks/a.js"]]\n');
|
|
63
|
+
expect(prefixNextStaticPaths(html, '')).toBe(html);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('prefixNextStaticPathsInPayload', () => {
|
|
67
|
+
it('prefixes chunk paths in a saved rsc payload but not escaped text', () => {
|
|
68
|
+
const payload = `1:I[123,["/_next/static/chunks/a.js"],"default"]\n3:["$","p",null,{"children":"\\"/_next/static/x.js\\""}]\n`;
|
|
69
|
+
expect(prefixNextStaticPathsInPayload(payload, '/docs')).toBe(payload.replace('["/_next/static/chunks/a.js"]', '["/docs/_next/static/chunks/a.js"]'));
|
|
70
|
+
expect(prefixNextStaticPathsInPayload(payload, '')).toBe(payload);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('applyBasePathToExport', () => {
|
|
74
|
+
let exportDir;
|
|
75
|
+
beforeEach(async () => {
|
|
76
|
+
exportDir = await fse.mkdtemp(path.join(os.tmpdir(), 'mint-export-base-path-test-'));
|
|
77
|
+
});
|
|
78
|
+
afterEach(async () => {
|
|
79
|
+
await fse.remove(exportDir);
|
|
80
|
+
});
|
|
81
|
+
it('patches html and turbopack chunks inside the export', async () => {
|
|
82
|
+
await fse.outputFile(path.join(exportDir, 'guides', 'index.html'), '<script src="/_next/static/chunks/a.js"></script>');
|
|
83
|
+
await fse.outputFile(path.join(exportDir, '_next', 'static', 'chunks', 'turbopack-1.js'), 'let t="/_next/";');
|
|
84
|
+
await fse.outputFile(path.join(exportDir, 'images', 'app.js'), 'let t="/_next/";');
|
|
85
|
+
await fse.outputFile(path.join(exportDir, 'guides', 'index.txt'), '1:I[1,["/_next/static/chunks/a.js"],"default"]\n');
|
|
86
|
+
await applyBasePathToExport(exportDir, '/docs');
|
|
87
|
+
await expect(fse.readFile(path.join(exportDir, 'guides', 'index.html'), 'utf8')).resolves.toBe('<script src="/docs/_next/static/chunks/a.js"></script>');
|
|
88
|
+
await expect(fse.readFile(path.join(exportDir, '_next', 'static', 'chunks', 'turbopack-1.js'), 'utf8')).resolves.toBe('let t="/docs/_next/";');
|
|
89
|
+
await expect(fse.readFile(path.join(exportDir, 'images', 'app.js'), 'utf8')).resolves.toBe('let t="/_next/";');
|
|
90
|
+
await expect(fse.readFile(path.join(exportDir, 'guides', 'index.txt'), 'utf8')).resolves.toBe('1:I[1,["/docs/_next/static/chunks/a.js"],"default"]\n');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('base path aware css and llms.txt', () => {
|
|
94
|
+
it('makes root-relative urls in local css relative to the stylesheet', () => {
|
|
95
|
+
const css = rewriteCssUrls('@font-face{src:url(/_next/static/media/inter.woff2)}', undefined, '/_next/static/chunks/app.css', () => undefined);
|
|
96
|
+
expect(css).toBe('@font-face{src:url(../media/inter.woff2)}');
|
|
97
|
+
});
|
|
98
|
+
it('prefixes llms.txt links with the base path', () => {
|
|
99
|
+
expect(generateExportLlmsTxt({ name: 'Conduit' }, [{ href: '/quickstart', title: 'Quickstart' }], ['/quickstart'], '/docs')).toBe('# Conduit\n\n## Docs\n\n- [Quickstart](/docs/quickstart)\n');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fse from 'fs-extra';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { generatePages, writeStaticUserInfo } from '../local-preview/export.js';
|
|
4
|
+
import { generatePages, withIndexRouteAliases, writeStaticUserInfo, } from '../local-preview/export.js';
|
|
5
5
|
import { getGroupFilteredRoutes } from '../local-preview/getGroupFilteredRoutes.js';
|
|
6
6
|
describe('getGroupFilteredRoutes', () => {
|
|
7
7
|
it('prunes routes outside the selected groups', () => {
|
|
@@ -21,6 +21,20 @@ describe('getGroupFilteredRoutes', () => {
|
|
|
21
21
|
expect(routes).toEqual(['/', '/index', '/quickstart', '/authenticated']);
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
|
+
describe('withIndexRouteAliases', () => {
|
|
25
|
+
it('adds the parent route for nested index pages', () => {
|
|
26
|
+
expect(withIndexRouteAliases(['/', '/index', '/agent/index', '/agent/slack'])).toEqual([
|
|
27
|
+
'/',
|
|
28
|
+
'/index',
|
|
29
|
+
'/agent/index',
|
|
30
|
+
'/agent',
|
|
31
|
+
'/agent/slack',
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
it('does not duplicate routes that are already listed', () => {
|
|
35
|
+
expect(withIndexRouteAliases(['/cli', '/cli/index'])).toEqual(['/cli', '/cli/index']);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
24
38
|
describe('generatePages', () => {
|
|
25
39
|
let outputDir;
|
|
26
40
|
beforeEach(async () => {
|
|
@@ -3,6 +3,7 @@ export type LocalizeAssetsResult = {
|
|
|
3
3
|
downloaded: string[];
|
|
4
4
|
failed: string[];
|
|
5
5
|
};
|
|
6
|
+
export declare function walkFiles(dir: string): AsyncGenerator<string>;
|
|
6
7
|
export declare function collectAssetPaths(exportDir: string): Promise<Set<string>>;
|
|
7
8
|
export declare function rewriteCssUrls(css: string, cssUrl: string | undefined, cssAssetPath: string, onAsset: (assetPath: string) => void): string;
|
|
8
9
|
export declare function localizeAssets(exportDir: string, fetchAsset?: FetchAsset): Promise<LocalizeAssetsResult>;
|
|
@@ -6,7 +6,7 @@ const ASSET_PATH_PATTERN = new RegExp(`${MINT_EXPORT_ASSETS_PREFIX.replace(/\//g
|
|
|
6
6
|
const CSS_URL_PATTERN = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
|
|
7
7
|
const CODE_LANGUAGE_PATTERN = /\slanguage="([^"]+)"/g;
|
|
8
8
|
const CONCURRENCY = 10;
|
|
9
|
-
async function* walkFiles(dir) {
|
|
9
|
+
export async function* walkFiles(dir) {
|
|
10
10
|
for (const entry of await fse.readdir(dir, { withFileTypes: true })) {
|
|
11
11
|
const entryPath = path.join(dir, entry.name);
|
|
12
12
|
if (entry.isDirectory()) {
|
|
@@ -54,6 +54,9 @@ export function rewriteCssUrls(css, cssUrl, cssAssetPath, onAsset) {
|
|
|
54
54
|
return css.replace(CSS_URL_PATTERN, (match, quote, rawUrl) => {
|
|
55
55
|
if (rawUrl.startsWith('data:') || rawUrl.startsWith('#'))
|
|
56
56
|
return match;
|
|
57
|
+
if (!cssUrl && rawUrl.startsWith('/') && !rawUrl.startsWith('//')) {
|
|
58
|
+
return `url(${quote}${path.posix.relative(path.posix.dirname(cssAssetPath), rawUrl)}${quote})`;
|
|
59
|
+
}
|
|
57
60
|
let absoluteUrl;
|
|
58
61
|
try {
|
|
59
62
|
absoluteUrl = new URL(rawUrl.startsWith('//') ? `https:${rawUrl}` : rawUrl, cssUrl).toString();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function normalizeExportBasePath(value: string | undefined): string;
|
|
2
|
+
export declare const findBasePathConflict: (routes: string[], basePath: string) => string | undefined;
|
|
3
|
+
export declare const prefixNextAssetPaths: (source: string, basePath: string) => string;
|
|
4
|
+
export declare const prefixNextStaticPathsInPayload: (payload: string, basePath: string) => string;
|
|
5
|
+
export declare const prefixNextStaticPaths: (html: string, basePath: string) => string;
|
|
6
|
+
export declare function applyBasePathToExport(exportDir: string, basePath: string): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { normalizeBasePath } from '@mintlify/common';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { walkFiles } from './export-assets.js';
|
|
5
|
+
const CHUNK_BASE_PATH_PATTERN = /(?<=[=(,:]\s*)(["'])\/_next\//g;
|
|
6
|
+
const NEXT_STATIC_IN_PAYLOAD_PATTERN = /(?<!\\)"\/_next\/static\//g;
|
|
7
|
+
const NEXT_STATIC_IN_HTML_PATTERN = /(?<!\\)(\\?)"\/_next\/static\//g;
|
|
8
|
+
const BASE_PATH_PATTERN = /^(\/[\w-]+)+$/;
|
|
9
|
+
export function normalizeExportBasePath(value) {
|
|
10
|
+
const basePath = normalizeBasePath(value?.trim());
|
|
11
|
+
if (basePath && !BASE_PATH_PATTERN.test(basePath)) {
|
|
12
|
+
throw new Error(`invalid base path: ${value}`);
|
|
13
|
+
}
|
|
14
|
+
return basePath;
|
|
15
|
+
}
|
|
16
|
+
export const findBasePathConflict = (routes, basePath) => basePath
|
|
17
|
+
? routes.find((route) => route === basePath || route.startsWith(`${basePath}/`))
|
|
18
|
+
: undefined;
|
|
19
|
+
export const prefixNextAssetPaths = (source, basePath) => source.replace(CHUNK_BASE_PATH_PATTERN, (_match, quote) => `${quote}${basePath}/_next/`);
|
|
20
|
+
export const prefixNextStaticPathsInPayload = (payload, basePath) => payload.replace(NEXT_STATIC_IN_PAYLOAD_PATTERN, `"${basePath}/_next/static/`);
|
|
21
|
+
export const prefixNextStaticPaths = (html, basePath) => html.replace(NEXT_STATIC_IN_HTML_PATTERN, `$1"${basePath}/_next/static/`);
|
|
22
|
+
export async function applyBasePathToExport(exportDir, basePath) {
|
|
23
|
+
const nextStaticDir = path.join(exportDir, '_next', 'static');
|
|
24
|
+
if (!basePath || !(await fse.pathExists(nextStaticDir)))
|
|
25
|
+
return;
|
|
26
|
+
for await (const file of walkFiles(exportDir)) {
|
|
27
|
+
if (file.endsWith('.html') || path.basename(file) === 'index.txt') {
|
|
28
|
+
const source = await fse.readFile(file, 'utf8');
|
|
29
|
+
const patched = file.endsWith('.html')
|
|
30
|
+
? prefixNextStaticPaths(source, basePath)
|
|
31
|
+
: prefixNextStaticPathsInPayload(source, basePath);
|
|
32
|
+
if (patched !== source)
|
|
33
|
+
await fse.writeFile(file, patched, 'utf8');
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!file.startsWith(nextStaticDir + path.sep) || !file.endsWith('.js'))
|
|
37
|
+
continue;
|
|
38
|
+
const source = await fse.readFile(file, 'utf8');
|
|
39
|
+
const patched = prefixNextAssetPaths(source, basePath);
|
|
40
|
+
if (patched !== source)
|
|
41
|
+
await fse.writeFile(file, patched, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function generateExportLlmsTxt(docsConfig: unknown, generatedNav: unknown, routes: string[]): string;
|
|
1
|
+
export declare function generateExportLlmsTxt(docsConfig: unknown, generatedNav: unknown, routes: string[], basePath?: string): string;
|
|
@@ -24,7 +24,7 @@ function collectPages(entry, pages) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
const escapeLinkText = (text) => text.replace(/([[\]])/g, '\\$1');
|
|
27
|
-
export function generateExportLlmsTxt(docsConfig, generatedNav, routes) {
|
|
27
|
+
export function generateExportLlmsTxt(docsConfig, generatedNav, routes, basePath = '') {
|
|
28
28
|
const config = isRecord(docsConfig) ? docsConfig : {};
|
|
29
29
|
const name = getString(config, 'name') ?? 'Documentation';
|
|
30
30
|
const description = getString(config, 'description');
|
|
@@ -34,6 +34,6 @@ export function generateExportLlmsTxt(docsConfig, generatedNav, routes) {
|
|
|
34
34
|
const exportedRoutes = new Set(routes);
|
|
35
35
|
const lines = [...pages.values()]
|
|
36
36
|
.filter((page) => exportedRoutes.has(page.href))
|
|
37
|
-
.map((page) => `- [${escapeLinkText(page.title)}](${page.href})${page.description ? `: ${page.description}` : ''}\n`);
|
|
37
|
+
.map((page) => `- [${escapeLinkText(page.title)}](${basePath}${page.href})${page.description ? `: ${page.description}` : ''}\n`);
|
|
38
38
|
return header + lines.join('');
|
|
39
39
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArgumentsCamelCase } from 'yargs';
|
|
2
|
+
export declare const withIndexRouteAliases: (routes: string[]) => string[];
|
|
2
3
|
export declare function generatePages(baseUrl: string, routes: string[], outputDir: string, rootRoute?: string): Promise<number>;
|
|
3
4
|
export declare function writeStaticUserInfo(tmpDir: string, groups: string[] | undefined): Promise<void>;
|
|
4
|
-
export declare function writeLlmsTxt(tmpDir: string, routes: string[]): Promise<void>;
|
|
5
|
+
export declare function writeLlmsTxt(tmpDir: string, routes: string[], basePath?: string): Promise<void>;
|
|
5
6
|
export declare const exportSite: (argv: ArgumentsCamelCase) => Promise<void>;
|
|
@@ -13,6 +13,7 @@ import { CLIENT_PATH, CMD_EXEC_PATH, VERSION_PATH, NEXT_PUBLIC_PATH, NEXT_PROPS_
|
|
|
13
13
|
import { addLog, clearLogs } from '../logging-state.js';
|
|
14
14
|
import { ErrorLog, SpinnerLog, SuccessLog, WarningLog } from '../logs.js';
|
|
15
15
|
import { localizeAssets } from './export-assets.js';
|
|
16
|
+
import { applyBasePathToExport, findBasePathConflict, normalizeExportBasePath, } from './export-base-path.js';
|
|
16
17
|
import { generateExportLlmsTxt } from './export-llms.js';
|
|
17
18
|
import { getGroupFilteredRoutes } from './getGroupFilteredRoutes.js';
|
|
18
19
|
import { setupNext } from './setupNext.js';
|
|
@@ -37,6 +38,10 @@ function prepareRoutesForExport(routes, groups) {
|
|
|
37
38
|
rootRoute: firstAccessibleRoute,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
const INDEX_SUFFIX = '/index';
|
|
42
|
+
export const withIndexRouteAliases = (routes) => [
|
|
43
|
+
...new Set(routes.flatMap((route) => route.endsWith(INDEX_SUFFIX) ? [route, route.slice(0, -INDEX_SUFFIX.length) || '/'] : [route])),
|
|
44
|
+
];
|
|
40
45
|
async function withExportConsoleSilenced(task) {
|
|
41
46
|
const originalConsole = {
|
|
42
47
|
log: console.log,
|
|
@@ -122,9 +127,9 @@ export async function generatePages(baseUrl, routes, outputDir, rootRoute) {
|
|
|
122
127
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, routes.length) }, () => worker()));
|
|
123
128
|
return failedPageCount;
|
|
124
129
|
}
|
|
125
|
-
async function startServer() {
|
|
130
|
+
async function startServer(basePath) {
|
|
126
131
|
const app = express();
|
|
127
|
-
const requestHandler = await setupNext();
|
|
132
|
+
const requestHandler = await setupNext(basePath ? { assetPrefix: basePath } : undefined);
|
|
128
133
|
app.use('/', express.static(NEXT_PUBLIC_PATH));
|
|
129
134
|
app.all('*', (req, res) => requestHandler(req, res));
|
|
130
135
|
const server = createServer(app);
|
|
@@ -148,7 +153,7 @@ export async function writeStaticUserInfo(tmpDir, groups) {
|
|
|
148
153
|
user: { groups },
|
|
149
154
|
});
|
|
150
155
|
}
|
|
151
|
-
async function bundleStaticAssets(tmpDir, groups) {
|
|
156
|
+
async function bundleStaticAssets(tmpDir, groups, basePath) {
|
|
152
157
|
const nextStaticSrc = path.join(CLIENT_PATH, '.next', 'static');
|
|
153
158
|
if (await pathExists(nextStaticSrc)) {
|
|
154
159
|
await fse.copy(nextStaticSrc, path.join(tmpDir, '_next', 'static'));
|
|
@@ -162,12 +167,13 @@ async function bundleStaticAssets(tmpDir, groups) {
|
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
await writeStaticUserInfo(tmpDir, groups);
|
|
165
|
-
await fse.
|
|
170
|
+
const serveScript = await fse.readFile(path.join(EXPORT_SCRIPTS_DIR, 'serve.js'), 'utf8');
|
|
171
|
+
await fse.writeFile(path.join(tmpDir, 'serve.js'), serveScript.replace("const BASE_PATH = '';", `const BASE_PATH = ${JSON.stringify(basePath)};`));
|
|
166
172
|
await fse.copy(path.join(EXPORT_SCRIPTS_DIR, 'README.md'), path.join(tmpDir, 'README.md'));
|
|
167
173
|
await fse.copy(path.join(EXPORT_SCRIPTS_DIR, 'start-docs.sh'), path.join(tmpDir, 'Start Docs.command'));
|
|
168
174
|
await fse.copy(path.join(EXPORT_SCRIPTS_DIR, 'start-docs.bat'), path.join(tmpDir, 'Start Docs.bat'));
|
|
169
175
|
}
|
|
170
|
-
export async function writeLlmsTxt(tmpDir, routes) {
|
|
176
|
+
export async function writeLlmsTxt(tmpDir, routes, basePath = '') {
|
|
171
177
|
const llmsTxtPath = path.join(tmpDir, 'llms.txt');
|
|
172
178
|
if (await pathExists(llmsTxtPath))
|
|
173
179
|
return;
|
|
@@ -175,16 +181,17 @@ export async function writeLlmsTxt(tmpDir, routes) {
|
|
|
175
181
|
fse.readJson(path.join(NEXT_PROPS_PATH, 'docs.json')).catch(() => undefined),
|
|
176
182
|
fse.readJson(path.join(NEXT_PROPS_PATH, 'generatedDocsNav.json')).catch(() => undefined),
|
|
177
183
|
]);
|
|
178
|
-
await fse.writeFile(llmsTxtPath, generateExportLlmsTxt(docsConfig, generatedNav, routes), 'utf8');
|
|
184
|
+
await fse.writeFile(llmsTxtPath, generateExportLlmsTxt(docsConfig, generatedNav, routes, basePath), 'utf8');
|
|
179
185
|
}
|
|
180
186
|
async function closeServer(server) {
|
|
181
187
|
await new Promise((resolve) => {
|
|
182
188
|
server.close(() => resolve());
|
|
183
189
|
});
|
|
184
190
|
}
|
|
185
|
-
async function generateExportArchive(routes, outputPath, groups) {
|
|
186
|
-
const { server, port } = await startServer();
|
|
187
|
-
const { routes:
|
|
191
|
+
async function generateExportArchive(routes, outputPath, groups, basePath) {
|
|
192
|
+
const { server, port } = await startServer(basePath);
|
|
193
|
+
const { routes: preparedRoutes, rootRoute } = prepareRoutesForExport(routes, groups);
|
|
194
|
+
const routesToGenerate = withIndexRouteAliases(preparedRoutes);
|
|
188
195
|
let tmpDir;
|
|
189
196
|
try {
|
|
190
197
|
clearLogs();
|
|
@@ -198,8 +205,9 @@ async function generateExportArchive(routes, outputPath, groups) {
|
|
|
198
205
|
throw new Error(`failed to export: ${failedPageCount} page(s) could not be generated.`);
|
|
199
206
|
}
|
|
200
207
|
addLog(_jsx(SpinnerLog, { message: "collecting static assets..." }));
|
|
201
|
-
await bundleStaticAssets(exportDir, groups);
|
|
202
|
-
await
|
|
208
|
+
await bundleStaticAssets(exportDir, groups, basePath);
|
|
209
|
+
await applyBasePathToExport(exportDir, basePath);
|
|
210
|
+
await writeLlmsTxt(exportDir, routes, basePath);
|
|
203
211
|
addLog(_jsx(SpinnerLog, { message: "bundling external assets..." }));
|
|
204
212
|
const { failed } = await localizeAssets(exportDir);
|
|
205
213
|
addLog(_jsx(SpinnerLog, { message: "creating zip archive..." }));
|
|
@@ -225,6 +233,13 @@ export const exportSite = async (argv) => {
|
|
|
225
233
|
const clientVersion = argv['client-version'];
|
|
226
234
|
const localClientVersion = argv['local-client-version'];
|
|
227
235
|
const groups = argv.groups;
|
|
236
|
+
let basePath = '';
|
|
237
|
+
try {
|
|
238
|
+
basePath = normalizeExportBasePath(argv.basePath);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
await fatal(err instanceof Error ? err.message : 'invalid base path');
|
|
242
|
+
}
|
|
228
243
|
const cliVersion = argv.cliVersion;
|
|
229
244
|
const disableOpenApi = argv.disableOpenapi;
|
|
230
245
|
const outputPath = path.resolve(argv.output);
|
|
@@ -268,10 +283,14 @@ export const exportSite = async (argv) => {
|
|
|
268
283
|
catch (err) {
|
|
269
284
|
await fatal(err instanceof Error && err.message ? err.message : 'failed to collect routes');
|
|
270
285
|
}
|
|
286
|
+
const conflictingRoute = findBasePathConflict(routes, basePath);
|
|
287
|
+
if (conflictingRoute) {
|
|
288
|
+
await fatal(`--base-path ${basePath} conflicts with the page ${conflictingRoute}. Pages can't start with the base path, so pick a different one.`);
|
|
289
|
+
}
|
|
271
290
|
addLog(_jsx(SpinnerLog, { message: "starting local server..." }));
|
|
272
|
-
process.env.MINT_EXPORT = JSON.stringify({ groups });
|
|
291
|
+
process.env.MINT_EXPORT = JSON.stringify({ groups, basePath: basePath || undefined });
|
|
273
292
|
try {
|
|
274
|
-
await generateExportArchive(routes, outputPath, groups);
|
|
293
|
+
await generateExportArchive(routes, outputPath, groups, basePath);
|
|
275
294
|
}
|
|
276
295
|
catch (err) {
|
|
277
296
|
await fatal(err instanceof Error && err.message ? err.message : 'export failed');
|
|
@@ -22,7 +22,7 @@ import { CLIENT_PATH, NEXT_CONFIG_PATH, NEXT_ROUTER_SERVER_PATH, NEXT_SIDE_EFFEC
|
|
|
22
22
|
*
|
|
23
23
|
* @returns the request handler provided by next.js
|
|
24
24
|
*/
|
|
25
|
-
export const setupNext = async () => {
|
|
25
|
+
export const setupNext = async ({ assetPrefix } = {}) => {
|
|
26
26
|
const hostname = process.env.HOSTNAME || 'localhost';
|
|
27
27
|
let config;
|
|
28
28
|
try {
|
|
@@ -38,7 +38,7 @@ export const setupNext = async () => {
|
|
|
38
38
|
}
|
|
39
39
|
throw err;
|
|
40
40
|
}
|
|
41
|
-
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(config);
|
|
41
|
+
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(assetPrefix === undefined ? config : { ...config, assetPrefix });
|
|
42
42
|
// The server.js provided by next.js's standalone build does a similar import of this file.
|
|
43
43
|
// Not sure what side effects are being created, but want to change as little as possible.
|
|
44
44
|
// Also, Windows requires us to use `pathToFileURL` (see #899)
|