@mintlify/previewing 4.0.1461 → 4.0.1463
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__/listener-graphql-packages.test.d.ts +1 -0
- package/dist/__tests__/listener-graphql-packages.test.js +68 -0
- package/dist/__tests__/listener-package-manifest.test.d.ts +1 -0
- package/dist/__tests__/listener-package-manifest.test.js +103 -0
- package/dist/__tests__/packages-builder.test.d.ts +1 -0
- package/dist/__tests__/packages-builder.test.js +88 -0
- package/dist/__tests__/packages-collect.test.d.ts +1 -0
- package/dist/__tests__/packages-collect.test.js +387 -0
- package/dist/__tests__/packages-dev-snippets.test.d.ts +1 -0
- package/dist/__tests__/packages-dev-snippets.test.js +457 -0
- package/dist/__tests__/packages-dev.test.d.ts +1 -0
- package/dist/__tests__/packages-dev.test.js +1304 -0
- package/dist/__tests__/packages-flag.test.d.ts +1 -0
- package/dist/__tests__/packages-flag.test.js +146 -0
- package/dist/__tests__/packages-imports.test.d.ts +1 -0
- package/dist/__tests__/packages-imports.test.js +68 -0
- package/dist/__tests__/packages-island-errors.test.d.ts +1 -0
- package/dist/__tests__/packages-island-errors.test.js +128 -0
- package/dist/__tests__/packages-serve.test.d.ts +1 -0
- package/dist/__tests__/packages-serve.test.js +73 -0
- package/dist/__tests__/packages-setup.test.d.ts +1 -0
- package/dist/__tests__/packages-setup.test.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-preview/index.js +6 -0
- package/dist/local-preview/listener/index.d.ts +1 -0
- package/dist/local-preview/listener/index.js +10 -1
- package/dist/local-preview/listener/regenerateAllSnippets.js +2 -0
- package/dist/local-preview/listener/resolve-imports-from-import-closure.js +3 -0
- package/dist/local-preview/listener/resolveAllImports.js +2 -0
- package/dist/local-preview/packages/dev.d.ts +88 -0
- package/dist/local-preview/packages/dev.js +478 -0
- package/dist/local-preview/packages/index.d.ts +8 -0
- package/dist/local-preview/packages/index.js +89 -0
- package/dist/local-preview/packages/serve.d.ts +8 -0
- package/dist/local-preview/packages/serve.js +41 -0
- package/dist/local-preview/packages/state.d.ts +6 -0
- package/dist/local-preview/packages/state.js +6 -0
- package/dist/local-preview/run.js +8 -0
- package/dist/logging-state.d.ts +1 -0
- package/dist/logging-state.js +5 -0
- package/dist/packages/builder.d.ts +6 -0
- package/dist/packages/builder.js +17 -0
- package/dist/packages/collect.d.ts +61 -0
- package/dist/packages/collect.js +240 -0
- package/dist/packages/errno.d.ts +1 -0
- package/dist/packages/errno.js +3 -0
- package/dist/packages/flag.d.ts +1 -0
- package/dist/packages/flag.js +1 -0
- package/dist/packages/index.d.ts +3 -0
- package/dist/packages/index.js +3 -0
- package/dist/packages/setup.d.ts +18 -0
- package/dist/packages/setup.js +81 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { findAndRemoveImports, hasImports, getFileCategory, normalizeOverlayKey, toPosixPath, validate, stringifyTree, isMintIgnored, replaceVariables, } from '@mintlify/common';
|
|
2
|
+
import { findAndRemoveImports, hasImports, getFileCategory, isPackageManifestPath, normalizeOverlayKey, toPosixPath, validate, stringifyTree, isMintIgnored, replaceVariables, } from '@mintlify/common';
|
|
3
3
|
import { createPage, discoverOverlayFiles, getFileListSync, getMintIgnore, MintConfigUpdater, DocsConfigUpdater, preparseMdxTree, prebuild, } from '@mintlify/prebuild';
|
|
4
4
|
import { isOverlayDocument } from '@mintlify/validation';
|
|
5
5
|
import Chalk from 'chalk';
|
|
@@ -12,7 +12,9 @@ import pathUtil from 'path';
|
|
|
12
12
|
import { CMD_EXEC_PATH, NEXT_PROPS_PATH, NEXT_PUBLIC_PATH, CLIENT_PATH } from '../../constants.js';
|
|
13
13
|
import { addChangeLog, addErrorLog, clearErrorLogs, getCurrentErrorLogs, } from '../../logging-state.js';
|
|
14
14
|
import { AddedLog, DeletedLog, EditedLog, WarningLog, InfoLog, ErrorLog } from '../../logs.js';
|
|
15
|
+
import { isNativePackagesEnabled } from '../../packages/flag.js';
|
|
15
16
|
import { reportPageCompileError, reportPageCompileSuccess } from '../on-demand-page.js';
|
|
17
|
+
import { notifyPackageInputChange, writePackageBuildProps } from '../packages/index.js';
|
|
16
18
|
import { clearDocsConfigCaches, getDocsConfigApiHash, getDocsConfigNavigationHash, getVariablesHash, hasDocsConfigApiChanged, hasDocsConfigNavigationChanged, hasNavigationApiReference, hadDocsConfigApiReference, hasDocsConfigVariablesChanged, updateDocsConfigHashCache, } from './docs-config-cache.js';
|
|
17
19
|
import { generateDependentSnippets } from './generateDependentSnippets.js';
|
|
18
20
|
import { generatePagesWithImports } from './generatePagesWithImports.js';
|
|
@@ -185,6 +187,7 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
|
|
|
185
187
|
try {
|
|
186
188
|
const importedFiles = getImportedFilesFromCache();
|
|
187
189
|
const potentialCategory = getFileCategory(filename, { importedFiles });
|
|
190
|
+
await notifyPackageInputChange(filename, potentialCategory);
|
|
188
191
|
frontmatterHashes.delete(filename);
|
|
189
192
|
removePageMetadataCacheEntryForFile(filename);
|
|
190
193
|
if (potentialCategory === 'page') {
|
|
@@ -274,6 +277,7 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
|
|
|
274
277
|
await fse.emptyDir(NEXT_PUBLIC_PATH);
|
|
275
278
|
await fse.emptyDir(NEXT_PROPS_PATH);
|
|
276
279
|
const prebuildResult = await prebuild(CMD_EXEC_PATH, options);
|
|
280
|
+
await writePackageBuildProps();
|
|
277
281
|
await initializeImportCache(CMD_EXEC_PATH, prebuildResult?.fileImportsMap);
|
|
278
282
|
await initializeFrontmatterHashCache();
|
|
279
283
|
await refreshTrackedReferencedFiles();
|
|
@@ -384,6 +388,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
384
388
|
clearErrorLogs();
|
|
385
389
|
const filePath = pathUtil.join(CMD_EXEC_PATH, filename);
|
|
386
390
|
if (shouldRegenerateGraphqlSource(filename)) {
|
|
391
|
+
await notifyPackageInputChange(filename, getFileCategory(filename, { importedFiles: getImportedFilesFromCache() }));
|
|
387
392
|
await rebuildGraphqlSchema(triggerRefresh, options);
|
|
388
393
|
return null;
|
|
389
394
|
}
|
|
@@ -391,6 +396,9 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
391
396
|
await syncImportedFileLocations(importChanges);
|
|
392
397
|
const importedFiles = getImportedFilesFromCache();
|
|
393
398
|
const potentialCategory = getFileCategory(filename, { importedFiles });
|
|
399
|
+
await notifyPackageInputChange(filename, potentialCategory);
|
|
400
|
+
if (isNativePackagesEnabled() && isPackageManifestPath(filename))
|
|
401
|
+
return null;
|
|
394
402
|
if (hasTrackedReferencedFile(filename)) {
|
|
395
403
|
if (await fse.pathExists(pathUtil.join(CMD_EXEC_PATH, 'docs.json'))) {
|
|
396
404
|
return onUpdateEvent('docs.json', triggerRefresh, options);
|
|
@@ -586,6 +594,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
|
|
|
586
594
|
await fse.emptyDir(NEXT_PUBLIC_PATH);
|
|
587
595
|
await fse.emptyDir(NEXT_PROPS_PATH);
|
|
588
596
|
const prebuildResult = await prebuild(CMD_EXEC_PATH, options);
|
|
597
|
+
await writePackageBuildProps();
|
|
589
598
|
await initializeImportCache(CMD_EXEC_PATH, prebuildResult?.fileImportsMap);
|
|
590
599
|
await initializeFrontmatterHashCache();
|
|
591
600
|
await refreshTrackedReferencedFiles();
|
|
@@ -4,6 +4,7 @@ import { outputFile } from 'fs-extra';
|
|
|
4
4
|
import { readFile } from 'fs/promises';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../../constants.js';
|
|
7
|
+
import { packageImportArgs } from '../packages/state.js';
|
|
7
8
|
import { getImportedFilesFromCache } from './importCache.js';
|
|
8
9
|
import { getCurrentVariables, getMintIgnoreGlobs, handleParseError } from './utils.js';
|
|
9
10
|
const getV2SnippetFilenames = () => {
|
|
@@ -65,6 +66,7 @@ export const regenerateAllSnippets = async () => {
|
|
|
65
66
|
const resolvedTree = await baseResolveAllImports({
|
|
66
67
|
snippets: resolvedTrees,
|
|
67
68
|
fileWithImports: importData,
|
|
69
|
+
...packageImportArgs(),
|
|
68
70
|
});
|
|
69
71
|
await outputFile(join(NEXT_PUBLIC_PATH, targetFilename), stringifyTree(resolvedTree), {
|
|
70
72
|
flag: 'w',
|
|
@@ -3,6 +3,7 @@ import { preparseMdxTree } from '@mintlify/prebuild';
|
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../../constants.js';
|
|
6
|
+
import { packageImportArgs } from '../packages/state.js';
|
|
6
7
|
import { getCurrentVariables, handleParseError } from './utils.js';
|
|
7
8
|
const getImportSourcePath = async (filename) => {
|
|
8
9
|
const generatedPath = join(NEXT_PUBLIC_PATH, filename);
|
|
@@ -70,6 +71,7 @@ const resolveSnippetImports = async (snippetsByFilename) => {
|
|
|
70
71
|
? await baseResolveAllImports({
|
|
71
72
|
snippets: resolvedSnippets,
|
|
72
73
|
fileWithImports: snippet,
|
|
74
|
+
...packageImportArgs(),
|
|
73
75
|
})
|
|
74
76
|
: snippet.tree;
|
|
75
77
|
resolvedSnippets.push({ filename: snippet.filename, tree });
|
|
@@ -94,5 +96,6 @@ export const resolveImportsFromImportClosure = async (fileWithImports) => {
|
|
|
94
96
|
return await baseResolveAllImports({
|
|
95
97
|
snippets,
|
|
96
98
|
fileWithImports,
|
|
99
|
+
...packageImportArgs(),
|
|
97
100
|
});
|
|
98
101
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { resolveAllImports as baseResolveAllImports } from '@mintlify/common';
|
|
2
|
+
import { packageImportArgs } from '../packages/state.js';
|
|
2
3
|
import { getProcessedSnippets } from './getSnippets.js';
|
|
3
4
|
export const resolveAllImports = async (fileWithImports) => {
|
|
4
5
|
const snippets = await getProcessedSnippets();
|
|
5
6
|
return await baseResolveAllImports({
|
|
6
7
|
snippets,
|
|
7
8
|
fileWithImports,
|
|
9
|
+
...packageImportArgs(),
|
|
8
10
|
});
|
|
9
11
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { BuildResult } from '@mintlify/package-builder';
|
|
2
|
+
import { type PackageRouteState } from './serve.js';
|
|
3
|
+
export declare const PACKAGE_BUILD_PROPS_FILE = "package-build.json";
|
|
4
|
+
export type PackagesDevStatus = {
|
|
5
|
+
state: 'disabled';
|
|
6
|
+
} | {
|
|
7
|
+
state: 'building';
|
|
8
|
+
} | {
|
|
9
|
+
state: 'ready';
|
|
10
|
+
} | {
|
|
11
|
+
state: 'missing-node-modules';
|
|
12
|
+
} | {
|
|
13
|
+
state: 'failed';
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
export type PackagesDevReporter = {
|
|
17
|
+
building(): void;
|
|
18
|
+
built(result: {
|
|
19
|
+
buildId: string;
|
|
20
|
+
islands: number;
|
|
21
|
+
ms: number;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
}): void;
|
|
24
|
+
failed(errors: string[], warnings: string[]): void;
|
|
25
|
+
missingNodeModules(docsRoot: string): void;
|
|
26
|
+
islandErrors(errors: string[]): void;
|
|
27
|
+
configError(message: string): void;
|
|
28
|
+
};
|
|
29
|
+
export type PackagesDevOptions = {
|
|
30
|
+
docsRoot: string;
|
|
31
|
+
buildsDir: string;
|
|
32
|
+
propsPath: string;
|
|
33
|
+
onRebuilt: () => void;
|
|
34
|
+
regenerateSnippets?: () => Promise<unknown>;
|
|
35
|
+
reporter: PackagesDevReporter;
|
|
36
|
+
debounceMs?: number;
|
|
37
|
+
pollMs?: number;
|
|
38
|
+
build?: (input: Parameters<typeof import('@mintlify/package-builder').build>[0]) => Promise<BuildResult>;
|
|
39
|
+
};
|
|
40
|
+
export declare class PackagesDev {
|
|
41
|
+
private readonly options;
|
|
42
|
+
private setup;
|
|
43
|
+
private collected;
|
|
44
|
+
private collector;
|
|
45
|
+
private adopted;
|
|
46
|
+
private status;
|
|
47
|
+
private builtInputKey;
|
|
48
|
+
private buildingInputKey;
|
|
49
|
+
private builder;
|
|
50
|
+
private buildFiles;
|
|
51
|
+
private buildFilesKnown;
|
|
52
|
+
private changedWhileBuilding;
|
|
53
|
+
private debounceTimer;
|
|
54
|
+
private pollTimer;
|
|
55
|
+
private running;
|
|
56
|
+
private rerun;
|
|
57
|
+
private collectPending;
|
|
58
|
+
private buildCount;
|
|
59
|
+
private closed;
|
|
60
|
+
private propsWrite;
|
|
61
|
+
private listedIds;
|
|
62
|
+
constructor(options: PackagesDevOptions);
|
|
63
|
+
get enabled(): boolean;
|
|
64
|
+
getStatus(): PackagesDevStatus;
|
|
65
|
+
routeState: () => PackageRouteState;
|
|
66
|
+
init(): Promise<void>;
|
|
67
|
+
onFileChange(filename: string, category: string | null | undefined): Promise<void>;
|
|
68
|
+
private buildReads;
|
|
69
|
+
private finishTracking;
|
|
70
|
+
writeProps(): Promise<void>;
|
|
71
|
+
private writePropsNow;
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
private reload;
|
|
74
|
+
private reportConfigError;
|
|
75
|
+
private importOptionsChanged;
|
|
76
|
+
private recollect;
|
|
77
|
+
private reportIslandProblems;
|
|
78
|
+
private inputKey;
|
|
79
|
+
private schedule;
|
|
80
|
+
private flush;
|
|
81
|
+
private runBuild;
|
|
82
|
+
private fail;
|
|
83
|
+
private pruneBuilds;
|
|
84
|
+
private nodeModulesStamp;
|
|
85
|
+
private watchForNodeModules;
|
|
86
|
+
private stopPolling;
|
|
87
|
+
private invalidateCompiledPages;
|
|
88
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { islandPrefixFor, isPackageManifestPath } from '@mintlify/common';
|
|
2
|
+
import { getFileListSync } from '@mintlify/prebuild';
|
|
3
|
+
import { existsSync, statSync } from 'fs';
|
|
4
|
+
import fse from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { formatDiagnostic, loadPackageBuilder } from '../../packages/builder.js';
|
|
7
|
+
import { formatProblem, PackageIslandCollector, } from '../../packages/collect.js';
|
|
8
|
+
import { errnoCode } from '../../packages/errno.js';
|
|
9
|
+
import { DocsJsonError, InvalidPackagesError, readPackagesSetup, } from '../../packages/setup.js';
|
|
10
|
+
import { PACKAGE_ROUTE_PREFIX } from './serve.js';
|
|
11
|
+
import { setPackageImportOptions } from './state.js';
|
|
12
|
+
export const PACKAGE_BUILD_PROPS_FILE = 'package-build.json';
|
|
13
|
+
const PENDING_BUILD_ID = 'pending';
|
|
14
|
+
const NODE_MODULES_MARKERS = [
|
|
15
|
+
'.package-lock.json',
|
|
16
|
+
'.modules.yaml',
|
|
17
|
+
'.yarn-integrity',
|
|
18
|
+
'.yarn-state.yml',
|
|
19
|
+
];
|
|
20
|
+
const MARKED_LOCKFILES = [
|
|
21
|
+
'package-lock.json',
|
|
22
|
+
'npm-shrinkwrap.json',
|
|
23
|
+
'pnpm-lock.yaml',
|
|
24
|
+
'yarn.lock',
|
|
25
|
+
];
|
|
26
|
+
const PAGE_EXTENSIONS = new Set(['.mdx', '.md']);
|
|
27
|
+
const sameSet = (a, b) => a.size === b.size && [...a].every((value) => b.has(value));
|
|
28
|
+
const sameVariables = (a = {}, b = {}) => Object.keys(a).length === Object.keys(b).length &&
|
|
29
|
+
Object.entries(a).every(([key, value]) => b[key] === value);
|
|
30
|
+
const mtimeOf = (file) => {
|
|
31
|
+
try {
|
|
32
|
+
return statSync(file).mtimeMs;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const RENAME_RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
39
|
+
async function replaceFile(from, to, attempts = 20) {
|
|
40
|
+
for (let attempt = 1;; attempt++) {
|
|
41
|
+
try {
|
|
42
|
+
await fse.rename(from, to);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const code = errnoCode(error);
|
|
47
|
+
if (attempt >= attempts || !code || !RENAME_RETRY_CODES.has(code))
|
|
48
|
+
throw error;
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 10 * attempt));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class PackagesDev {
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = options;
|
|
56
|
+
this.status = { state: 'disabled' };
|
|
57
|
+
this.buildFiles = new Set();
|
|
58
|
+
this.buildFilesKnown = false;
|
|
59
|
+
this.rerun = false;
|
|
60
|
+
this.collectPending = false;
|
|
61
|
+
this.buildCount = 0;
|
|
62
|
+
this.closed = false;
|
|
63
|
+
this.propsWrite = Promise.resolve();
|
|
64
|
+
this.listedIds = new Set();
|
|
65
|
+
this.routeState = () => ({
|
|
66
|
+
enabled: this.enabled,
|
|
67
|
+
buildsDir: this.options.buildsDir,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
get enabled() {
|
|
71
|
+
return this.setup !== undefined;
|
|
72
|
+
}
|
|
73
|
+
getStatus() {
|
|
74
|
+
return this.status;
|
|
75
|
+
}
|
|
76
|
+
async init() {
|
|
77
|
+
await this.reload(true);
|
|
78
|
+
if (!this.enabled) {
|
|
79
|
+
await fse.remove(this.options.buildsDir);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await fse.emptyDir(this.options.buildsDir);
|
|
83
|
+
await this.flush();
|
|
84
|
+
}
|
|
85
|
+
async onFileChange(filename, category) {
|
|
86
|
+
if (this.closed)
|
|
87
|
+
return;
|
|
88
|
+
const normalized = filename.split(path.sep).join('/');
|
|
89
|
+
const extension = path.extname(normalized).toLowerCase();
|
|
90
|
+
const manifest = isPackageManifestPath(normalized);
|
|
91
|
+
if (category === 'docsConfig' || manifest) {
|
|
92
|
+
if (await this.reload())
|
|
93
|
+
this.schedule(manifest);
|
|
94
|
+
else
|
|
95
|
+
this.reportIslandProblems();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!this.enabled)
|
|
99
|
+
return;
|
|
100
|
+
if (category === 'mintIgnore') {
|
|
101
|
+
await this.writeProps();
|
|
102
|
+
this.collectPending = true;
|
|
103
|
+
this.schedule(true);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (category === 'page') {
|
|
107
|
+
await this.recollect((collector) => collector.updatePage(normalized));
|
|
108
|
+
this.schedule(false);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (category === 'snippet-v2' ||
|
|
112
|
+
(category === 'snippet' && PAGE_EXTENSIONS.has(extension)) ||
|
|
113
|
+
Object.prototype.hasOwnProperty.call(this.collected?.snippetFiles ?? {}, `/${normalized}`)) {
|
|
114
|
+
try {
|
|
115
|
+
await this.recollect();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this.schedule(true);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.reportIslandProblems();
|
|
123
|
+
const file = `/${normalized.toLowerCase()}`;
|
|
124
|
+
if (this.buildReads(file))
|
|
125
|
+
this.schedule(true);
|
|
126
|
+
else
|
|
127
|
+
this.changedWhileBuilding?.add(file);
|
|
128
|
+
}
|
|
129
|
+
buildReads(file) {
|
|
130
|
+
if (this.buildFiles.has(file))
|
|
131
|
+
return true;
|
|
132
|
+
if (this.buildFilesKnown || this.changedWhileBuilding)
|
|
133
|
+
return false;
|
|
134
|
+
return this.builder?.isLocalInputPath(file) ?? false;
|
|
135
|
+
}
|
|
136
|
+
finishTracking(changed, files) {
|
|
137
|
+
this.changedWhileBuilding = undefined;
|
|
138
|
+
if (files)
|
|
139
|
+
this.buildFiles = new Set(files.map((file) => file.toLowerCase()));
|
|
140
|
+
this.buildFilesKnown = files !== undefined;
|
|
141
|
+
if ([...changed].some((file) => this.buildReads(file)))
|
|
142
|
+
this.rerun = true;
|
|
143
|
+
}
|
|
144
|
+
writeProps() {
|
|
145
|
+
const write = this.propsWrite.then(() => this.writePropsNow());
|
|
146
|
+
this.propsWrite = write.catch(() => { });
|
|
147
|
+
return write;
|
|
148
|
+
}
|
|
149
|
+
async writePropsNow() {
|
|
150
|
+
const target = path.join(this.options.propsPath, PACKAGE_BUILD_PROPS_FILE);
|
|
151
|
+
if (!this.setup || !this.collected) {
|
|
152
|
+
await fse.remove(target);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const buildId = this.adopted?.buildId ?? PENDING_BUILD_ID;
|
|
156
|
+
const baseUrl = `${PACKAGE_ROUTE_PREFIX}/${buildId}`;
|
|
157
|
+
const built = this.adopted?.manifest.islands ?? {};
|
|
158
|
+
const collectedIds = this.collected.islands.map((island) => island.id);
|
|
159
|
+
const islands = {};
|
|
160
|
+
for (const id of [...collectedIds, ...this.listedIds, ...Object.keys(built)]) {
|
|
161
|
+
if (Object.prototype.hasOwnProperty.call(islands, id))
|
|
162
|
+
continue;
|
|
163
|
+
const html = Object.prototype.hasOwnProperty.call(built, id) ? built[id]?.html : undefined;
|
|
164
|
+
const css = Object.prototype.hasOwnProperty.call(built, id) ? built[id]?.css : undefined;
|
|
165
|
+
islands[id] = {
|
|
166
|
+
prefix: islandPrefixFor(id),
|
|
167
|
+
...(html !== undefined && { html }),
|
|
168
|
+
...(css !== undefined && { css }),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
for (const id of collectedIds)
|
|
172
|
+
this.listedIds.add(id);
|
|
173
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
174
|
+
const props = {
|
|
175
|
+
baseUrl,
|
|
176
|
+
islands,
|
|
177
|
+
packageNames: [...this.setup.packageNames].sort(),
|
|
178
|
+
packageSnippets: [...this.collected.packageSnippets].sort(),
|
|
179
|
+
css: this.adopted?.manifest.css ?? [],
|
|
180
|
+
};
|
|
181
|
+
await fse.outputFile(tmp, JSON.stringify(props));
|
|
182
|
+
await replaceFile(tmp, target);
|
|
183
|
+
}
|
|
184
|
+
close() {
|
|
185
|
+
this.closed = true;
|
|
186
|
+
clearTimeout(this.debounceTimer);
|
|
187
|
+
this.stopPolling();
|
|
188
|
+
return this.running ?? Promise.resolve();
|
|
189
|
+
}
|
|
190
|
+
async reload(initial = false) {
|
|
191
|
+
const previous = this.setup;
|
|
192
|
+
const previousSnippets = this.collected?.packageSnippets;
|
|
193
|
+
let setup;
|
|
194
|
+
try {
|
|
195
|
+
setup = await readPackagesSetup(this.options.docsRoot);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (!(error instanceof DocsJsonError))
|
|
199
|
+
throw error;
|
|
200
|
+
if (previous) {
|
|
201
|
+
this.reportConfigError(error.problems, 'Keeping the last package setup until docs.json is fixed.');
|
|
202
|
+
if (!(error instanceof InvalidPackagesError))
|
|
203
|
+
return false;
|
|
204
|
+
if (sameVariables(previous.variables, error.variables))
|
|
205
|
+
return false;
|
|
206
|
+
setup = { ...previous, variables: error.variables };
|
|
207
|
+
}
|
|
208
|
+
else if (error instanceof InvalidPackagesError) {
|
|
209
|
+
this.reportConfigError(error.problems, 'Packages stay off until docs.json is fixed.');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (previous && setup?.packageJsonError) {
|
|
213
|
+
this.reportConfigError([setup.packageJsonError], 'Keeping the last package setup until package.json is fixed.');
|
|
214
|
+
if (sameVariables(previous.variables, setup.variables))
|
|
215
|
+
return false;
|
|
216
|
+
setup = { ...previous, variables: setup.variables };
|
|
217
|
+
}
|
|
218
|
+
this.setup = setup;
|
|
219
|
+
if (!setup) {
|
|
220
|
+
this.collected = undefined;
|
|
221
|
+
this.collector = undefined;
|
|
222
|
+
this.status = { state: 'disabled' };
|
|
223
|
+
this.builtInputKey = undefined;
|
|
224
|
+
this.buildingInputKey = undefined;
|
|
225
|
+
this.stopPolling();
|
|
226
|
+
setPackageImportOptions(undefined);
|
|
227
|
+
await this.writeProps();
|
|
228
|
+
if (previous)
|
|
229
|
+
await this.importOptionsChanged();
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
const previousCollector = this.collector;
|
|
233
|
+
this.collector = new PackageIslandCollector({
|
|
234
|
+
docsRoot: this.options.docsRoot,
|
|
235
|
+
packageNames: setup.packageNames,
|
|
236
|
+
variables: setup.variables,
|
|
237
|
+
provider: setup.packages.provider,
|
|
238
|
+
});
|
|
239
|
+
if (previousCollector)
|
|
240
|
+
this.collector.inheritSnippetTrees(previousCollector);
|
|
241
|
+
await this.recollect();
|
|
242
|
+
const namesChanged = !previous || !sameSet(previous.packageNames, setup.packageNames);
|
|
243
|
+
const snippetsChanged = !previousSnippets || !sameSet(previousSnippets, this.collected?.packageSnippets ?? new Set());
|
|
244
|
+
if (!initial && (namesChanged || snippetsChanged))
|
|
245
|
+
await this.importOptionsChanged();
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
reportConfigError(problems, note) {
|
|
249
|
+
problems.forEach((problem, index) => this.options.reporter.configError(index === problems.length - 1 ? `${problem}. ${note}` : problem));
|
|
250
|
+
}
|
|
251
|
+
async importOptionsChanged() {
|
|
252
|
+
await this.options.regenerateSnippets?.();
|
|
253
|
+
await this.invalidateCompiledPages();
|
|
254
|
+
}
|
|
255
|
+
async recollect(collect = (collector) => collector.collectAll()) {
|
|
256
|
+
const { setup, collector } = this;
|
|
257
|
+
if (!setup || !collector)
|
|
258
|
+
return;
|
|
259
|
+
const collected = await collect(collector);
|
|
260
|
+
if (collector !== this.collector)
|
|
261
|
+
return;
|
|
262
|
+
const previousSnippets = this.collected?.packageSnippets;
|
|
263
|
+
this.collected = collected;
|
|
264
|
+
setPackageImportOptions({
|
|
265
|
+
packageNames: setup.packageNames,
|
|
266
|
+
packageSnippets: collected.packageSnippets,
|
|
267
|
+
});
|
|
268
|
+
if (previousSnippets && !sameSet(previousSnippets, collected.packageSnippets)) {
|
|
269
|
+
await this.invalidateCompiledPages();
|
|
270
|
+
}
|
|
271
|
+
this.reportIslandProblems();
|
|
272
|
+
await this.writeProps();
|
|
273
|
+
}
|
|
274
|
+
reportIslandProblems() {
|
|
275
|
+
this.options.reporter.islandErrors([
|
|
276
|
+
...(this.setup?.errors ?? []),
|
|
277
|
+
...(this.collected?.errors ?? []).map(formatProblem),
|
|
278
|
+
]);
|
|
279
|
+
}
|
|
280
|
+
inputKey() {
|
|
281
|
+
return JSON.stringify({
|
|
282
|
+
packages: this.setup?.packages,
|
|
283
|
+
names: [...(this.setup?.packageNames ?? [])].sort(),
|
|
284
|
+
islands: (this.collected?.islands ?? []).map((island) => island.id).sort(),
|
|
285
|
+
snippets: this.collected?.snippetFiles,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
schedule(force) {
|
|
289
|
+
if (!this.enabled || this.closed)
|
|
290
|
+
return;
|
|
291
|
+
if (!force && this.inputKey() === (this.buildingInputKey ?? this.builtInputKey))
|
|
292
|
+
return;
|
|
293
|
+
if (force)
|
|
294
|
+
this.builtInputKey = undefined;
|
|
295
|
+
if (this.status.state === 'ready')
|
|
296
|
+
this.status = { state: 'building' };
|
|
297
|
+
clearTimeout(this.debounceTimer);
|
|
298
|
+
this.debounceTimer = setTimeout(() => void this.flush(), this.options.debounceMs ?? 300);
|
|
299
|
+
}
|
|
300
|
+
async flush() {
|
|
301
|
+
if (this.running) {
|
|
302
|
+
this.rerun = true;
|
|
303
|
+
return this.running;
|
|
304
|
+
}
|
|
305
|
+
this.running = (async () => {
|
|
306
|
+
do {
|
|
307
|
+
this.rerun = false;
|
|
308
|
+
try {
|
|
309
|
+
await this.runBuild();
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
313
|
+
this.status = { state: 'failed', message };
|
|
314
|
+
this.options.reporter.failed([message], []);
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
this.buildingInputKey = undefined;
|
|
318
|
+
}
|
|
319
|
+
} while (this.rerun && !this.closed);
|
|
320
|
+
})().finally(() => {
|
|
321
|
+
this.running = undefined;
|
|
322
|
+
});
|
|
323
|
+
return this.running;
|
|
324
|
+
}
|
|
325
|
+
async runBuild() {
|
|
326
|
+
if (this.collectPending) {
|
|
327
|
+
this.collectPending = false;
|
|
328
|
+
await this.recollect();
|
|
329
|
+
}
|
|
330
|
+
const setup = this.setup;
|
|
331
|
+
const collected = this.collected;
|
|
332
|
+
if (!setup || !collected || this.closed)
|
|
333
|
+
return;
|
|
334
|
+
const inputKey = this.inputKey();
|
|
335
|
+
this.builtInputKey = undefined;
|
|
336
|
+
this.buildingInputKey = inputKey;
|
|
337
|
+
const started = Date.now();
|
|
338
|
+
const stamp = this.nodeModulesStamp();
|
|
339
|
+
this.status = { state: 'building' };
|
|
340
|
+
this.options.reporter.building();
|
|
341
|
+
const tmpDir = path.join(this.options.buildsDir, `.tmp-${process.pid}-${++this.buildCount}`);
|
|
342
|
+
this.builder ??= await loadPackageBuilder();
|
|
343
|
+
const build = this.options.build ?? this.builder.build;
|
|
344
|
+
const changed = new Set();
|
|
345
|
+
this.changedWhileBuilding = changed;
|
|
346
|
+
let result;
|
|
347
|
+
try {
|
|
348
|
+
result = await build({
|
|
349
|
+
docsRoot: this.options.docsRoot,
|
|
350
|
+
packages: setup.packages,
|
|
351
|
+
islands: collected.islands,
|
|
352
|
+
snippetFiles: collected.snippetFiles,
|
|
353
|
+
outDir: tmpDir,
|
|
354
|
+
installer: 'local',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
this.finishTracking(changed);
|
|
359
|
+
if (this.buildingInputKey !== inputKey) {
|
|
360
|
+
await fse.remove(tmpDir);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
return this.fail(error, tmpDir, stamp);
|
|
364
|
+
}
|
|
365
|
+
this.finishTracking(changed, [...result.localFiles, ...result.missingLocalFiles]);
|
|
366
|
+
if (this.buildingInputKey !== inputKey) {
|
|
367
|
+
await fse.remove(tmpDir);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const diagnostics = result.manifest.diagnostics;
|
|
371
|
+
const errors = diagnostics.filter((d) => d.level === 'error');
|
|
372
|
+
const warnings = diagnostics.filter((d) => d.level === 'warning').map(formatDiagnostic);
|
|
373
|
+
const servable = Object.entries(result.manifest.islands).filter(([, island]) => existsSync(path.join(tmpDir, island.entry)));
|
|
374
|
+
const previous = this.adopted;
|
|
375
|
+
const servedId = `${result.manifest.buildId}-${this.buildCount}`;
|
|
376
|
+
const dir = path.join(this.options.buildsDir, servedId);
|
|
377
|
+
try {
|
|
378
|
+
if (servable.length > 0 || (result.ok && collected.islands.length === 0)) {
|
|
379
|
+
await fse.move(tmpDir, dir);
|
|
380
|
+
await this.pruneBuilds([servedId, previous?.buildId]);
|
|
381
|
+
this.adopted = { buildId: servedId, manifest: result.manifest, dir };
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
await fse.remove(tmpDir);
|
|
385
|
+
}
|
|
386
|
+
this.listedIds.clear();
|
|
387
|
+
await this.writeProps();
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
this.adopted = previous;
|
|
391
|
+
await fse.remove(dir);
|
|
392
|
+
return this.fail(error, tmpDir, stamp);
|
|
393
|
+
}
|
|
394
|
+
const dependencyDrift = diagnostics.some((d) => d.code === 'local-node-modules-drift');
|
|
395
|
+
if (!result.ok || dependencyDrift)
|
|
396
|
+
this.watchForNodeModules(stamp);
|
|
397
|
+
else
|
|
398
|
+
this.stopPolling();
|
|
399
|
+
if (errors.some((d) => d.code === 'install-missing-node-modules')) {
|
|
400
|
+
this.status = { state: 'missing-node-modules' };
|
|
401
|
+
this.options.reporter.missingNodeModules(this.options.docsRoot);
|
|
402
|
+
}
|
|
403
|
+
else if (!result.ok) {
|
|
404
|
+
const messages = errors.map(formatDiagnostic);
|
|
405
|
+
this.status = { state: 'failed', message: messages[0] ?? 'unknown error' };
|
|
406
|
+
this.options.reporter.failed(messages, warnings);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
this.status = { state: 'ready' };
|
|
410
|
+
this.options.reporter.built({
|
|
411
|
+
buildId: result.manifest.buildId,
|
|
412
|
+
islands: Object.keys(result.manifest.islands).length,
|
|
413
|
+
ms: Date.now() - started,
|
|
414
|
+
warnings,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (result.ok && inputKey === this.inputKey())
|
|
418
|
+
this.builtInputKey = inputKey;
|
|
419
|
+
this.options.onRebuilt();
|
|
420
|
+
}
|
|
421
|
+
async fail(error, tmpDir, stamp) {
|
|
422
|
+
this.listedIds.clear();
|
|
423
|
+
this.watchForNodeModules(stamp);
|
|
424
|
+
await fse.remove(tmpDir);
|
|
425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
426
|
+
this.status = { state: 'failed', message };
|
|
427
|
+
this.options.reporter.failed([message], []);
|
|
428
|
+
await this.writeProps();
|
|
429
|
+
this.options.onRebuilt();
|
|
430
|
+
}
|
|
431
|
+
async pruneBuilds(keep) {
|
|
432
|
+
const entries = await fse.readdir(this.options.buildsDir).catch(() => []);
|
|
433
|
+
await Promise.all(entries
|
|
434
|
+
.filter((entry) => !keep.includes(entry) && !entry.startsWith('.tmp-'))
|
|
435
|
+
.map((entry) => fse.remove(path.join(this.options.buildsDir, entry))));
|
|
436
|
+
}
|
|
437
|
+
nodeModulesStamp() {
|
|
438
|
+
const { docsRoot } = this.options;
|
|
439
|
+
const dir = path.join(docsRoot, 'node_modules');
|
|
440
|
+
const markers = NODE_MODULES_MARKERS.map((marker) => mtimeOf(path.join(dir, marker)));
|
|
441
|
+
const stamp = MARKED_LOCKFILES.some((file) => existsSync(path.join(docsRoot, file)))
|
|
442
|
+
? markers
|
|
443
|
+
: [mtimeOf(dir), ...markers];
|
|
444
|
+
return stamp.every((mtime) => mtime === undefined) ? undefined : stamp.join(',');
|
|
445
|
+
}
|
|
446
|
+
watchForNodeModules(initial) {
|
|
447
|
+
if (this.pollTimer || this.closed)
|
|
448
|
+
return;
|
|
449
|
+
let seen = initial;
|
|
450
|
+
let settling = false;
|
|
451
|
+
this.pollTimer = setInterval(() => {
|
|
452
|
+
const stamp = this.nodeModulesStamp();
|
|
453
|
+
if (stamp !== seen) {
|
|
454
|
+
seen = stamp;
|
|
455
|
+
settling = true;
|
|
456
|
+
}
|
|
457
|
+
else if (settling) {
|
|
458
|
+
settling = false;
|
|
459
|
+
this.schedule(true);
|
|
460
|
+
}
|
|
461
|
+
}, this.options.pollMs ?? 2000);
|
|
462
|
+
this.pollTimer.unref?.();
|
|
463
|
+
}
|
|
464
|
+
stopPolling() {
|
|
465
|
+
clearInterval(this.pollTimer);
|
|
466
|
+
this.pollTimer = undefined;
|
|
467
|
+
}
|
|
468
|
+
async invalidateCompiledPages() {
|
|
469
|
+
const { propsPath, docsRoot } = this.options;
|
|
470
|
+
for (const relative of getFileListSync(propsPath)) {
|
|
471
|
+
if (!PAGE_EXTENSIONS.has(path.extname(relative).toLowerCase()))
|
|
472
|
+
continue;
|
|
473
|
+
if (await fse.pathExists(path.join(docsRoot, relative))) {
|
|
474
|
+
await fse.utimes(path.join(propsPath, relative), 0, 0);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const setPackagesRebuiltListener: (listener: () => void) => void;
|
|
2
|
+
export declare const startPackagesDev: () => Promise<void>;
|
|
3
|
+
export declare const notifyPackageInputChange: (filename: string, category: string | null | undefined) => Promise<void>;
|
|
4
|
+
export declare const writePackageBuildProps: () => Promise<void>;
|
|
5
|
+
export declare const packageRoute: {
|
|
6
|
+
prefix: string;
|
|
7
|
+
handler: (req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise<void>;
|
|
8
|
+
};
|