@mintlify/previewing 4.0.1461 → 4.0.1462
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__/packages-check.test.d.ts +1 -0
- package/dist/__tests__/packages-check.test.js +254 -0
- package/dist/__tests__/packages-setup.test.d.ts +1 -0
- package/dist/__tests__/packages-setup.test.js +102 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/packages/builder.d.ts +4 -0
- package/dist/packages/builder.js +16 -0
- package/dist/packages/check.d.ts +25 -0
- package/dist/packages/check.js +132 -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 +4 -0
- package/dist/packages/index.js +4 -0
- package/dist/packages/setup.d.ts +16 -0
- package/dist/packages/setup.js +77 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { classifyPackageSources, extractImportSources, extractPagePackageIslands, findAndRemoveImports, findPackageSnippets, getAST, hasImports, isPackageSnippetPath, isSnippetExtension, mergePackageIslands, parseFrontmatter, replaceVariables, resolveAllImports, resolveImportPath, resolveSnippetImports, stringifyTree, } from '@mintlify/common';
|
|
2
|
+
import { createPage, getFileListSync, getMintIgnore } from '@mintlify/prebuild';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import { extname, join, sep } from 'path';
|
|
5
|
+
export const formatProblem = ({ file, line, message }) => `${file}${line === undefined ? '' : `:${line}`} ${message}`;
|
|
6
|
+
const withSlash = (filename) => (filename.startsWith('/') ? filename : `/${filename}`);
|
|
7
|
+
const displayPath = (filename) => filename.replace(/^\/+/, '');
|
|
8
|
+
const mayImport = (content) => /\bimport\b/.test(content);
|
|
9
|
+
const parseOrUndefined = (content, filename) => {
|
|
10
|
+
try {
|
|
11
|
+
return getAST(content, filename);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const readSources = async (docsRoot, mintIgnore) => {
|
|
18
|
+
const filenames = getFileListSync(docsRoot, docsRoot, mintIgnore)
|
|
19
|
+
.filter((filename) => isSnippetExtension(extname(filename).slice(1)))
|
|
20
|
+
.sort();
|
|
21
|
+
const sources = await Promise.all(filenames.map(async (filename) => {
|
|
22
|
+
const content = await readFile(join(docsRoot, filename), 'utf8').catch(() => undefined);
|
|
23
|
+
return content === undefined ? [] : [{ path: displayPath(filename), content }];
|
|
24
|
+
}));
|
|
25
|
+
return sources.flat();
|
|
26
|
+
};
|
|
27
|
+
const createLegacySnippetTreeMap = async (snippets, variables) => {
|
|
28
|
+
if (snippets.length === 0)
|
|
29
|
+
return {};
|
|
30
|
+
const { createPackageSnippetTreeMap } = await import('@mintlify/common/server-mdx');
|
|
31
|
+
return createPackageSnippetTreeMap(snippets, variables);
|
|
32
|
+
};
|
|
33
|
+
const resolveSnippets = async (snippets, variables, packages, onWarning) => {
|
|
34
|
+
const parsed = [];
|
|
35
|
+
for (const { filename, content } of snippets) {
|
|
36
|
+
const tree = parseOrUndefined(replaceVariables(content, variables), filename);
|
|
37
|
+
if (tree)
|
|
38
|
+
parsed.push({ ...(await findAndRemoveImports(tree)), filename });
|
|
39
|
+
}
|
|
40
|
+
return resolveSnippetImports(parsed, {
|
|
41
|
+
packages,
|
|
42
|
+
onWarning: (warning, filename) => onWarning(filename, warning.message),
|
|
43
|
+
skip: (filename) => packages.packageSnippets?.has(filename) ?? false,
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
export const resolvePageContent = async ({ page, docsRoot, snippets, packages, onWarning, }) => {
|
|
47
|
+
const tree = parseOrUndefined(page.content, page.filename);
|
|
48
|
+
if (!tree)
|
|
49
|
+
return undefined;
|
|
50
|
+
const importsResponse = await findAndRemoveImports(tree);
|
|
51
|
+
const content = hasImports(importsResponse)
|
|
52
|
+
? stringifyTree(await resolveAllImports({
|
|
53
|
+
snippets,
|
|
54
|
+
fileWithImports: { ...importsResponse, filename: page.filename },
|
|
55
|
+
onWarning: (warning) => onWarning?.(warning.message),
|
|
56
|
+
packages,
|
|
57
|
+
}))
|
|
58
|
+
: page.content;
|
|
59
|
+
const { pageContent } = await createPage(displayPath(page.filename), content, docsRoot, [], [], () => { });
|
|
60
|
+
return parseFrontmatter(pageContent).body;
|
|
61
|
+
};
|
|
62
|
+
export class PackageIslandCollector {
|
|
63
|
+
constructor(input) {
|
|
64
|
+
this.input = input;
|
|
65
|
+
this.snippetSources = [];
|
|
66
|
+
this.snippets = [];
|
|
67
|
+
this.snippetTreeMap = {};
|
|
68
|
+
this.packageSnippets = new Set();
|
|
69
|
+
this.packageSnippetTrees = new Map();
|
|
70
|
+
this.pageEntries = new Map();
|
|
71
|
+
this.warnings = [];
|
|
72
|
+
this.providerErrors = [];
|
|
73
|
+
this.snippetErrors = [];
|
|
74
|
+
this.snippetFiles = {};
|
|
75
|
+
this.queue = Promise.resolve();
|
|
76
|
+
this.warn = (file, message) => {
|
|
77
|
+
if (!this.warnings.some((warning) => warning.message === message)) {
|
|
78
|
+
this.warnings.push({ file: displayPath(file), message });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
get packages() {
|
|
83
|
+
return { packageNames: this.input.packageNames, packageSnippets: this.packageSnippets };
|
|
84
|
+
}
|
|
85
|
+
inheritSnippetTrees(previous) {
|
|
86
|
+
if (previous.input.docsRoot !== this.input.docsRoot)
|
|
87
|
+
return;
|
|
88
|
+
this.queue = Promise.all([this.queue, previous.queue]).then(() => {
|
|
89
|
+
this.packageSnippetTrees = new Map(previous.packageSnippetTrees);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
collectAll() {
|
|
93
|
+
return this.enqueue(() => this.collectAllNow());
|
|
94
|
+
}
|
|
95
|
+
updatePage(filename) {
|
|
96
|
+
return this.enqueue(() => this.updatePageNow(filename));
|
|
97
|
+
}
|
|
98
|
+
enqueue(operation) {
|
|
99
|
+
const run = this.queue.then(operation);
|
|
100
|
+
this.queue = run.catch(() => { });
|
|
101
|
+
return run;
|
|
102
|
+
}
|
|
103
|
+
classify(files) {
|
|
104
|
+
return classifyPackageSources(files, {
|
|
105
|
+
packageNames: this.input.packageNames,
|
|
106
|
+
variables: this.input.variables,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async collectAllNow() {
|
|
110
|
+
const { docsRoot, variables, provider } = this.input;
|
|
111
|
+
const files = await readSources(docsRoot, this.input.mintIgnore ?? (await getMintIgnore(docsRoot)));
|
|
112
|
+
const classified = this.classify(files);
|
|
113
|
+
const snippetPaths = new Set([
|
|
114
|
+
...classified.snippets.map(({ path }) => path),
|
|
115
|
+
...classified.unparsed.flatMap(({ path, category }) => (category === 'snippet' ? path : [])),
|
|
116
|
+
]);
|
|
117
|
+
this.snippetSources = files
|
|
118
|
+
.filter(({ path }) => snippetPaths.has(path))
|
|
119
|
+
.map(({ path, content }) => ({ filename: withSlash(path), content }));
|
|
120
|
+
this.snippetTreeMap = await createLegacySnippetTreeMap(classified.legacySnippets, variables);
|
|
121
|
+
const previousTrees = this.packageSnippetTrees;
|
|
122
|
+
const parsedTrees = new Map(classified.snippets.map(({ path, tree }) => [path, tree]));
|
|
123
|
+
this.packageSnippetTrees = new Map();
|
|
124
|
+
for (const { filename } of this.snippetSources) {
|
|
125
|
+
if (!isPackageSnippetPath(filename))
|
|
126
|
+
continue;
|
|
127
|
+
const tree = parsedTrees.get(displayPath(filename)) ?? previousTrees.get(filename);
|
|
128
|
+
if (tree)
|
|
129
|
+
this.packageSnippetTrees.set(filename, tree);
|
|
130
|
+
}
|
|
131
|
+
this.packageSnippets = findPackageSnippets([...this.packageSnippetTrees].map(([filename, tree]) => ({ filename, tree })));
|
|
132
|
+
this.warnings = [];
|
|
133
|
+
this.snippets = await resolveSnippets(this.snippetSources, variables, this.packages, this.warn);
|
|
134
|
+
const sourceErrors = new Map();
|
|
135
|
+
for (const error of classified.errors) {
|
|
136
|
+
sourceErrors.set(error.file, [...(sourceErrors.get(error.file) ?? []), error]);
|
|
137
|
+
}
|
|
138
|
+
this.pageEntries = new Map();
|
|
139
|
+
for (const page of classified.pages)
|
|
140
|
+
await this.extractPage(page, sourceErrors.get(page.path));
|
|
141
|
+
for (const { path, content, category } of classified.unparsed) {
|
|
142
|
+
if (category === 'page')
|
|
143
|
+
await this.extractPage({ path, content }, sourceErrors.get(path));
|
|
144
|
+
}
|
|
145
|
+
this.snippetErrors = classified.errors.filter(({ file }) => !this.pageEntries.has(file));
|
|
146
|
+
this.snippetFiles = {};
|
|
147
|
+
for (const path of [...this.packageSnippets].sort()) {
|
|
148
|
+
const source = this.snippetSources.find(({ filename }) => filename === path);
|
|
149
|
+
if (source)
|
|
150
|
+
this.snippetFiles[path] = source.content;
|
|
151
|
+
}
|
|
152
|
+
this.providerErrors = [];
|
|
153
|
+
if (provider !== undefined) {
|
|
154
|
+
try {
|
|
155
|
+
this.snippetFiles[provider] = await readFile(join(docsRoot, provider), 'utf8');
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
this.providerErrors.push({
|
|
159
|
+
file: 'docs.json',
|
|
160
|
+
message: `packages.provider ${provider} was not found.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return this.result();
|
|
165
|
+
}
|
|
166
|
+
async updatePageNow(filename) {
|
|
167
|
+
const path = displayPath(filename.split(sep).join('/'));
|
|
168
|
+
if (!this.pageEntries.has(path))
|
|
169
|
+
return this.collectAllNow();
|
|
170
|
+
let content;
|
|
171
|
+
try {
|
|
172
|
+
content = await readFile(join(this.input.docsRoot, path), 'utf8');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
this.pageEntries.delete(path);
|
|
176
|
+
return this.result();
|
|
177
|
+
}
|
|
178
|
+
const tree = parseOrUndefined(content, withSlash(path));
|
|
179
|
+
const importsPages = (tree ? extractImportSources(tree) : []).some((source) => {
|
|
180
|
+
const resolved = resolveImportPath(source, withSlash(path));
|
|
181
|
+
return resolved !== null && this.pageEntries.has(displayPath(resolved));
|
|
182
|
+
});
|
|
183
|
+
if (importsPages)
|
|
184
|
+
return this.collectAllNow();
|
|
185
|
+
await this.extractPage({ path, content }, this.classify([{ path, content }]).errors);
|
|
186
|
+
return this.result();
|
|
187
|
+
}
|
|
188
|
+
result() {
|
|
189
|
+
const entries = [...this.pageEntries.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
190
|
+
const pages = {};
|
|
191
|
+
for (const [file, entry] of entries)
|
|
192
|
+
pages[file] = { ids: entry.ids, errors: entry.errors };
|
|
193
|
+
return {
|
|
194
|
+
islands: mergePackageIslands(entries.map(([, entry]) => entry)).islands,
|
|
195
|
+
errors: [
|
|
196
|
+
...entries.flatMap(([, entry]) => entry.errors),
|
|
197
|
+
...this.snippetErrors,
|
|
198
|
+
...this.providerErrors,
|
|
199
|
+
],
|
|
200
|
+
warnings: [...this.warnings],
|
|
201
|
+
packageSnippets: new Set(this.packageSnippets),
|
|
202
|
+
snippetFiles: { ...this.snippetFiles },
|
|
203
|
+
pages,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async extractPage(page, sourceErrors = []) {
|
|
207
|
+
const { path } = page;
|
|
208
|
+
const content = replaceVariables(page.content, this.input.variables);
|
|
209
|
+
const noIslands = { ids: [], islands: [], errors: sourceErrors };
|
|
210
|
+
if (!mayImport(content) && !content.includes('<Snippet')) {
|
|
211
|
+
this.pageEntries.set(path, noIslands);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const body = await resolvePageContent({
|
|
215
|
+
page: { filename: withSlash(path), content: page.content },
|
|
216
|
+
docsRoot: this.input.docsRoot,
|
|
217
|
+
snippets: this.snippets,
|
|
218
|
+
packages: this.packages,
|
|
219
|
+
onWarning: (message) => this.warn(path, message),
|
|
220
|
+
});
|
|
221
|
+
if (body === undefined) {
|
|
222
|
+
this.pageEntries.set(path, noIslands);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const extraction = extractPagePackageIslands({
|
|
226
|
+
path,
|
|
227
|
+
content: body,
|
|
228
|
+
packageNames: this.input.packageNames,
|
|
229
|
+
packageSnippets: this.packageSnippets,
|
|
230
|
+
variables: this.input.variables,
|
|
231
|
+
snippetTreeMap: this.snippetTreeMap,
|
|
232
|
+
});
|
|
233
|
+
this.pageEntries.set(path, {
|
|
234
|
+
ids: extraction.ids,
|
|
235
|
+
islands: extraction.islands,
|
|
236
|
+
errors: [...sourceErrors, ...extraction.errors],
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export const collectPackageIslands = (input) => new PackageIslandCollector(input).collectAll();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const errnoCode: (error: unknown) => string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isNativePackagesEnabled: () => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isNativePackagesEnabled = () => process.env.MINTLIFY_NATIVE_NPM_PACKAGES === '1';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { checkPackages, describeBuild, type PackagesCheckLine, type PackagesCheckOptions, type PackagesCheckResult, } from './check.js';
|
|
2
|
+
export { collectPackageIslands, PackageIslandCollector, resolvePageContent, type CollectedIslands, type CollectPackageIslandsInput, type PackageIslandProblem, type PageIslands, } from './collect.js';
|
|
3
|
+
export { isNativePackagesEnabled } from './flag.js';
|
|
4
|
+
export { normalizeProviderPath, readPackagesSetup, type PackagesSetup } from './setup.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { checkPackages, describeBuild, } from './check.js';
|
|
2
|
+
export { collectPackageIslands, PackageIslandCollector, resolvePageContent, } from './collect.js';
|
|
3
|
+
export { isNativePackagesEnabled } from './flag.js';
|
|
4
|
+
export { normalizeProviderPath, readPackagesSetup } from './setup.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PackagesConfig } from '@mintlify/validation';
|
|
2
|
+
export type PackagesSetup = {
|
|
3
|
+
packages: PackagesConfig;
|
|
4
|
+
packageNames: Set<string>;
|
|
5
|
+
variables?: Record<string, string>;
|
|
6
|
+
errors: string[];
|
|
7
|
+
packageJsonError?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class DocsJsonError extends Error {
|
|
10
|
+
readonly problems: string[];
|
|
11
|
+
constructor(problems: string[]);
|
|
12
|
+
}
|
|
13
|
+
export declare class InvalidPackagesError extends DocsJsonError {
|
|
14
|
+
}
|
|
15
|
+
export declare const normalizeProviderPath: (provider: string) => string;
|
|
16
|
+
export declare const readPackagesSetup: (docsRoot: string) => Promise<PackagesSetup | undefined>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parsePackageJsonDependencies } from '@mintlify/common';
|
|
2
|
+
import { getConfigObj } from '@mintlify/prebuild';
|
|
3
|
+
import { packagesSchema } from '@mintlify/validation';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { errnoCode } from './errno.js';
|
|
7
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
export class DocsJsonError extends Error {
|
|
9
|
+
constructor(problems) {
|
|
10
|
+
super(problems.join('\n'));
|
|
11
|
+
this.problems = problems;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class InvalidPackagesError extends DocsJsonError {
|
|
15
|
+
}
|
|
16
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
17
|
+
const readText = async (path) => {
|
|
18
|
+
try {
|
|
19
|
+
return { content: await readFile(path, 'utf8') };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (errnoCode(error) === 'ENOENT')
|
|
23
|
+
return {};
|
|
24
|
+
return { error: `could not be read: ${messageOf(error)}` };
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const normalizeProviderPath = (provider) => `/${provider.replace(/\\/g, '/').replace(/^\/+/, '')}`;
|
|
28
|
+
const readVariables = (value) => {
|
|
29
|
+
if (!isRecord(value))
|
|
30
|
+
return undefined;
|
|
31
|
+
const variables = {};
|
|
32
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
33
|
+
if (typeof entry === 'string')
|
|
34
|
+
variables[key] = entry;
|
|
35
|
+
}
|
|
36
|
+
return variables;
|
|
37
|
+
};
|
|
38
|
+
export const readPackagesSetup = async (docsRoot) => {
|
|
39
|
+
let docsConfig;
|
|
40
|
+
try {
|
|
41
|
+
docsConfig = await getConfigObj(docsRoot, 'docs');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new DocsJsonError([`docs.json could not be loaded: ${messageOf(error)}`]);
|
|
45
|
+
}
|
|
46
|
+
if (docsConfig === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (!isRecord(docsConfig))
|
|
49
|
+
throw new DocsJsonError(['docs.json is not a JSON object']);
|
|
50
|
+
if (!('packages' in docsConfig))
|
|
51
|
+
return undefined;
|
|
52
|
+
const parsed = packagesSchema.safeParse(docsConfig.packages);
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
throw new InvalidPackagesError(parsed.error.issues.map((issue) => `docs.json ${['packages', ...issue.path].join('.')}: ${issue.message}`));
|
|
55
|
+
}
|
|
56
|
+
const packages = parsed.data;
|
|
57
|
+
if (packages.provider !== undefined)
|
|
58
|
+
packages.provider = normalizeProviderPath(packages.provider);
|
|
59
|
+
const errors = [];
|
|
60
|
+
const packageJson = await readText(join(docsRoot, 'package.json'));
|
|
61
|
+
const dependencies = packageJson.content === undefined
|
|
62
|
+
? undefined
|
|
63
|
+
: parsePackageJsonDependencies(packageJson.content);
|
|
64
|
+
const problem = packageJson.error ?? dependencies?.error;
|
|
65
|
+
const packageJsonError = problem && `package.json ${problem}`;
|
|
66
|
+
if (packageJsonError)
|
|
67
|
+
errors.push(packageJsonError);
|
|
68
|
+
else if (!dependencies)
|
|
69
|
+
errors.push('docs.json sets "packages" but there is no package.json next to it.');
|
|
70
|
+
return {
|
|
71
|
+
packages,
|
|
72
|
+
packageNames: dependencies?.error ? new Set() : (dependencies?.packageNames ?? new Set()),
|
|
73
|
+
variables: readVariables(docsConfig.variables),
|
|
74
|
+
errors,
|
|
75
|
+
...(packageJsonError && { packageJsonError }),
|
|
76
|
+
};
|
|
77
|
+
};
|