@mintlify/previewing 4.0.1460 → 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 +7 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import { mkdtemp, rm } from 'fs/promises';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { basename, join } from 'path';
|
|
5
|
+
import { checkPackages } from '../packages/check.js';
|
|
6
|
+
const PAGE = `import { LineChart } from "recharts";
|
|
7
|
+
|
|
8
|
+
<LineChart width={400} height={200} data={[{ v: 1 }]} />
|
|
9
|
+
`;
|
|
10
|
+
const result = (input) => {
|
|
11
|
+
const id = input.islands[0]?.id ?? '';
|
|
12
|
+
return {
|
|
13
|
+
ok: true,
|
|
14
|
+
manifest: {
|
|
15
|
+
version: 1,
|
|
16
|
+
builderVersion: '0.0.1',
|
|
17
|
+
buildId: 'f'.repeat(32),
|
|
18
|
+
depsHash: 'd'.repeat(64),
|
|
19
|
+
islandsHash: 'e'.repeat(64),
|
|
20
|
+
react: { version: '18.3.1', domVersion: '18.3.1' },
|
|
21
|
+
packages: { react: { version: '18.3.1' }, recharts: { version: '2.15.4' } },
|
|
22
|
+
css: [],
|
|
23
|
+
clientAccess: {
|
|
24
|
+
version: 1,
|
|
25
|
+
files: { [`client/islands/${id}.js`]: [id] },
|
|
26
|
+
globalFiles: [],
|
|
27
|
+
},
|
|
28
|
+
islands: {
|
|
29
|
+
[id]: { entry: `client/islands/${id}.js`, prefix: `mi-${id}-`, html: '<svg/>', ssrMs: 2 },
|
|
30
|
+
},
|
|
31
|
+
files: [
|
|
32
|
+
{ path: `client/islands/${id}.js`, bytes: 2048, gzipBytes: 512, integrity: 'sha384-abc' },
|
|
33
|
+
],
|
|
34
|
+
diagnostics: [
|
|
35
|
+
{ level: 'warning', code: 'peer-range', message: 'peer warning', pkg: 'recharts' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
stats: {
|
|
39
|
+
installMs: 400,
|
|
40
|
+
bundleMs: 300,
|
|
41
|
+
renderMs: 100,
|
|
42
|
+
totalMs: 850,
|
|
43
|
+
clientBytes: 2048,
|
|
44
|
+
clientGzipBytes: 512,
|
|
45
|
+
serverBytes: 4096,
|
|
46
|
+
islandClientGzipBytes: { [id]: 512 },
|
|
47
|
+
reactDirs: { react: [], 'react-dom': [] },
|
|
48
|
+
},
|
|
49
|
+
localFiles: [],
|
|
50
|
+
missingLocalFiles: [],
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
describe('checkPackages', () => {
|
|
54
|
+
let docsRoot;
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
docsRoot = await mkdtemp(join(tmpdir(), 'packages-check-'));
|
|
57
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), { name: 'Docs', packages: {} });
|
|
58
|
+
await fse.outputJson(join(docsRoot, 'package.json'), { dependencies: { recharts: '2.15.4' } });
|
|
59
|
+
await fse.outputFile(join(docsRoot, 'charts.mdx'), PAGE);
|
|
60
|
+
});
|
|
61
|
+
afterEach(async () => {
|
|
62
|
+
await rm(docsRoot, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
it('builds with npm ci in a temp folder and prints hashes, sizes and diagnostics', async () => {
|
|
65
|
+
let input;
|
|
66
|
+
const check = await checkPackages({
|
|
67
|
+
docsRoot,
|
|
68
|
+
build: async (received) => {
|
|
69
|
+
input = received;
|
|
70
|
+
await fse.ensureDir(received.outDir);
|
|
71
|
+
return result(received);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
expect(input?.installer).toBe('npm-ci');
|
|
75
|
+
expect(input?.islands).toHaveLength(1);
|
|
76
|
+
expect(await fse.pathExists(input?.outDir ?? '')).toBe(false);
|
|
77
|
+
expect(check.ok).toBe(true);
|
|
78
|
+
const text = check.lines.map((line) => `${line.level} ${line.message}`);
|
|
79
|
+
expect(text.some((line) => /[0-9a-f]{32}|sha384-|client\/islands/.test(line))).toBe(false);
|
|
80
|
+
expect(text).toEqual(expect.arrayContaining([
|
|
81
|
+
'info 1 package component on 1 page',
|
|
82
|
+
'info installed react 18.3.1, react-dom 18.3.1, recharts 2.15.4',
|
|
83
|
+
'info install 400 ms, bundle 300 ms, render 100 ms, total 850 ms',
|
|
84
|
+
'info 0.5 KB of browser JS (gzip) across all pages',
|
|
85
|
+
'info charts.mdx: <LineChart> from recharts, 0.5 KB JS (gzip), renders on the server',
|
|
86
|
+
'warning recharts: peer warning',
|
|
87
|
+
]));
|
|
88
|
+
});
|
|
89
|
+
it('keeps the output when --out is given', async () => {
|
|
90
|
+
const out = join(docsRoot, '..', `${basename(docsRoot)}-out`);
|
|
91
|
+
await checkPackages({
|
|
92
|
+
docsRoot,
|
|
93
|
+
outDir: out,
|
|
94
|
+
build: async (received) => {
|
|
95
|
+
await fse.ensureDir(received.outDir);
|
|
96
|
+
return result(received);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
expect(await fse.pathExists(out)).toBe(true);
|
|
100
|
+
await rm(out, { recursive: true, force: true });
|
|
101
|
+
});
|
|
102
|
+
it('refuses to replace a non-empty --out folder unless forced', async () => {
|
|
103
|
+
const out = join(docsRoot, '..', `${basename(docsRoot)}-keep`);
|
|
104
|
+
await fse.outputFile(join(out, 'notes.txt'), 'keep me');
|
|
105
|
+
const build = vi.fn(async (received) => {
|
|
106
|
+
await fse.emptyDir(received.outDir);
|
|
107
|
+
return result(received);
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const check = await checkPackages({ docsRoot, outDir: out, build });
|
|
111
|
+
expect(build).not.toHaveBeenCalled();
|
|
112
|
+
expect(check.ok).toBe(false);
|
|
113
|
+
expect(check.lines).toEqual([
|
|
114
|
+
{
|
|
115
|
+
level: 'error',
|
|
116
|
+
message: expect.stringContaining(`${out} already exists and is not an empty folder`),
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
expect(await fse.readFile(join(out, 'notes.txt'), 'utf8')).toBe('keep me');
|
|
120
|
+
const forced = await checkPackages({ docsRoot, outDir: out, force: true, build });
|
|
121
|
+
expect(build).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(forced.ok).toBe(true);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await rm(out, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('refuses an --out path that is a file', async () => {
|
|
129
|
+
const out = join(docsRoot, 'charts.mdx');
|
|
130
|
+
const build = vi.fn();
|
|
131
|
+
const check = await checkPackages({ docsRoot, outDir: out, build });
|
|
132
|
+
expect(build).not.toHaveBeenCalled();
|
|
133
|
+
expect(check.ok).toBe(false);
|
|
134
|
+
expect(check.lines[0]?.message).toContain(`${out} already exists and is not an empty folder`);
|
|
135
|
+
expect(await fse.readFile(out, 'utf8')).toBe(PAGE);
|
|
136
|
+
});
|
|
137
|
+
it('builds the package components that <Snippet file> injects', async () => {
|
|
138
|
+
await fse.outputFile(join(docsRoot, 'charts.mdx'), '<Snippet file="chart.mdx" />\n');
|
|
139
|
+
await fse.outputFile(join(docsRoot, '_snippets/chart.mdx'), PAGE);
|
|
140
|
+
const build = vi.fn(async (received) => result(received));
|
|
141
|
+
const check = await checkPackages({ docsRoot, build });
|
|
142
|
+
expect(build.mock.calls[0]?.[0].islands).toEqual([
|
|
143
|
+
expect.objectContaining({ pagePath: 'charts', kind: 'inline' }),
|
|
144
|
+
]);
|
|
145
|
+
expect(check.lines).toContainEqual({
|
|
146
|
+
level: 'info',
|
|
147
|
+
message: '1 package component on 1 page',
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
it('fails before building when a page has island errors', async () => {
|
|
151
|
+
await fse.outputFile(join(docsRoot, 'bad.mdx'), PAGE.replace('/>', 'onClick={() => {}} />'));
|
|
152
|
+
const build = vi.fn();
|
|
153
|
+
const check = await checkPackages({ docsRoot, build });
|
|
154
|
+
expect(build).not.toHaveBeenCalled();
|
|
155
|
+
expect(check.ok).toBe(false);
|
|
156
|
+
expect(check.lines.some((line) => line.level === 'error' && line.message.startsWith('bad.mdx:3'))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('fails before building when a snippet a page imports has errors', async () => {
|
|
159
|
+
await fse.outputFile(join(docsRoot, 'charts.mdx'), 'import { Chart } from "/snippets/chart.jsx";\n\n<Chart />\n');
|
|
160
|
+
await fse.outputFile(join(docsRoot, 'snippets/chart.jsx'), 'import { LineChart } from "recharts";\n\nexport const Chart = () => <LineChart\n');
|
|
161
|
+
const build = vi.fn();
|
|
162
|
+
const check = await checkPackages({ docsRoot, build });
|
|
163
|
+
expect(build).not.toHaveBeenCalled();
|
|
164
|
+
expect(check.ok).toBe(false);
|
|
165
|
+
expect(check.lines.some((line) => line.level === 'error' && line.message.startsWith('snippets/chart.jsx'))).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
it('reports a docs.json that does not parse instead of a missing packages key', async () => {
|
|
168
|
+
const raw = '{ "name": "Docs", "packages": {}, }';
|
|
169
|
+
await fse.outputFile(join(docsRoot, 'docs.json'), raw);
|
|
170
|
+
const parserMessage = (() => {
|
|
171
|
+
try {
|
|
172
|
+
JSON.parse(raw);
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return error instanceof Error ? error.message : String(error);
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
const build = vi.fn();
|
|
180
|
+
const check = await checkPackages({ docsRoot, build });
|
|
181
|
+
expect(build).not.toHaveBeenCalled();
|
|
182
|
+
expect(check.ok).toBe(false);
|
|
183
|
+
expect(check.lines).toEqual([
|
|
184
|
+
{ level: 'error', message: `docs.json could not be loaded: ${parserMessage}` },
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
it.each([
|
|
188
|
+
['', 'docs.json could not be loaded: Unexpected end of JSON input'],
|
|
189
|
+
['["packages"]', 'docs.json is not a JSON object'],
|
|
190
|
+
])('fails on docs.json %j without building', async (raw, message) => {
|
|
191
|
+
await fse.outputFile(join(docsRoot, 'docs.json'), raw);
|
|
192
|
+
const build = vi.fn();
|
|
193
|
+
expect(await checkPackages({ docsRoot, build })).toEqual({
|
|
194
|
+
ok: false,
|
|
195
|
+
lines: [{ level: 'error', message }],
|
|
196
|
+
});
|
|
197
|
+
expect(build).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
it.each([
|
|
200
|
+
['', 'package.json is not valid JSON: Unexpected end of JSON input'],
|
|
201
|
+
['{ "dependencies": ', 'package.json is not valid JSON: Unexpected end of JSON input'],
|
|
202
|
+
['["recharts"]', 'package.json is not a JSON object'],
|
|
203
|
+
['null', 'package.json is not a JSON object'],
|
|
204
|
+
])('fails on package.json %j without building', async (raw, message) => {
|
|
205
|
+
await fse.outputFile(join(docsRoot, 'package.json'), raw);
|
|
206
|
+
const build = vi.fn();
|
|
207
|
+
expect(await checkPackages({ docsRoot, build })).toEqual({
|
|
208
|
+
ok: false,
|
|
209
|
+
lines: [{ level: 'error', message }],
|
|
210
|
+
});
|
|
211
|
+
expect(build).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
it.each([
|
|
214
|
+
['a string', 'nope', ['docs.json packages: Expected object, received string']],
|
|
215
|
+
['null', null, ['docs.json packages: Expected object, received null']],
|
|
216
|
+
['an array', [], ['docs.json packages: Expected object, received array']],
|
|
217
|
+
[
|
|
218
|
+
'an http registry',
|
|
219
|
+
{ registries: { '@acme': 'http://npm.acme.com/' } },
|
|
220
|
+
[
|
|
221
|
+
'docs.json packages.registries.@acme: Registry URLs must start with `https://` and then the host',
|
|
222
|
+
],
|
|
223
|
+
],
|
|
224
|
+
[
|
|
225
|
+
'a provider without a leading slash',
|
|
226
|
+
{ provider: 'snippets/provider.jsx' },
|
|
227
|
+
[
|
|
228
|
+
'docs.json packages.provider: Provider path must start with `/`, such as `/snippets/provider.jsx`',
|
|
229
|
+
],
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
'a provider outside the docs',
|
|
233
|
+
{ provider: '../provider.jsx', isolation: 'none' },
|
|
234
|
+
[
|
|
235
|
+
'docs.json packages.provider: Provider path cannot contain `..` segments',
|
|
236
|
+
"docs.json packages.isolation: Invalid enum value. Expected 'page' | 'frame', received 'none'",
|
|
237
|
+
],
|
|
238
|
+
],
|
|
239
|
+
])('fails on %s for packages with only the schema errors and no build', async (_, packages, errors) => {
|
|
240
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), { name: 'Docs', packages });
|
|
241
|
+
const build = vi.fn();
|
|
242
|
+
expect(await checkPackages({ docsRoot, build })).toEqual({
|
|
243
|
+
ok: false,
|
|
244
|
+
lines: errors.map((message) => ({ level: 'error', message })),
|
|
245
|
+
});
|
|
246
|
+
expect(build).not.toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
it('fails without a packages key', async () => {
|
|
249
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), { name: 'Docs' });
|
|
250
|
+
const check = await checkPackages({ docsRoot, build: vi.fn() });
|
|
251
|
+
expect(check.ok).toBe(false);
|
|
252
|
+
expect(check.lines[0]?.message).toContain('has no "packages" key');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getMdx } from '@mintlify/common/server-mdx';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import { mkdtemp, rm } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { checkPackages } from '../packages/check.js';
|
|
7
|
+
import { collectPackageIslands } from '../packages/collect.js';
|
|
8
|
+
import { DocsJsonError, readPackagesSetup } from '../packages/setup.js';
|
|
9
|
+
const PAGE = 'import { Button } from "@acme/ui";\n\n<Button label="{{label}}" />\n';
|
|
10
|
+
describe('package setup config references', () => {
|
|
11
|
+
let docsRoot;
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
docsRoot = await mkdtemp(join(tmpdir(), 'packages-setup-'));
|
|
14
|
+
await fse.outputJson(join(docsRoot, 'package.json'), {
|
|
15
|
+
dependencies: { '@acme/ui': '1.0.0' },
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await rm(docsRoot, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
it('collects the same island ids that compilation uses with referenced variables', async () => {
|
|
22
|
+
const variables = { label: 'Synthetic label' };
|
|
23
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), {
|
|
24
|
+
packages: {},
|
|
25
|
+
variables: { $ref: './variables.json' },
|
|
26
|
+
});
|
|
27
|
+
await fse.outputJson(join(docsRoot, 'variables.json'), variables);
|
|
28
|
+
await fse.outputFile(join(docsRoot, 'page.mdx'), PAGE);
|
|
29
|
+
const setup = await readPackagesSetup(docsRoot);
|
|
30
|
+
const packageNames = setup?.packageNames ?? new Set();
|
|
31
|
+
const collected = await collectPackageIslands({
|
|
32
|
+
docsRoot,
|
|
33
|
+
packageNames,
|
|
34
|
+
variables: setup?.variables,
|
|
35
|
+
});
|
|
36
|
+
const compiled = await getMdx({
|
|
37
|
+
path: 'page',
|
|
38
|
+
content: PAGE,
|
|
39
|
+
metadata: {},
|
|
40
|
+
snippets: [],
|
|
41
|
+
subdomain: 'local',
|
|
42
|
+
codeStyling: undefined,
|
|
43
|
+
tailwindSelectors: [],
|
|
44
|
+
variables,
|
|
45
|
+
packageBuild: {
|
|
46
|
+
baseUrl: '/_mintlify/pkg/synthetic',
|
|
47
|
+
islands: Object.fromEntries(collected.islands.map(({ id }) => [id, { prefix: `mi-${id}-`, html: '<button />' }])),
|
|
48
|
+
packageNames: [...packageNames],
|
|
49
|
+
packageSnippets: [...collected.packageSnippets],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
expect(setup?.variables).toEqual(variables);
|
|
53
|
+
expect(collected.errors).toEqual([]);
|
|
54
|
+
expect(collected.islands).toHaveLength(1);
|
|
55
|
+
expect(compiled.parseFailed).toBeUndefined();
|
|
56
|
+
expect(Object.keys(compiled.mdxExtracts.islands ?? {})).toEqual(collected.pages['page.mdx']?.ids);
|
|
57
|
+
});
|
|
58
|
+
it('reads referenced provider, css and ssr settings with sibling overrides', async () => {
|
|
59
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), {
|
|
60
|
+
packages: { $ref: './packages.json', ssr: false },
|
|
61
|
+
});
|
|
62
|
+
await fse.outputJson(join(docsRoot, 'packages.json'), {
|
|
63
|
+
provider: '/snippets\\provider.jsx',
|
|
64
|
+
css: ['@acme/ui/styles.css'],
|
|
65
|
+
ssr: true,
|
|
66
|
+
});
|
|
67
|
+
expect((await readPackagesSetup(docsRoot))?.packages).toEqual({
|
|
68
|
+
provider: '/snippets/provider.jsx',
|
|
69
|
+
css: ['@acme/ui/styles.css'],
|
|
70
|
+
ssr: false,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
it('finds packages and variables in a referenced root config', async () => {
|
|
74
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), { $ref: './config.json' });
|
|
75
|
+
await fse.outputJson(join(docsRoot, 'config.json'), {
|
|
76
|
+
packages: { ssr: false },
|
|
77
|
+
variables: { label: 'Synthetic label' },
|
|
78
|
+
});
|
|
79
|
+
const setup = await readPackagesSetup(docsRoot);
|
|
80
|
+
expect(setup?.packages).toEqual({ ssr: false });
|
|
81
|
+
expect(setup?.variables).toEqual({ label: 'Synthetic label' });
|
|
82
|
+
});
|
|
83
|
+
it.each([
|
|
84
|
+
{ file: 'missing.json', content: undefined, message: /does not exist/ },
|
|
85
|
+
{ file: 'invalid.json', content: '{', message: /contains invalid JSON/ },
|
|
86
|
+
])('reports a broken $ref to $file as a docs config error', async ({ file, content, message }) => {
|
|
87
|
+
await fse.outputJson(join(docsRoot, 'docs.json'), {
|
|
88
|
+
packages: { $ref: `./${file}` },
|
|
89
|
+
});
|
|
90
|
+
if (content !== undefined)
|
|
91
|
+
await fse.outputFile(join(docsRoot, file), content);
|
|
92
|
+
await expect(readPackagesSetup(docsRoot)).rejects.toThrow(DocsJsonError);
|
|
93
|
+
const build = vi.fn();
|
|
94
|
+
const checked = await checkPackages({ docsRoot, build });
|
|
95
|
+
expect(checked.ok).toBe(false);
|
|
96
|
+
expect(checked.lines).toEqual([{ level: 'error', message: expect.stringMatching(message) }]);
|
|
97
|
+
expect(build).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
it('keeps packages disabled when docs.json is missing', async () => {
|
|
100
|
+
expect(await readPackagesSetup(docsRoot)).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ import validateBuild from './local-preview/validate-build.js';
|
|
|
6
6
|
import { getClientVersion } from './util.js';
|
|
7
7
|
export * from './logs.js';
|
|
8
8
|
export * from './logging-state.js';
|
|
9
|
+
export * from './packages/index.js';
|
|
9
10
|
export { dev, exportSite, validateBuild, getClientVersion, getLatestClientVersion, downloadTargetMint, getVersionMap, LOCAL_LINKED_CLI_VERSION, };
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,5 @@ import validateBuild from './local-preview/validate-build.js';
|
|
|
6
6
|
import { getClientVersion } from './util.js';
|
|
7
7
|
export * from './logs.js';
|
|
8
8
|
export * from './logging-state.js';
|
|
9
|
+
export * from './packages/index.js';
|
|
9
10
|
export { dev, exportSite, validateBuild, getClientVersion, getLatestClientVersion, downloadTargetMint, getVersionMap, LOCAL_LINKED_CLI_VERSION, };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
type PackageBuilder = typeof import('@mintlify/package-builder');
|
|
2
|
+
export declare const PACKAGE_BUILDER_MISSING = "The optional @mintlify/package-builder dependency is not installed, so npm packages are unavailable. npm skips it under --omit=optional or when esbuild fails to install; reinstall mint with --include=optional --foreground-scripts to see why.";
|
|
3
|
+
export declare const loadPackageBuilder: (load?: () => Promise<PackageBuilder>) => Promise<PackageBuilder>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { errnoCode } from './errno.js';
|
|
2
|
+
export const PACKAGE_BUILDER_MISSING = 'The optional @mintlify/package-builder dependency is not installed, so npm packages are unavailable. npm skips it under --omit=optional or when esbuild fails to install; reinstall mint with --include=optional --foreground-scripts to see why.';
|
|
3
|
+
const NOT_FOUND_CODES = new Set(['ERR_MODULE_NOT_FOUND', 'MODULE_NOT_FOUND']);
|
|
4
|
+
export const loadPackageBuilder = async (load = () => import('@mintlify/package-builder')) => {
|
|
5
|
+
try {
|
|
6
|
+
return await load();
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (NOT_FOUND_CODES.has(errnoCode(error) ?? '') &&
|
|
10
|
+
error instanceof Error &&
|
|
11
|
+
error.message.includes("'@mintlify/package-builder'")) {
|
|
12
|
+
throw new Error(PACKAGE_BUILDER_MISSING);
|
|
13
|
+
}
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BuildDiagnostic, BuildResult, BuildStats, PackageBuildManifest } from '@mintlify/package-builder';
|
|
2
|
+
import { type CollectedIslands } from './collect.js';
|
|
3
|
+
export type PackagesCheckLine = {
|
|
4
|
+
level: 'info' | 'warning' | 'error';
|
|
5
|
+
message: string;
|
|
6
|
+
};
|
|
7
|
+
export type PackagesCheckResult = {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
lines: PackagesCheckLine[];
|
|
10
|
+
collected?: CollectedIslands;
|
|
11
|
+
manifest?: PackageBuildManifest;
|
|
12
|
+
stats?: BuildStats;
|
|
13
|
+
};
|
|
14
|
+
export type PackagesCheckOptions = {
|
|
15
|
+
docsRoot: string;
|
|
16
|
+
outDir?: string;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
build?: (input: Parameters<typeof import('@mintlify/package-builder').build>[0]) => Promise<BuildResult>;
|
|
19
|
+
};
|
|
20
|
+
export declare const formatDiagnostic: ({ pkg, message }: BuildDiagnostic) => string;
|
|
21
|
+
export declare const describeBuild: ({ manifest, stats }: {
|
|
22
|
+
manifest: PackageBuildManifest;
|
|
23
|
+
stats: BuildStats;
|
|
24
|
+
}, collected: CollectedIslands) => PackagesCheckLine[];
|
|
25
|
+
export declare const checkPackages: ({ docsRoot, outDir, force, build, }: PackagesCheckOptions) => Promise<PackagesCheckResult>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mkdtemp, readdir, rm } from 'fs/promises';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
import { loadPackageBuilder } from './builder.js';
|
|
5
|
+
import { collectPackageIslands, formatProblem } from './collect.js';
|
|
6
|
+
import { errnoCode } from './errno.js';
|
|
7
|
+
import { DocsJsonError, readPackagesSetup } from './setup.js';
|
|
8
|
+
const kb = (bytes) => `${(bytes / 1024).toFixed(1)} KB`;
|
|
9
|
+
const ms = (value) => `${Math.round(value)} ms`;
|
|
10
|
+
const occupied = async (dir) => {
|
|
11
|
+
try {
|
|
12
|
+
return (await readdir(dir)).length > 0;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return errnoCode(error) !== 'ENOENT';
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export const formatDiagnostic = ({ pkg, message }) => `${pkg ? `${pkg}: ` : ''}${message}`;
|
|
19
|
+
const describeImports = (island) => {
|
|
20
|
+
const byPackage = new Map();
|
|
21
|
+
for (const { local, pkg } of island?.imports ?? []) {
|
|
22
|
+
byPackage.set(pkg, [...(byPackage.get(pkg) ?? []), `<${local}>`]);
|
|
23
|
+
}
|
|
24
|
+
return [...byPackage].map(([pkg, names]) => `${names.join(', ')} from ${pkg}`).join('; ');
|
|
25
|
+
};
|
|
26
|
+
export const describeBuild = ({ manifest, stats }, collected) => {
|
|
27
|
+
const info = (message) => ({ level: 'info', message });
|
|
28
|
+
const specs = new Map(collected.islands.map((island) => [island.id, island]));
|
|
29
|
+
const lines = [
|
|
30
|
+
info(`installed ${[
|
|
31
|
+
`react ${manifest.react.version || '?'}`,
|
|
32
|
+
`react-dom ${manifest.react.domVersion || '?'}`,
|
|
33
|
+
...Object.entries(manifest.packages)
|
|
34
|
+
.filter(([name]) => name !== 'react' && name !== 'react-dom')
|
|
35
|
+
.map(([name, { version }]) => `${name} ${version}`),
|
|
36
|
+
].join(', ')}`),
|
|
37
|
+
info(`install ${ms(stats.installMs)}, bundle ${ms(stats.bundleMs)}, render ${ms(stats.renderMs)}, total ${ms(stats.totalMs)}`),
|
|
38
|
+
info(`${kb(stats.clientGzipBytes)} of browser JS (gzip) across all pages`),
|
|
39
|
+
];
|
|
40
|
+
for (const [page, { ids }] of Object.entries(collected.pages)) {
|
|
41
|
+
for (const id of new Set(ids)) {
|
|
42
|
+
const island = manifest.islands[id];
|
|
43
|
+
if (!island)
|
|
44
|
+
continue;
|
|
45
|
+
const gzip = stats.islandClientGzipBytes[id];
|
|
46
|
+
const parts = [
|
|
47
|
+
describeImports(specs.get(id)) || 'package component',
|
|
48
|
+
...(gzip === undefined ? [] : [`${kb(gzip)} JS (gzip)`]),
|
|
49
|
+
island.html !== undefined ? 'renders on the server' : 'renders in the browser only',
|
|
50
|
+
];
|
|
51
|
+
lines.push(info(`${page}: ${parts.join(', ')}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const d of manifest.diagnostics) {
|
|
55
|
+
lines.push({ level: d.level, message: formatDiagnostic(d) });
|
|
56
|
+
}
|
|
57
|
+
return lines;
|
|
58
|
+
};
|
|
59
|
+
export const checkPackages = async ({ docsRoot, outDir, force, build, }) => {
|
|
60
|
+
if (outDir && !force && (await occupied(resolve(outDir)))) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
lines: [
|
|
64
|
+
{
|
|
65
|
+
level: 'error',
|
|
66
|
+
message: `${resolve(outDir)} already exists and is not an empty folder; use an empty folder or pass --force to replace it.`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
let setup;
|
|
72
|
+
try {
|
|
73
|
+
setup = await readPackagesSetup(docsRoot);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof DocsJsonError))
|
|
77
|
+
throw error;
|
|
78
|
+
return { ok: false, lines: error.problems.map((message) => ({ level: 'error', message })) };
|
|
79
|
+
}
|
|
80
|
+
if (!setup) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
lines: [{ level: 'error', message: `${join(docsRoot, 'docs.json')} has no "packages" key.` }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const lines = setup.errors.map((message) => ({ level: 'error', message }));
|
|
87
|
+
if (setup.packageJsonError)
|
|
88
|
+
return { ok: false, lines };
|
|
89
|
+
const collected = await collectPackageIslands({
|
|
90
|
+
docsRoot,
|
|
91
|
+
packageNames: setup.packageNames,
|
|
92
|
+
variables: setup.variables,
|
|
93
|
+
provider: setup.packages.provider,
|
|
94
|
+
});
|
|
95
|
+
const pageCount = Object.values(collected.pages).filter(({ ids }) => ids.length > 0).length;
|
|
96
|
+
lines.push({
|
|
97
|
+
level: 'info',
|
|
98
|
+
message: `${collected.islands.length} package component${collected.islands.length === 1 ? '' : 's'} on ${pageCount} page${pageCount === 1 ? '' : 's'}`,
|
|
99
|
+
});
|
|
100
|
+
for (const warning of collected.warnings) {
|
|
101
|
+
lines.push({ level: 'warning', message: formatProblem(warning) });
|
|
102
|
+
}
|
|
103
|
+
for (const error of collected.errors) {
|
|
104
|
+
lines.push({ level: 'error', message: formatProblem(error) });
|
|
105
|
+
}
|
|
106
|
+
if (lines.some((line) => line.level === 'error'))
|
|
107
|
+
return { ok: false, lines, collected };
|
|
108
|
+
const run = build ?? (await loadPackageBuilder()).build;
|
|
109
|
+
const target = outDir ? resolve(outDir) : await mkdtemp(join(tmpdir(), 'mint-packages-check-'));
|
|
110
|
+
try {
|
|
111
|
+
const result = await run({
|
|
112
|
+
docsRoot,
|
|
113
|
+
packages: setup.packages,
|
|
114
|
+
islands: collected.islands,
|
|
115
|
+
snippetFiles: collected.snippetFiles,
|
|
116
|
+
outDir: target,
|
|
117
|
+
installer: 'npm-ci',
|
|
118
|
+
});
|
|
119
|
+
lines.push(...describeBuild(result, collected));
|
|
120
|
+
return {
|
|
121
|
+
ok: result.ok,
|
|
122
|
+
lines,
|
|
123
|
+
collected,
|
|
124
|
+
manifest: result.manifest,
|
|
125
|
+
stats: result.stats,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (!outDir)
|
|
130
|
+
await rm(target, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type FileWithImports, type IslandSpec, type PackageImportOptions, type PackageSourceProblem } from '@mintlify/common';
|
|
2
|
+
export type PackageIslandProblem = PackageSourceProblem;
|
|
3
|
+
export declare const formatProblem: ({ file, line, message }: PackageIslandProblem) => string;
|
|
4
|
+
export type CollectPackageIslandsInput = {
|
|
5
|
+
docsRoot: string;
|
|
6
|
+
packageNames: ReadonlySet<string>;
|
|
7
|
+
variables?: Record<string, string>;
|
|
8
|
+
provider?: string;
|
|
9
|
+
mintIgnore?: string[];
|
|
10
|
+
};
|
|
11
|
+
export type PageIslands = {
|
|
12
|
+
ids: string[];
|
|
13
|
+
errors: PackageIslandProblem[];
|
|
14
|
+
};
|
|
15
|
+
export type CollectedIslands = {
|
|
16
|
+
islands: IslandSpec[];
|
|
17
|
+
errors: PackageIslandProblem[];
|
|
18
|
+
warnings: PackageIslandProblem[];
|
|
19
|
+
packageSnippets: Set<string>;
|
|
20
|
+
snippetFiles: Record<string, string>;
|
|
21
|
+
pages: Record<string, PageIslands>;
|
|
22
|
+
};
|
|
23
|
+
type SourceFile = {
|
|
24
|
+
filename: string;
|
|
25
|
+
content: string;
|
|
26
|
+
};
|
|
27
|
+
export declare const resolvePageContent: ({ page, docsRoot, snippets, packages, onWarning, }: {
|
|
28
|
+
page: SourceFile;
|
|
29
|
+
docsRoot: string;
|
|
30
|
+
snippets: FileWithImports[];
|
|
31
|
+
packages: PackageImportOptions;
|
|
32
|
+
onWarning?: (message: string) => void;
|
|
33
|
+
}) => Promise<string | undefined>;
|
|
34
|
+
export declare class PackageIslandCollector {
|
|
35
|
+
private readonly input;
|
|
36
|
+
private snippetSources;
|
|
37
|
+
private snippets;
|
|
38
|
+
private snippetTreeMap;
|
|
39
|
+
private packageSnippets;
|
|
40
|
+
private packageSnippetTrees;
|
|
41
|
+
private pageEntries;
|
|
42
|
+
private warnings;
|
|
43
|
+
private providerErrors;
|
|
44
|
+
private snippetErrors;
|
|
45
|
+
private snippetFiles;
|
|
46
|
+
private queue;
|
|
47
|
+
constructor(input: CollectPackageIslandsInput);
|
|
48
|
+
private get packages();
|
|
49
|
+
private warn;
|
|
50
|
+
inheritSnippetTrees(previous: PackageIslandCollector): void;
|
|
51
|
+
collectAll(): Promise<CollectedIslands>;
|
|
52
|
+
updatePage(filename: string): Promise<CollectedIslands>;
|
|
53
|
+
private enqueue;
|
|
54
|
+
private classify;
|
|
55
|
+
private collectAllNow;
|
|
56
|
+
private updatePageNow;
|
|
57
|
+
result(): CollectedIslands;
|
|
58
|
+
private extractPage;
|
|
59
|
+
}
|
|
60
|
+
export declare const collectPackageIslands: (input: CollectPackageIslandsInput) => Promise<CollectedIslands>;
|
|
61
|
+
export {};
|