@mintlify/package-builder 0.0.0-stage → 0.0.3
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/LICENSE +93 -0
- package/README.md +66 -2
- package/dist/build.d.ts +4 -0
- package/dist/build.js +338 -0
- package/dist/bundle.d.ts +44 -0
- package/dist/bundle.js +681 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +112 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +32 -0
- package/dist/hashing.d.ts +28 -0
- package/dist/hashing.js +98 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/install.d.ts +34 -0
- package/dist/install.js +325 -0
- package/dist/local-files.d.ts +11 -0
- package/dist/local-files.js +120 -0
- package/dist/lockfile.d.ts +40 -0
- package/dist/lockfile.js +498 -0
- package/dist/package-json.d.ts +14 -0
- package/dist/package-json.js +170 -0
- package/dist/package-manager.d.ts +17 -0
- package/dist/package-manager.js +87 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +264 -0
- package/dist/render.d.ts +37 -0
- package/dist/render.js +292 -0
- package/dist/runtime/client.d.ts +11 -0
- package/dist/runtime/client.js +54 -0
- package/dist/runtime/server.d.ts +6 -0
- package/dist/runtime/server.js +11 -0
- package/dist/runtime/shell.d.ts +21 -0
- package/dist/runtime/shell.js +68 -0
- package/dist/runtime/theme.d.ts +1 -0
- package/dist/runtime/theme.js +1 -0
- package/dist/sanitize.d.ts +9 -0
- package/dist/sanitize.js +650 -0
- package/dist/semver.d.ts +2 -0
- package/dist/semver.js +10 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +11 -0
- package/package.json +74 -4
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild';
|
|
2
|
+
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { performance } from 'node:perf_hooks';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { isSkipped, NAMESPACED, packageOfPath, singleReactPlugin, skipData, } from './react.js';
|
|
9
|
+
export const ISLAND_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
10
|
+
export const ISLANDS_HOOKS_SPECIFIER = '@mintlify/islands';
|
|
11
|
+
const RUNTIME_SPECIFIER = /^mint-island-runtime\/(client|server|theme)$/;
|
|
12
|
+
export const URL_LIKE = /^(?:[a-zA-Z][a-zA-Z0-9+.-]+:|\/\/|#)/;
|
|
13
|
+
const ASSET_LOADERS = {
|
|
14
|
+
...Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.woff', '.woff2', '.ttf', '.otf'].map((ext) => [ext, 'file'])),
|
|
15
|
+
'.svg': 'dataurl',
|
|
16
|
+
'.eot': 'empty',
|
|
17
|
+
};
|
|
18
|
+
const OUTSIDE = 'outside the docs folder and node_modules';
|
|
19
|
+
const CSS_STUB = 'mint-css-stub';
|
|
20
|
+
const CSS_SPECIFIER = /\.css(?:[?#].*)?$/;
|
|
21
|
+
const CSS_MODULE = /\.module\.css$/;
|
|
22
|
+
const CSS_DUPLICATE = 'mint-css-duplicate';
|
|
23
|
+
const SERVER_BANNER = "import { createRequire as __mintCreateRequire } from 'node:module';\nconst require = __mintCreateRequire(import.meta.url);";
|
|
24
|
+
function runtimeFile(name) {
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
for (const ext of ['.js', '.tsx', '.ts']) {
|
|
27
|
+
const candidate = join(here, 'runtime', `${name}${ext}`);
|
|
28
|
+
if (existsSync(candidate))
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Island runtime file ${name} not found next to ${here}`);
|
|
32
|
+
}
|
|
33
|
+
const NATIVE_PATH = { relative, isAbsolute, resolve };
|
|
34
|
+
export function inside(parent, child, ops = NATIVE_PATH) {
|
|
35
|
+
const rel = ops.relative(parent, child);
|
|
36
|
+
return rel === '' || (!rel.startsWith('..') && !ops.isAbsolute(rel));
|
|
37
|
+
}
|
|
38
|
+
export function canonical(path) {
|
|
39
|
+
try {
|
|
40
|
+
return realpathSync.native(path);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
try {
|
|
44
|
+
return realpathSync(path);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return resolve(path);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function within(roots, real = canonical, ops = NATIVE_PATH) {
|
|
52
|
+
const realRoots = roots.map(real);
|
|
53
|
+
return (path) => {
|
|
54
|
+
const target = real(path);
|
|
55
|
+
return realRoots.some((root) => inside(root, target, ops));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function nodeModulesRoots(installRoot, hoisted) {
|
|
59
|
+
const roots = [];
|
|
60
|
+
for (let dir = installRoot;; dir = dirname(dir)) {
|
|
61
|
+
const nodeModules = join(dir, 'node_modules');
|
|
62
|
+
if (existsSync(nodeModules))
|
|
63
|
+
roots.push(nodeModules);
|
|
64
|
+
if (!hoisted || dirname(dir) === dir)
|
|
65
|
+
return roots;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function hardLinked(path) {
|
|
69
|
+
try {
|
|
70
|
+
return statSync(path).nlink > 1;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function climbsOut(specifier) {
|
|
77
|
+
return specifier.split(/[\\/]/).includes('..');
|
|
78
|
+
}
|
|
79
|
+
function globLeaves(root, dir, pattern) {
|
|
80
|
+
const segments = pattern.split(/[\\/]/);
|
|
81
|
+
const wild = segments.findIndex((s) => s.includes('*'));
|
|
82
|
+
if (wild === -1)
|
|
83
|
+
return false;
|
|
84
|
+
return (!inside(root, resolve(dir, segments.slice(0, wild).join('/'))) ||
|
|
85
|
+
segments.slice(wild).includes('..'));
|
|
86
|
+
}
|
|
87
|
+
function isPackageSpecifier(specifier) {
|
|
88
|
+
return (specifier.length > 0 &&
|
|
89
|
+
!/^[./\\]/.test(specifier) &&
|
|
90
|
+
!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(specifier) &&
|
|
91
|
+
!climbsOut(specifier));
|
|
92
|
+
}
|
|
93
|
+
function badSegment(seg) {
|
|
94
|
+
return seg === '' || seg === '..' || /[:\0]/.test(seg) || (seg !== '.' && /[. ]$/.test(seg));
|
|
95
|
+
}
|
|
96
|
+
export function docsPathToRelative(path) {
|
|
97
|
+
const rel = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
98
|
+
if (!rel || rel.split('/').some(badSegment))
|
|
99
|
+
return undefined;
|
|
100
|
+
return rel;
|
|
101
|
+
}
|
|
102
|
+
export function docsTarget(docsDir, path, ops = NATIVE_PATH) {
|
|
103
|
+
const rel = docsPathToRelative(path);
|
|
104
|
+
if (rel === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const target = ops.resolve(docsDir, rel);
|
|
107
|
+
return ops.relative(docsDir, target) !== '' && inside(docsDir, target, ops) ? target : undefined;
|
|
108
|
+
}
|
|
109
|
+
function packagePath(path) {
|
|
110
|
+
const normalized = path.replace(/\\/g, '/');
|
|
111
|
+
const pkg = packageOfPath(normalized);
|
|
112
|
+
return pkg && `${pkg.name}${normalized.slice(pkg.dir.length)}`;
|
|
113
|
+
}
|
|
114
|
+
export function shownPath(path, docsDir, ops = NATIVE_PATH) {
|
|
115
|
+
if (inside(docsDir, path, ops))
|
|
116
|
+
return `/${ops.relative(docsDir, path).replace(/\\/g, '/')}`;
|
|
117
|
+
return packagePath(path) ?? path.replace(/\\/g, '/');
|
|
118
|
+
}
|
|
119
|
+
function entryFile(id, side) {
|
|
120
|
+
return `entries/${id}.${side}.jsx`;
|
|
121
|
+
}
|
|
122
|
+
function wildcard(pattern, name) {
|
|
123
|
+
let p = 0;
|
|
124
|
+
let star = -1;
|
|
125
|
+
let mark = 0;
|
|
126
|
+
for (let n = 0; n < name.length;) {
|
|
127
|
+
if (pattern[p] === '*') {
|
|
128
|
+
star = p++;
|
|
129
|
+
mark = n;
|
|
130
|
+
}
|
|
131
|
+
else if (pattern[p] === name[n]) {
|
|
132
|
+
p++;
|
|
133
|
+
n++;
|
|
134
|
+
}
|
|
135
|
+
else if (star >= 0) {
|
|
136
|
+
p = star + 1;
|
|
137
|
+
n = ++mark;
|
|
138
|
+
}
|
|
139
|
+
else
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
while (pattern[p] === '*')
|
|
143
|
+
p++;
|
|
144
|
+
return p === pattern.length;
|
|
145
|
+
}
|
|
146
|
+
function globTest(pattern, path) {
|
|
147
|
+
const segments = path.split('/');
|
|
148
|
+
let next = segments.map(() => false).concat(true);
|
|
149
|
+
for (const part of pattern.split('/').reverse()) {
|
|
150
|
+
const row = next.map(() => false);
|
|
151
|
+
for (let s = segments.length; s >= 0; s--) {
|
|
152
|
+
row[s] =
|
|
153
|
+
part === '**'
|
|
154
|
+
? next[s] === true || row[s + 1] === true
|
|
155
|
+
: next[s + 1] === true && wildcard(part, segments[s]);
|
|
156
|
+
}
|
|
157
|
+
next = row;
|
|
158
|
+
}
|
|
159
|
+
return next[0] === true;
|
|
160
|
+
}
|
|
161
|
+
function globMatches(metafile, srcDir, importer, pattern) {
|
|
162
|
+
const cut = pattern.lastIndexOf('/', pattern.indexOf('*')) + 1;
|
|
163
|
+
const base = canonical(resolve(srcDir, dirname(importer), pattern.slice(0, cut)));
|
|
164
|
+
return Object.keys(metafile.inputs)
|
|
165
|
+
.filter((key) => !NAMESPACED.test(key))
|
|
166
|
+
.flatMap((key) => {
|
|
167
|
+
const path = resolve(srcDir, key);
|
|
168
|
+
const rel = relative(base, path).split(sep).join('/');
|
|
169
|
+
return inside(base, path) && globTest(pattern.slice(cut), rel) ? [{ key, rel }] : [];
|
|
170
|
+
})
|
|
171
|
+
.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0))
|
|
172
|
+
.map((m) => m.key);
|
|
173
|
+
}
|
|
174
|
+
function importedCss(metafile, entries, srcDir, loaded) {
|
|
175
|
+
const prefix = `${CSS_STUB}:`;
|
|
176
|
+
const cssOf = (input) => {
|
|
177
|
+
if (input.startsWith(prefix))
|
|
178
|
+
return input.slice(prefix.length);
|
|
179
|
+
const path = resolve(srcDir, input);
|
|
180
|
+
return !NAMESPACED.test(input) && loaded.has(path) ? path : undefined;
|
|
181
|
+
};
|
|
182
|
+
const seen = new Set();
|
|
183
|
+
const order = new Set();
|
|
184
|
+
const stack = [...entries].reverse();
|
|
185
|
+
for (let input = stack.pop(); input !== undefined; input = stack.pop()) {
|
|
186
|
+
if (seen.has(input))
|
|
187
|
+
continue;
|
|
188
|
+
seen.add(input);
|
|
189
|
+
const css = cssOf(input);
|
|
190
|
+
if (css !== undefined)
|
|
191
|
+
order.add(css);
|
|
192
|
+
const next = (metafile.inputs[input]?.imports ?? []).flatMap((i) => !i.external
|
|
193
|
+
? [i.path]
|
|
194
|
+
: i.path.includes('*')
|
|
195
|
+
? globMatches(metafile, srcDir, input, i.path)
|
|
196
|
+
: []);
|
|
197
|
+
for (const path of next.reverse())
|
|
198
|
+
stack.push(path);
|
|
199
|
+
}
|
|
200
|
+
const stubs = Object.keys(metafile.inputs).filter((input) => input.startsWith(prefix));
|
|
201
|
+
const all = new Set([...stubs.map((input) => input.slice(prefix.length)), ...loaded]);
|
|
202
|
+
return { order: [...order], missed: [...all].filter((path) => !order.has(path)).sort() };
|
|
203
|
+
}
|
|
204
|
+
function entrySource(island, side, provider) {
|
|
205
|
+
const lines = provider ? [`import * as ProviderModule from ${JSON.stringify(provider)};`] : [];
|
|
206
|
+
lines.push(`import Island from ${JSON.stringify(`../islands/${island.id}.jsx`)};`);
|
|
207
|
+
if (side === 'client') {
|
|
208
|
+
lines.push(`import { createHydrate } from 'mint-island-runtime/client';`);
|
|
209
|
+
lines.push(`export const hydrate = createHydrate(Island${provider ? ', ProviderModule' : ''});`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push(`import { createRender } from 'mint-island-runtime/server';`);
|
|
213
|
+
lines.push(`export const render = createRender(Island${provider ? ', ProviderModule' : ''});`);
|
|
214
|
+
}
|
|
215
|
+
return `${lines.join('\n')}\n`;
|
|
216
|
+
}
|
|
217
|
+
export function formatMessages(messages, srcDir, islands, ops = NATIVE_PATH) {
|
|
218
|
+
const byFile = new Map(islands.map((i) => [`islands/${i.id}.jsx`, i]));
|
|
219
|
+
return messages.map((m) => {
|
|
220
|
+
if (!m.location)
|
|
221
|
+
return m.text;
|
|
222
|
+
const file = m.location.file.replace(/\\/g, '/');
|
|
223
|
+
const rel = ops.relative(srcDir, ops.resolve(srcDir, file)).replace(/\\/g, '/');
|
|
224
|
+
const island = byFile.get(rel);
|
|
225
|
+
const where = island
|
|
226
|
+
? `island ${island.id} (${island.pagePath})`
|
|
227
|
+
: rel.startsWith('docs/')
|
|
228
|
+
? `/${rel.slice('docs/'.length)}`
|
|
229
|
+
: (packagePath(file) ?? file);
|
|
230
|
+
return `${where}${m.location.line ? `:${m.location.line}` : ''}: ${m.text}`;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function packageFromMessage(message) {
|
|
234
|
+
const detail = message?.detail?.pkg;
|
|
235
|
+
if (typeof detail === 'string')
|
|
236
|
+
return detail;
|
|
237
|
+
const text = message?.text ?? '';
|
|
238
|
+
const m = /node_modules\/((?:@[^/]+\/)?[^/"]+)/.exec(text) ??
|
|
239
|
+
/Could not resolve "((?:@[^/]+\/)?[^/"]+)/.exec(text);
|
|
240
|
+
return m?.[1] ?? packageOfPath(message?.location?.file ?? '')?.name;
|
|
241
|
+
}
|
|
242
|
+
function packageName(specifier) {
|
|
243
|
+
return specifier
|
|
244
|
+
.split('/')
|
|
245
|
+
.slice(0, specifier.startsWith('@') ? 2 : 1)
|
|
246
|
+
.join('/');
|
|
247
|
+
}
|
|
248
|
+
export async function bundle(opts) {
|
|
249
|
+
const start = performance.now();
|
|
250
|
+
const diagnostics = [];
|
|
251
|
+
const replacedReact = [];
|
|
252
|
+
const srcDir = canonical(await mkdtemp(join(opts.workParent ?? tmpdir(), '.mint-pkg-src-')));
|
|
253
|
+
const srcDocs = join(srcDir, 'docs');
|
|
254
|
+
const srcIslands = join(srcDir, 'islands');
|
|
255
|
+
const installRoot = canonical(opts.installRoot);
|
|
256
|
+
const runtimeDir = dirname(runtimeFile('client'));
|
|
257
|
+
const allowed = within([
|
|
258
|
+
srcDir,
|
|
259
|
+
runtimeDir,
|
|
260
|
+
...nodeModulesRoots(installRoot, opts.hoistedNodeModules === true),
|
|
261
|
+
]);
|
|
262
|
+
const loadedCss = new Set();
|
|
263
|
+
const outsideText = (from) => `${from === undefined ? 'An import' : `The import of "${from}"`} reads a file ${OUTSIDE}`;
|
|
264
|
+
const outside = (pkg, from) => ({
|
|
265
|
+
errors: [{ text: outsideText(from), ...(pkg ? { detail: { pkg } } : {}) }],
|
|
266
|
+
});
|
|
267
|
+
const cssModule = (path) => {
|
|
268
|
+
const pkg = packageOfPath(path)?.name;
|
|
269
|
+
return {
|
|
270
|
+
text: `${shownPath(path, srcDocs)} is a CSS module. styles.css loads every stylesheet globally, so its class names would not match; import a plain .css file.`,
|
|
271
|
+
...(pkg ? { detail: { pkg } } : {}),
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
const done = (extra = {}) => ({
|
|
275
|
+
ok: !diagnostics.some((d) => d.level === 'error'),
|
|
276
|
+
diagnostics,
|
|
277
|
+
durationMs: performance.now() - start,
|
|
278
|
+
srcDir,
|
|
279
|
+
replacedReact,
|
|
280
|
+
...extra,
|
|
281
|
+
});
|
|
282
|
+
for (const [path, source] of Object.entries(opts.snippetFiles)) {
|
|
283
|
+
const target = docsTarget(srcDocs, path);
|
|
284
|
+
if (!target) {
|
|
285
|
+
diagnostics.push({
|
|
286
|
+
level: 'error',
|
|
287
|
+
code: 'snippet-invalid-path',
|
|
288
|
+
message: `Snippet path ${path} is not a file path inside the docs folder.`,
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
await mkdir(dirname(target), { recursive: true });
|
|
293
|
+
await writeFile(target, source);
|
|
294
|
+
}
|
|
295
|
+
for (const specifier of opts.packages.css ?? []) {
|
|
296
|
+
if (!isPackageSpecifier(specifier)) {
|
|
297
|
+
diagnostics.push({
|
|
298
|
+
level: 'error',
|
|
299
|
+
code: 'css-invalid-specifier',
|
|
300
|
+
message: `packages.css entry ${specifier} must be a package path such as @acme/ui/dist/styles.css.`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const provider = opts.packages.provider;
|
|
305
|
+
if (provider !== undefined && (!provider.startsWith('/') || !docsPathToRelative(provider))) {
|
|
306
|
+
diagnostics.push({
|
|
307
|
+
level: 'error',
|
|
308
|
+
code: 'provider-invalid-path',
|
|
309
|
+
message: `packages.provider ${provider} must be a path such as /snippets/provider.jsx.`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
await mkdir(srcIslands, { recursive: true });
|
|
313
|
+
await mkdir(join(srcDir, 'entries'), { recursive: true });
|
|
314
|
+
const clientEntries = {};
|
|
315
|
+
const serverEntries = {};
|
|
316
|
+
for (const island of opts.islands) {
|
|
317
|
+
if (!ISLAND_ID.test(island.id)) {
|
|
318
|
+
diagnostics.push({
|
|
319
|
+
level: 'error',
|
|
320
|
+
code: 'island-invalid-id',
|
|
321
|
+
message: `Island id ${island.id} is not valid.`,
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
await writeFile(join(srcIslands, `${island.id}.jsx`), island.module);
|
|
326
|
+
for (const side of ['client', 'server']) {
|
|
327
|
+
const file = join(srcDir, entryFile(island.id, side));
|
|
328
|
+
await writeFile(file, entrySource(island, side, provider));
|
|
329
|
+
(side === 'client' ? clientEntries : serverEntries)[`islands/${island.id}`] = file;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (diagnostics.some((d) => d.level === 'error'))
|
|
333
|
+
return done();
|
|
334
|
+
const sandboxPlugin = {
|
|
335
|
+
name: 'mint-sandbox',
|
|
336
|
+
setup(build) {
|
|
337
|
+
build.onLoad({ filter: /.*/, namespace: 'file' }, (args) => {
|
|
338
|
+
if (!allowed(args.path))
|
|
339
|
+
return outside();
|
|
340
|
+
if (opts.rejectHardLinks && hardLinked(args.path)) {
|
|
341
|
+
const pkg = packageOfPath(args.path)?.name;
|
|
342
|
+
return {
|
|
343
|
+
errors: [
|
|
344
|
+
{
|
|
345
|
+
text: `${pkg ?? 'A docs file'} contains a hard-linked file, which could point outside the package`,
|
|
346
|
+
...(pkg ? { detail: { pkg } } : {}),
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
const cssModulePlugin = {
|
|
356
|
+
name: 'mint-css-modules',
|
|
357
|
+
setup(build) {
|
|
358
|
+
build.onLoad({ filter: CSS_MODULE, namespace: 'file' }, (args) => ({
|
|
359
|
+
errors: [cssModule(args.path)],
|
|
360
|
+
}));
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
const ours = (importer) => inside(srcDir, importer) || inside(runtimeDir, importer);
|
|
364
|
+
const resolveInDocs = async (build, args) => {
|
|
365
|
+
const absolute = args.path.startsWith('/');
|
|
366
|
+
const rel = absolute ? docsPathToRelative(args.path) : args.path;
|
|
367
|
+
const resolveDir = absolute ? srcDocs : args.resolveDir;
|
|
368
|
+
if (rel === undefined || !inside(srcDocs, resolve(resolveDir, rel))) {
|
|
369
|
+
return { errors: [{ text: `${args.path} is outside the docs folder` }] };
|
|
370
|
+
}
|
|
371
|
+
const r = await build.resolve(absolute ? `./${rel}` : rel, {
|
|
372
|
+
kind: args.kind,
|
|
373
|
+
resolveDir,
|
|
374
|
+
pluginData: skipData,
|
|
375
|
+
});
|
|
376
|
+
return r.errors.length === 0 && inside(srcDocs, r.path)
|
|
377
|
+
? { path: r.path, suffix: r.suffix, sideEffects: r.sideEffects }
|
|
378
|
+
: { errors: [{ text: `Could not find ${args.path} in the docs folder` }] };
|
|
379
|
+
};
|
|
380
|
+
const sourcePlugin = {
|
|
381
|
+
name: 'mint-sources',
|
|
382
|
+
setup(build) {
|
|
383
|
+
build.onResolve({ filter: RUNTIME_SPECIFIER }, (args) => ({
|
|
384
|
+
path: runtimeFile(RUNTIME_SPECIFIER.exec(args.path)[1]),
|
|
385
|
+
}));
|
|
386
|
+
build.onResolve({ filter: /^@mintlify\/islands$/ }, () => ({ path: runtimeFile('theme') }));
|
|
387
|
+
build.onResolve({ filter: /^\// }, async (args) => {
|
|
388
|
+
if (isSkipped(args.pluginData) || !args.importer || !ours(args.importer))
|
|
389
|
+
return undefined;
|
|
390
|
+
return resolveInDocs(build, args);
|
|
391
|
+
});
|
|
392
|
+
build.onResolve({ filter: /^\.\.?(\/|$)/ }, async (args) => {
|
|
393
|
+
if (isSkipped(args.pluginData))
|
|
394
|
+
return undefined;
|
|
395
|
+
if (!inside(srcDocs, args.importer) && !inside(srcIslands, args.importer))
|
|
396
|
+
return undefined;
|
|
397
|
+
return resolveInDocs(build, args);
|
|
398
|
+
});
|
|
399
|
+
build.onResolve({ filter: /^[^./]/ }, async (args) => {
|
|
400
|
+
if (isSkipped(args.pluginData) || !args.importer || !ours(args.importer))
|
|
401
|
+
return undefined;
|
|
402
|
+
if (climbsOut(args.path)) {
|
|
403
|
+
return { errors: [{ text: `"${args.path}" must not contain ".." segments` }] };
|
|
404
|
+
}
|
|
405
|
+
const r = await build.resolve(args.path, {
|
|
406
|
+
kind: args.kind,
|
|
407
|
+
resolveDir: installRoot,
|
|
408
|
+
pluginData: skipData,
|
|
409
|
+
});
|
|
410
|
+
if (r.errors.length > 0) {
|
|
411
|
+
return {
|
|
412
|
+
errors: [
|
|
413
|
+
{
|
|
414
|
+
text: `Could not resolve "${args.path}". Is it listed in package.json dependencies?`,
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (!r.external && !allowed(r.path)) {
|
|
420
|
+
return outside(packageName(args.path), args.path);
|
|
421
|
+
}
|
|
422
|
+
return { path: r.path, sideEffects: r.sideEffects, external: r.external };
|
|
423
|
+
});
|
|
424
|
+
build.onLoad({ filter: /\.(js|mjs)$/ }, async (args) => inside(srcDocs, args.path)
|
|
425
|
+
? { loader: 'jsx', contents: await readFile(args.path, 'utf8') }
|
|
426
|
+
: undefined);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
const cssCollector = {
|
|
430
|
+
name: 'mint-css-collector',
|
|
431
|
+
setup(build) {
|
|
432
|
+
const stub = (path) => CSS_MODULE.test(path) ? { errors: [cssModule(path)] } : { path, namespace: CSS_STUB };
|
|
433
|
+
build.onResolve({ filter: CSS_SPECIFIER }, async (args) => {
|
|
434
|
+
if (isSkipped(args.pluginData))
|
|
435
|
+
return undefined;
|
|
436
|
+
const bare = !args.path.startsWith('.') && !args.path.startsWith('/');
|
|
437
|
+
const mine = ours(args.importer);
|
|
438
|
+
if (mine && !bare) {
|
|
439
|
+
const local = await resolveInDocs(build, args);
|
|
440
|
+
return local.path?.endsWith('.css') ? stub(local.path) : local;
|
|
441
|
+
}
|
|
442
|
+
if (bare && mine && climbsOut(args.path)) {
|
|
443
|
+
return { errors: [{ text: `"${args.path}" must not contain ".." segments` }] };
|
|
444
|
+
}
|
|
445
|
+
const resolveDir = bare && mine ? installRoot : args.resolveDir;
|
|
446
|
+
const r = await build.resolve(args.path, {
|
|
447
|
+
kind: args.kind,
|
|
448
|
+
resolveDir,
|
|
449
|
+
pluginData: skipData,
|
|
450
|
+
});
|
|
451
|
+
if (r.errors.length > 0)
|
|
452
|
+
return { errors: r.errors };
|
|
453
|
+
if (!allowed(r.path))
|
|
454
|
+
return outside(undefined, args.path);
|
|
455
|
+
return r.path.endsWith('.css')
|
|
456
|
+
? stub(r.path)
|
|
457
|
+
: { path: r.path, sideEffects: r.sideEffects };
|
|
458
|
+
});
|
|
459
|
+
build.onLoad({ filter: /.*/, namespace: CSS_STUB }, () => ({
|
|
460
|
+
contents: '',
|
|
461
|
+
loader: 'js',
|
|
462
|
+
}));
|
|
463
|
+
build.onLoad({ filter: /\.css$/, namespace: 'file' }, (args) => {
|
|
464
|
+
loadedCss.add(args.path);
|
|
465
|
+
return { contents: '', loader: 'js' };
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
const common = {
|
|
470
|
+
absWorkingDir: srcDir,
|
|
471
|
+
bundle: true,
|
|
472
|
+
splitting: true,
|
|
473
|
+
format: 'esm',
|
|
474
|
+
metafile: true,
|
|
475
|
+
jsx: 'automatic',
|
|
476
|
+
logLevel: 'silent',
|
|
477
|
+
assetNames: 'assets/[name]-[hash]',
|
|
478
|
+
chunkNames: '[name]-[hash]',
|
|
479
|
+
loader: ASSET_LOADERS,
|
|
480
|
+
};
|
|
481
|
+
const reactPlugin = singleReactPlugin({ installRoot, onReplaced: (r) => replacedReact.push(r) });
|
|
482
|
+
const escapes = (metafile) => Object.keys(metafile.inputs)
|
|
483
|
+
.filter((input) => !NAMESPACED.test(input))
|
|
484
|
+
.map((input) => resolve(srcDir, input))
|
|
485
|
+
.filter((path) => !allowed(path))
|
|
486
|
+
.map(() => ({ text: outsideText() }));
|
|
487
|
+
const docsGlobs = (metafile) => Object.entries(metafile.inputs).flatMap(([input, { imports }]) => {
|
|
488
|
+
const importer = resolve(srcDir, input);
|
|
489
|
+
if (!inside(srcDocs, importer) && !inside(srcIslands, importer))
|
|
490
|
+
return [];
|
|
491
|
+
return imports
|
|
492
|
+
.filter((i) => i.path.includes('*'))
|
|
493
|
+
.map((i) => ({
|
|
494
|
+
text: globLeaves(srcDocs, dirname(importer), i.path)
|
|
495
|
+
? `${i.path} is outside the docs folder`
|
|
496
|
+
: `the import of ${i.path} builds its path at runtime; import each file statically`,
|
|
497
|
+
location: { file: input },
|
|
498
|
+
}));
|
|
499
|
+
});
|
|
500
|
+
const run = async (label, options) => {
|
|
501
|
+
let errors;
|
|
502
|
+
try {
|
|
503
|
+
const result = await esbuild.build({ ...options, write: false, metafile: true });
|
|
504
|
+
errors = [...docsGlobs(result.metafile), ...escapes(result.metafile)];
|
|
505
|
+
if (errors.length === 0) {
|
|
506
|
+
for (const file of result.outputFiles) {
|
|
507
|
+
await mkdir(dirname(file.path), { recursive: true });
|
|
508
|
+
await writeFile(file.path, file.contents);
|
|
509
|
+
}
|
|
510
|
+
return result.metafile;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
errors = err.errors ?? [
|
|
515
|
+
{ text: String(err) },
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
const messages = formatMessages(errors, srcDir, opts.islands);
|
|
519
|
+
for (const [i, message] of messages.slice(0, 20).entries()) {
|
|
520
|
+
diagnostics.push({
|
|
521
|
+
level: 'error',
|
|
522
|
+
code: 'bundle-error',
|
|
523
|
+
message: `${label} build: ${message}`,
|
|
524
|
+
...withPkg(packageFromMessage(errors[i])),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return undefined;
|
|
528
|
+
};
|
|
529
|
+
const hasEntries = Object.keys(clientEntries).length > 0;
|
|
530
|
+
const client = hasEntries
|
|
531
|
+
? await run('client', {
|
|
532
|
+
...common,
|
|
533
|
+
entryPoints: clientEntries,
|
|
534
|
+
outdir: join(opts.outDir, 'client'),
|
|
535
|
+
platform: 'browser',
|
|
536
|
+
target: ['es2020'],
|
|
537
|
+
minify: true,
|
|
538
|
+
define: {
|
|
539
|
+
'process.env.NODE_ENV': '"production"',
|
|
540
|
+
'process.env': '{}',
|
|
541
|
+
global: 'globalThis',
|
|
542
|
+
},
|
|
543
|
+
plugins: [sandboxPlugin, reactPlugin, cssModulePlugin, cssCollector, sourcePlugin],
|
|
544
|
+
})
|
|
545
|
+
: undefined;
|
|
546
|
+
const server = client
|
|
547
|
+
? await run('server', {
|
|
548
|
+
...common,
|
|
549
|
+
entryPoints: serverEntries,
|
|
550
|
+
outdir: join(opts.outDir, 'server'),
|
|
551
|
+
platform: 'node',
|
|
552
|
+
target: ['node18'],
|
|
553
|
+
minifyWhitespace: true,
|
|
554
|
+
minifySyntax: true,
|
|
555
|
+
outExtension: { '.js': '.mjs' },
|
|
556
|
+
banner: { js: SERVER_BANNER },
|
|
557
|
+
define: { 'process.env.NODE_ENV': '"production"' },
|
|
558
|
+
loader: { ...ASSET_LOADERS, '.css': 'empty' },
|
|
559
|
+
plugins: [
|
|
560
|
+
sandboxPlugin,
|
|
561
|
+
singleReactPlugin({ installRoot, onReplaced: () => { } }),
|
|
562
|
+
cssModulePlugin,
|
|
563
|
+
sourcePlugin,
|
|
564
|
+
],
|
|
565
|
+
})
|
|
566
|
+
: undefined;
|
|
567
|
+
let css;
|
|
568
|
+
const cssSpecifiers = [...new Set(opts.packages.css ?? [])];
|
|
569
|
+
const collected = client
|
|
570
|
+
? importedCss(client, opts.islands
|
|
571
|
+
.map((i) => i.id)
|
|
572
|
+
.sort()
|
|
573
|
+
.map((id) => entryFile(id, 'client')), srcDir, loadedCss)
|
|
574
|
+
: { order: [], missed: [] };
|
|
575
|
+
for (const path of collected.missed.slice(0, 20)) {
|
|
576
|
+
diagnostics.push({
|
|
577
|
+
level: 'error',
|
|
578
|
+
code: 'css-not-collected',
|
|
579
|
+
message: `${shownPath(path, srcDocs)} is loaded from JS in a way the builder cannot trace to an island, so styles.css would leave it out. Import it with a static path from a snippet or island.`,
|
|
580
|
+
...withPkg(packageOfPath(path)?.name),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const jsCss = collected.order;
|
|
584
|
+
const jsOk = !hasEntries || (client !== undefined && server !== undefined);
|
|
585
|
+
if (jsOk && (cssSpecifiers.length > 0 || jsCss.length > 0)) {
|
|
586
|
+
const packageCss = new Map();
|
|
587
|
+
const packageFiles = new Set();
|
|
588
|
+
const cssOptions = (imports, record) => ({
|
|
589
|
+
absWorkingDir: srcDir,
|
|
590
|
+
stdin: {
|
|
591
|
+
contents: imports.map((p) => `@import ${JSON.stringify(p)};`).join('\n'),
|
|
592
|
+
loader: 'css',
|
|
593
|
+
resolveDir: srcDir,
|
|
594
|
+
sourcefile: 'styles.css',
|
|
595
|
+
},
|
|
596
|
+
bundle: true,
|
|
597
|
+
minify: true,
|
|
598
|
+
metafile: true,
|
|
599
|
+
logLevel: 'silent',
|
|
600
|
+
outfile: join(opts.outDir, 'client', 'styles.css'),
|
|
601
|
+
assetNames: 'assets/[name]-[hash]',
|
|
602
|
+
loader: ASSET_LOADERS,
|
|
603
|
+
plugins: [
|
|
604
|
+
sandboxPlugin,
|
|
605
|
+
cssModulePlugin,
|
|
606
|
+
{
|
|
607
|
+
name: 'mint-css-entries',
|
|
608
|
+
setup(build) {
|
|
609
|
+
build.onStart(async () => {
|
|
610
|
+
for (const specifier of cssSpecifiers) {
|
|
611
|
+
packageCss.set(specifier, await build.resolve(specifier, {
|
|
612
|
+
kind: 'import-rule',
|
|
613
|
+
resolveDir: installRoot,
|
|
614
|
+
pluginData: skipData,
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
if (record) {
|
|
619
|
+
build.onLoad({ filter: /.*/, namespace: 'file' }, (args) => {
|
|
620
|
+
packageFiles.add(args.path);
|
|
621
|
+
return undefined;
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
const firstAt = (path) => cssSpecifiers.findIndex((s) => packageCss.get(s)?.path === path);
|
|
625
|
+
build.onLoad({ filter: /.*/, namespace: CSS_DUPLICATE }, () => ({
|
|
626
|
+
contents: '',
|
|
627
|
+
loader: 'css',
|
|
628
|
+
}));
|
|
629
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
630
|
+
if (isSkipped(args.pluginData))
|
|
631
|
+
return undefined;
|
|
632
|
+
if (inside(srcDocs, args.importer)) {
|
|
633
|
+
if (URL_LIKE.test(args.path))
|
|
634
|
+
return undefined;
|
|
635
|
+
const local = await resolveInDocs(build, args);
|
|
636
|
+
if (!local.errors || !isPackageSpecifier(args.path))
|
|
637
|
+
return local;
|
|
638
|
+
const pkg = await build.resolve(args.path, {
|
|
639
|
+
kind: args.kind,
|
|
640
|
+
resolveDir: installRoot,
|
|
641
|
+
pluginData: skipData,
|
|
642
|
+
});
|
|
643
|
+
if (pkg.errors.length > 0)
|
|
644
|
+
return local;
|
|
645
|
+
return allowed(pkg.path) ? { path: pkg.path } : outside(undefined, args.path);
|
|
646
|
+
}
|
|
647
|
+
if (relative(join(srcDir, 'styles.css'), args.importer) !== '')
|
|
648
|
+
return undefined;
|
|
649
|
+
const duplicate = { path: args.path, namespace: CSS_DUPLICATE };
|
|
650
|
+
if (isAbsolute(args.path))
|
|
651
|
+
return packageFiles.has(args.path) ? duplicate : undefined;
|
|
652
|
+
const r = packageCss.get(args.path);
|
|
653
|
+
if (!r || r.errors.length > 0) {
|
|
654
|
+
return {
|
|
655
|
+
errors: [
|
|
656
|
+
{ text: `packages.css entry "${args.path}" was not found in node_modules` },
|
|
657
|
+
],
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (firstAt(r.path) < cssSpecifiers.indexOf(args.path))
|
|
661
|
+
return duplicate;
|
|
662
|
+
return allowed(r.path)
|
|
663
|
+
? { path: r.path }
|
|
664
|
+
: outside(packageName(args.path), args.path);
|
|
665
|
+
});
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
});
|
|
670
|
+
if (cssSpecifiers.length > 0 && jsCss.length > 0) {
|
|
671
|
+
await esbuild
|
|
672
|
+
.build({ ...cssOptions(cssSpecifiers, true), write: false })
|
|
673
|
+
.catch(() => undefined);
|
|
674
|
+
}
|
|
675
|
+
css = await run('css', cssOptions([...cssSpecifiers, ...jsCss], false));
|
|
676
|
+
}
|
|
677
|
+
return done({ client, server, css });
|
|
678
|
+
}
|
|
679
|
+
function withPkg(pkg) {
|
|
680
|
+
return pkg ? { pkg } : {};
|
|
681
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { BuildResult } from './types.js';
|
|
3
|
+
type Args = {
|
|
4
|
+
command?: string;
|
|
5
|
+
input?: string;
|
|
6
|
+
out?: string;
|
|
7
|
+
installer?: string;
|
|
8
|
+
json: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function parseArgs(argv: string[]): Args;
|
|
11
|
+
export declare function formatResult(result: BuildResult): string;
|
|
12
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
13
|
+
export {};
|