@thallylabs/migrate 0.2.32 → 0.2.34
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/README.md +60 -9
- package/dist/index.d.ts +13 -5
- package/dist/index.js +769 -253
- package/dist/index.js.map +1 -1
- package/dist/{url-migration-Coy62q1L.d.ts → url-migration-EwtII4P-.d.ts} +20 -0
- package/dist/url-migration.d.ts +1 -1
- package/dist/url-migration.js +54 -22
- package/dist/url-migration.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -230,8 +230,11 @@ var JS_GLOBALS = /* @__PURE__ */ new Set([
|
|
|
230
230
|
"BroadcastChannel"
|
|
231
231
|
]);
|
|
232
232
|
var IMPLICIT_MODULE_NAMES = /* @__PURE__ */ new Set(["React", "MintlifyComponents"]);
|
|
233
|
-
function
|
|
234
|
-
return ts
|
|
233
|
+
function scriptKindFor(fileName) {
|
|
234
|
+
return /\.[cm]?ts$/.test(fileName) ? ts.ScriptKind.TS : ts.ScriptKind.TSX;
|
|
235
|
+
}
|
|
236
|
+
function parse(source, fileName = "inline.tsx") {
|
|
237
|
+
return ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKindFor(fileName));
|
|
235
238
|
}
|
|
236
239
|
function bindingNames(name, into) {
|
|
237
240
|
if (ts.isIdentifier(name)) into.add(name.text);
|
|
@@ -240,20 +243,44 @@ function bindingNames(name, into) {
|
|
|
240
243
|
function isFunctionLike(node) {
|
|
241
244
|
return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node);
|
|
242
245
|
}
|
|
246
|
+
function hoistedVarNames(root, into) {
|
|
247
|
+
const collect = (node) => {
|
|
248
|
+
if (isFunctionLike(node) || ts.isClassLike(node) || ts.isModuleDeclaration(node)) return;
|
|
249
|
+
if (ts.isVariableDeclaration(node) && !ts.isCatchClause(node.parent) && !(ts.getCombinedNodeFlags(node) & ts.NodeFlags.BlockScoped)) bindingNames(node.name, into);
|
|
250
|
+
ts.forEachChild(node, collect);
|
|
251
|
+
};
|
|
252
|
+
collect(root);
|
|
253
|
+
}
|
|
243
254
|
function functionScope(fn) {
|
|
244
255
|
const scope = /* @__PURE__ */ new Set();
|
|
245
256
|
if ((ts.isFunctionExpression(fn) || ts.isFunctionDeclaration(fn)) && fn.name) scope.add(fn.name.text);
|
|
246
257
|
for (const parameter of fn.parameters) bindingNames(parameter.name, scope);
|
|
247
|
-
function collect(node) {
|
|
248
|
-
if (ts.isVariableDeclaration(node)) bindingNames(node.name, scope);
|
|
249
|
-
if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) scope.add(node.name.text);
|
|
250
|
-
if (isFunctionLike(node)) return;
|
|
251
|
-
ts.forEachChild(node, collect);
|
|
252
|
-
}
|
|
253
|
-
if (fn.body) ts.forEachChild(fn.body, collect);
|
|
254
|
-
if (fn.body && !ts.isBlock(fn.body)) collect(fn.body);
|
|
255
258
|
return scope;
|
|
256
259
|
}
|
|
260
|
+
function lexicalNames(statements) {
|
|
261
|
+
const names = /* @__PURE__ */ new Set();
|
|
262
|
+
for (const statement of statements) {
|
|
263
|
+
if (ts.isVariableStatement(statement)) {
|
|
264
|
+
if (statement.declarationList.flags & ts.NodeFlags.BlockScoped) for (const declaration of statement.declarationList.declarations) bindingNames(declaration.name, names);
|
|
265
|
+
} else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) names.add(statement.name.text);
|
|
266
|
+
}
|
|
267
|
+
return names;
|
|
268
|
+
}
|
|
269
|
+
function ownScope(node) {
|
|
270
|
+
if (ts.isBlock(node)) return lexicalNames(node.statements);
|
|
271
|
+
if (ts.isCaseBlock(node)) return lexicalNames(node.clauses.flatMap((clause) => [...clause.statements]));
|
|
272
|
+
if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer)) {
|
|
273
|
+
const names = /* @__PURE__ */ new Set();
|
|
274
|
+
if (node.initializer.flags & ts.NodeFlags.BlockScoped) for (const declaration of node.initializer.declarations) bindingNames(declaration.name, names);
|
|
275
|
+
return names;
|
|
276
|
+
}
|
|
277
|
+
if (ts.isCatchClause(node) && node.variableDeclaration) {
|
|
278
|
+
const names = /* @__PURE__ */ new Set();
|
|
279
|
+
bindingNames(node.variableDeclaration.name, names);
|
|
280
|
+
return names;
|
|
281
|
+
}
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
257
284
|
function collectRefs(root) {
|
|
258
285
|
const refs = [];
|
|
259
286
|
const assigned = [];
|
|
@@ -268,13 +295,22 @@ function collectRefs(root) {
|
|
|
268
295
|
function walkNode(node, scopes, inAsync) {
|
|
269
296
|
if (ts.isTypeNode(node)) return;
|
|
270
297
|
if (isFunctionLike(node)) {
|
|
271
|
-
const
|
|
298
|
+
const parameterScopes = [...scopes, functionScope(node)];
|
|
272
299
|
const async = !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword);
|
|
273
300
|
for (const parameter of node.parameters) {
|
|
274
|
-
if (!ts.isIdentifier(parameter.name)) walkNode(parameter.name,
|
|
275
|
-
if (parameter.initializer) walkNode(parameter.initializer,
|
|
301
|
+
if (!ts.isIdentifier(parameter.name)) walkNode(parameter.name, parameterScopes, async);
|
|
302
|
+
if (parameter.initializer) walkNode(parameter.initializer, parameterScopes, async);
|
|
303
|
+
}
|
|
304
|
+
if (node.body) {
|
|
305
|
+
const hoisted = /* @__PURE__ */ new Set();
|
|
306
|
+
hoistedVarNames(node.body, hoisted);
|
|
307
|
+
walkNode(node.body, [...parameterScopes, hoisted], async);
|
|
276
308
|
}
|
|
277
|
-
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const own2 = ownScope(node);
|
|
312
|
+
if (own2) {
|
|
313
|
+
ts.forEachChild(node, (child) => walkNode(child, [...scopes, own2], inAsync));
|
|
278
314
|
return;
|
|
279
315
|
}
|
|
280
316
|
if (ts.isAwaitExpression(node) && !inAsync) awaitOutsideAsync = true;
|
|
@@ -651,6 +687,23 @@ function unboundTags(moduleSource, boundNames) {
|
|
|
651
687
|
}
|
|
652
688
|
return [...tags].sort();
|
|
653
689
|
}
|
|
690
|
+
function unboundBuiltinTags(moduleSource, boundNames, fileName) {
|
|
691
|
+
const tags = /* @__PURE__ */ new Set();
|
|
692
|
+
const values = /* @__PURE__ */ new Set();
|
|
693
|
+
for (const statement of parse(moduleSource, fileName).statements) {
|
|
694
|
+
if (ts.isImportDeclaration(statement)) continue;
|
|
695
|
+
for (const { name, kind } of collectRefs(statement).refs) {
|
|
696
|
+
if (boundNames.has(name)) continue;
|
|
697
|
+
if (kind === "tag" || kind === "member") tags.add(name);
|
|
698
|
+
else values.add(name);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const isGlobal = (name) => JS_GLOBALS.has(name) || BROWSER_IDENTIFIERS.has(name) || SERVER_ONLY_IDENTIFIERS.has(name);
|
|
702
|
+
const builtinTags = [...tags].filter(isThallyBuiltinComponent);
|
|
703
|
+
const shadowed = builtinTags.filter((name) => JS_GLOBALS.has(name) && values.has(name)).sort();
|
|
704
|
+
const builtinValues = [...values].filter((name) => isThallyBuiltinComponent(name) && !isGlobal(name));
|
|
705
|
+
return { bind: [.../* @__PURE__ */ new Set([...builtinTags.filter((name) => !shadowed.includes(name)), ...builtinValues])].sort(), shadowed };
|
|
706
|
+
}
|
|
654
707
|
var INLINE_SAFE_GLOBALS = /* @__PURE__ */ new Set([
|
|
655
708
|
"undefined",
|
|
656
709
|
"NaN",
|
|
@@ -719,12 +772,14 @@ function isSelfContainedFunction(node) {
|
|
|
719
772
|
function pageScopeNames(esmSources) {
|
|
720
773
|
const names = /* @__PURE__ */ new Set();
|
|
721
774
|
for (const source of esmSources) {
|
|
722
|
-
|
|
775
|
+
const sf = parse(source);
|
|
776
|
+
for (const statement of sf.statements) {
|
|
723
777
|
for (const local of importLocals(statement.getText())) names.add(local);
|
|
724
778
|
if (ts.isImportDeclaration(statement)) continue;
|
|
725
779
|
const target2 = ts.isExportDeclaration(statement) || ts.isExportAssignment(statement) ? void 0 : statement;
|
|
726
780
|
if (target2) for (const name of declaredNames(target2).names) names.add(name);
|
|
727
781
|
}
|
|
782
|
+
hoistedVarNames(sf, names);
|
|
728
783
|
}
|
|
729
784
|
return names;
|
|
730
785
|
}
|
|
@@ -2393,9 +2448,9 @@ function escapeOrphanCapitalizedTags(body) {
|
|
|
2393
2448
|
continue;
|
|
2394
2449
|
}
|
|
2395
2450
|
const start = match.index;
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
opens.set(name,
|
|
2451
|
+
const list2 = opens.get(name) ?? [];
|
|
2452
|
+
list2.push({ start, end: start + full.length });
|
|
2453
|
+
opens.set(name, list2);
|
|
2399
2454
|
}
|
|
2400
2455
|
const edits = [];
|
|
2401
2456
|
for (const [name, ranges] of opens) {
|
|
@@ -2934,13 +2989,7 @@ function sanitizeCssModuleSelectors(css) {
|
|
|
2934
2989
|
return { css: root.toString(), omitted };
|
|
2935
2990
|
}
|
|
2936
2991
|
function sourceFile(source, filename) {
|
|
2937
|
-
return ts2.createSourceFile(
|
|
2938
|
-
filename,
|
|
2939
|
-
source,
|
|
2940
|
-
ts2.ScriptTarget.Latest,
|
|
2941
|
-
true,
|
|
2942
|
-
/\.[cm]?ts$/.test(filename) ? ts2.ScriptKind.TS : ts2.ScriptKind.TSX
|
|
2943
|
-
);
|
|
2992
|
+
return ts2.createSourceFile(filename, source, ts2.ScriptTarget.Latest, true, scriptKindFor(filename));
|
|
2944
2993
|
}
|
|
2945
2994
|
function walk(node, visitor) {
|
|
2946
2995
|
visitor(node);
|
|
@@ -2997,7 +3046,7 @@ function needsClientBoundary(source) {
|
|
|
2997
3046
|
visitNode(source);
|
|
2998
3047
|
return needed;
|
|
2999
3048
|
}
|
|
3000
|
-
function implicitReactImports(source) {
|
|
3049
|
+
function implicitReactImports(source, onShadowed) {
|
|
3001
3050
|
const bindings = /* @__PURE__ */ new Set();
|
|
3002
3051
|
const references = /* @__PURE__ */ new Set();
|
|
3003
3052
|
function bind(name) {
|
|
@@ -3015,7 +3064,9 @@ function implicitReactImports(source) {
|
|
|
3015
3064
|
}
|
|
3016
3065
|
if (ts2.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) bind(declaration.name);
|
|
3017
3066
|
if ((ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement)) && statement.name) bindings.add(statement.name.text);
|
|
3067
|
+
if ((ts2.isEnumDeclaration(statement) || ts2.isModuleDeclaration(statement) || ts2.isImportEqualsDeclaration(statement)) && ts2.isIdentifier(statement.name)) bindings.add(statement.name.text);
|
|
3018
3068
|
}
|
|
3069
|
+
hoistedVarNames(source, bindings);
|
|
3019
3070
|
function visit(node) {
|
|
3020
3071
|
if (ts2.isIdentifier(node) && !(ts2.isPropertyAccessExpression(node.parent) && node.parent.name === node) && !(ts2.isPropertyAssignment(node.parent) && node.parent.name === node)) references.add(node.text);
|
|
3021
3072
|
ts2.forEachChild(node, visit);
|
|
@@ -3023,26 +3074,50 @@ function implicitReactImports(source) {
|
|
|
3023
3074
|
visit(source);
|
|
3024
3075
|
const hooks = [...REACT_GLOBALS].filter((name) => references.has(name) && !bindings.has(name)).sort();
|
|
3025
3076
|
const usesMintlifyComponents = references.has("MintlifyComponents") && !bindings.has("MintlifyComponents");
|
|
3077
|
+
const leafBound = /* @__PURE__ */ new Set();
|
|
3026
3078
|
const implicitComponents = MINTLIFY_COMPONENT_MODULES.map(({ module, names }) => {
|
|
3027
|
-
const needed = names.filter((name) => references.has(name) && !bindings.has(name));
|
|
3079
|
+
const needed = names.filter((name) => name !== "Note" && references.has(name) && !bindings.has(name));
|
|
3080
|
+
for (const name of needed) leafBound.add(name);
|
|
3028
3081
|
return needed.length ? `import { ${needed.join(", ")} } from '${module}';` : "";
|
|
3029
3082
|
}).filter(Boolean);
|
|
3083
|
+
const { bind: builtinTags, shadowed } = unboundBuiltinTags(source.text, bindings, source.fileName);
|
|
3084
|
+
const registryNames = builtinTags.filter((name) => !leafBound.has(name));
|
|
3085
|
+
if (shadowed.length) onShadowed?.(shadowed);
|
|
3030
3086
|
return [
|
|
3031
3087
|
references.has("React") && !bindings.has("React") ? "import * as React from 'react';" : "",
|
|
3032
3088
|
hooks.length ? `import { ${hooks.join(", ")} } from 'react';` : "",
|
|
3033
3089
|
...implicitComponents,
|
|
3090
|
+
registryNames.length ? builtinRegistryBindings(registryNames, identifierNames(source)) : "",
|
|
3034
3091
|
usesMintlifyComponents ? mintlifyComponentsShim() : ""
|
|
3035
3092
|
].filter(Boolean).join("\n");
|
|
3036
3093
|
}
|
|
3037
3094
|
function resolveInlineBuiltinReferences(source, moduleNames) {
|
|
3038
3095
|
const unresolved = unboundTags(source, moduleNames);
|
|
3039
3096
|
if (unresolved.length === 0) return source;
|
|
3097
|
+
return [builtinRegistryBindings(unresolved, identifierNames(sourceFile(source, "inline.jsx"))), source].join("\n");
|
|
3098
|
+
}
|
|
3099
|
+
function identifierNames(root) {
|
|
3100
|
+
const names = /* @__PURE__ */ new Set();
|
|
3101
|
+
const visit = (node) => {
|
|
3102
|
+
if (ts2.isIdentifier(node)) names.add(node.text);
|
|
3103
|
+
ts2.forEachChild(node, visit);
|
|
3104
|
+
};
|
|
3105
|
+
visit(root);
|
|
3106
|
+
return names;
|
|
3107
|
+
}
|
|
3108
|
+
function builtinRegistryBindings(names, used) {
|
|
3109
|
+
const registry = "builtinMdxComponents";
|
|
3110
|
+
let alias = registry;
|
|
3111
|
+
for (let suffix = 2; used.has(alias); suffix++) alias = `${registry}${suffix}`;
|
|
3040
3112
|
return [
|
|
3041
|
-
|
|
3042
|
-
`const { ${
|
|
3043
|
-
source
|
|
3113
|
+
`import { ${alias === registry ? registry : `${registry} as ${alias}`} } from '@/components/mdx/builtin-components';`,
|
|
3114
|
+
`const { ${names.join(", ")} } = ${alias};`
|
|
3044
3115
|
].join("\n");
|
|
3045
3116
|
}
|
|
3117
|
+
function shadowedBuiltinMessage(file, names) {
|
|
3118
|
+
const tags = names.map((name) => `<${name}>`).join(", ");
|
|
3119
|
+
return `${file} renders ${tags} as a Thally built-in component but also uses the JavaScript global of the same name, so the tag was left unbound. Rename the component or the global use in the source.`;
|
|
3120
|
+
}
|
|
3046
3121
|
function imports(statement) {
|
|
3047
3122
|
if (!ts2.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) return [];
|
|
3048
3123
|
const clause = statement.importClause;
|
|
@@ -3269,6 +3344,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
3269
3344
|
function copyGraph(entry) {
|
|
3270
3345
|
const staged = /* @__PURE__ */ new Map();
|
|
3271
3346
|
const stagedWarnings = [];
|
|
3347
|
+
const stagedShadowed = [];
|
|
3272
3348
|
const modules = /* @__PURE__ */ new Map();
|
|
3273
3349
|
let stagedBytes = 0;
|
|
3274
3350
|
function stageShim(filename, content) {
|
|
@@ -3373,7 +3449,8 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
|
|
|
3373
3449
|
const prologue = `// @ts-nocheck
|
|
3374
3450
|
${client ? "'use client';\n" : ""}
|
|
3375
3451
|
`;
|
|
3376
|
-
|
|
3452
|
+
const implicit = implicitReactImports(ast, (names) => stagedShadowed.push({ path, names }));
|
|
3453
|
+
return shebangMatch ? shebangMatch[0] + prologue + implicit + "\n" + replaced.slice(shebangMatch[0].length) : prologue + implicit + "\n" + replaced;
|
|
3377
3454
|
}
|
|
3378
3455
|
});
|
|
3379
3456
|
}
|
|
@@ -3391,6 +3468,7 @@ ${client ? "'use client';\n" : ""}
|
|
|
3391
3468
|
}
|
|
3392
3469
|
for (const [path, module] of modules) staged.set(path, { path, content: module.render(clientModules.has(path)) });
|
|
3393
3470
|
for (const [path, file] of staged) copied.set(path, file);
|
|
3471
|
+
for (const { path, names } of stagedShadowed) warn(shadowedBuiltinMessage(relative2(root, path).replace(/\\/g, "/"), names), path);
|
|
3394
3472
|
for (const { path, omitted } of stagedWarnings) {
|
|
3395
3473
|
warn(`Site-authored CSS module ${relative2(root, path).replace(/\\/g, "/")} has ${omitted} global-only selector branch(es) that cannot be copied into Thally CSS Modules; those styles were omitted.`, path);
|
|
3396
3474
|
}
|
|
@@ -3845,8 +3923,12 @@ ${inlineSource}` });
|
|
|
3845
3923
|
}`),
|
|
3846
3924
|
""
|
|
3847
3925
|
].join("\n");
|
|
3926
|
+
const implicit = implicitReactImports(
|
|
3927
|
+
sourceFile(inlineSource, "inline.jsx"),
|
|
3928
|
+
(names) => warn(shadowedBuiltinMessage(relative2(root, currentFile).replace(/\\/g, "/"), names), currentFile)
|
|
3929
|
+
);
|
|
3848
3930
|
copied.set(path, { path, content: `'use client';
|
|
3849
|
-
${
|
|
3931
|
+
${implicit}
|
|
3850
3932
|
${inlineSource}` });
|
|
3851
3933
|
copiedBytes += Buffer.byteLength(inlineSource);
|
|
3852
3934
|
for (const { node, name } of extracted) {
|
|
@@ -4238,24 +4320,25 @@ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as rea
|
|
|
4238
4320
|
import { dirname as dirname2, extname as extname3, relative as relative3 } from "path";
|
|
4239
4321
|
var MAX_MINTLIFY_CONFIG_BYTES = 2e7;
|
|
4240
4322
|
function addMintlifyDirectoryRedirects(config, pages) {
|
|
4241
|
-
const pageIds = new Set(pages.map((page) => page.id));
|
|
4323
|
+
const pageIds = new Set(pages.map((page) => page.id.toLowerCase()));
|
|
4242
4324
|
const redirects = [...config.redirects ?? []];
|
|
4243
|
-
const redirectSources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
4325
|
+
const redirectSources = new Set(redirects.map((redirect) => (redirect.source.replace(/\/$/, "") || "/").toLowerCase()));
|
|
4244
4326
|
function hasAuthoredRedirect(source) {
|
|
4245
|
-
|
|
4327
|
+
const route = source.toLowerCase();
|
|
4328
|
+
if (redirectSources.has(route)) return true;
|
|
4246
4329
|
return (config.redirects ?? []).some((redirect) => {
|
|
4247
|
-
const match = redirect.source.match(/^(.*)\/:[
|
|
4248
|
-
return match ?
|
|
4330
|
+
const match = redirect.source.toLowerCase().match(/^(.*)\/:[a-z_][a-z0-9_]*\*\/?$/);
|
|
4331
|
+
return match ? route === match[1] || route.startsWith(`${match[1]}/`) : false;
|
|
4249
4332
|
});
|
|
4250
4333
|
}
|
|
4251
4334
|
for (const page of pages) {
|
|
4252
4335
|
if (!/\/(?:overview|introduction)$/.test(page.navigationId)) continue;
|
|
4253
4336
|
const parent = page.id.replace(/\/(?:overview|introduction)$/, "");
|
|
4254
|
-
if (!parent || pageIds.has(parent)) continue;
|
|
4337
|
+
if (!parent || pageIds.has(parent.toLowerCase())) continue;
|
|
4255
4338
|
const source = `/${parent}`;
|
|
4256
4339
|
if (hasAuthoredRedirect(source)) continue;
|
|
4257
4340
|
redirects.push({ source, destination: `/${page.id}`, permanent: false });
|
|
4258
|
-
redirectSources.add(source);
|
|
4341
|
+
redirectSources.add(source.toLowerCase());
|
|
4259
4342
|
}
|
|
4260
4343
|
const navigationOrder = [];
|
|
4261
4344
|
function collectPages(nodes) {
|
|
@@ -4285,11 +4368,11 @@ function addMintlifyDirectoryRedirects(config, pages) {
|
|
|
4285
4368
|
}
|
|
4286
4369
|
}
|
|
4287
4370
|
for (const [directory, landing] of firstDescendantByDirectory) {
|
|
4288
|
-
if (pageIds.has(directory)) continue;
|
|
4371
|
+
if (pageIds.has(directory.toLowerCase())) continue;
|
|
4289
4372
|
const source = `/${directory}`;
|
|
4290
4373
|
if (hasAuthoredRedirect(source)) continue;
|
|
4291
4374
|
redirects.push({ source, destination: `/${landing.id}`, permanent: false });
|
|
4292
|
-
redirectSources.add(source);
|
|
4375
|
+
redirectSources.add(source.toLowerCase());
|
|
4293
4376
|
}
|
|
4294
4377
|
return redirects.length > 0 ? { ...config, redirects } : config;
|
|
4295
4378
|
}
|
|
@@ -4308,11 +4391,11 @@ function addMintlifyHomepageRedirects(config, pages) {
|
|
|
4308
4391
|
const homepage = config.tabs.filter((tab) => !tab.hidden).map((tab) => firstPage([...tab.pages ?? [], ...tab.groups ?? []])).find(Boolean);
|
|
4309
4392
|
if (!homepage) return config;
|
|
4310
4393
|
const redirects = [...config.redirects ?? []];
|
|
4311
|
-
const sources = new Set(redirects.map((redirect) => redirect.source.replace(/\/$/, "") || "/"));
|
|
4394
|
+
const sources = new Set(redirects.map((redirect) => (redirect.source.replace(/\/$/, "") || "/").toLowerCase()));
|
|
4312
4395
|
const roots = ["", ...(config.i18n?.locales ?? []).filter((locale) => locale.code !== config.i18n?.defaultLocale).filter((locale) => homepage !== "introduction" || pages.some((page) => page.locale === locale.code && page.navigationId === homepage)).map((locale) => locale.code)];
|
|
4313
4396
|
for (const locale of roots) {
|
|
4314
4397
|
const source = locale ? `/${locale}` : "/";
|
|
4315
|
-
if (sources.has(source)) continue;
|
|
4398
|
+
if (sources.has(source.toLowerCase())) continue;
|
|
4316
4399
|
redirects.push({ source, destination: `${locale ? `/${locale}` : ""}/${homepage}`, permanent: true });
|
|
4317
4400
|
}
|
|
4318
4401
|
return { ...config, redirects };
|
|
@@ -4439,6 +4522,22 @@ function specSource(value) {
|
|
|
4439
4522
|
const source = typeof value === "string" ? value : objectValue2(value)?.source;
|
|
4440
4523
|
return typeof source === "string" && source.trim() ? source.trim() : void 0;
|
|
4441
4524
|
}
|
|
4525
|
+
function listedOperations(container) {
|
|
4526
|
+
const operations = [];
|
|
4527
|
+
const visit = (entry, root) => {
|
|
4528
|
+
if (typeof entry === "string") {
|
|
4529
|
+
if (OPENAPI_OPERATION_REF.test(entry)) operations.push(entry.trim());
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
const object2 = objectValue2(entry);
|
|
4533
|
+
if (!object2 || !root && (specSource(object2.openapi) || specSource(object2.asyncapi))) return;
|
|
4534
|
+
for (const key of ["groups", "pages"]) {
|
|
4535
|
+
if (Array.isArray(object2[key])) object2[key].forEach((child) => visit(child, false));
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
visit(container, true);
|
|
4539
|
+
return operations;
|
|
4540
|
+
}
|
|
4442
4541
|
var hasNavigationChildren = (node) => ["groups", "pages"].some((key) => Array.isArray(node[key]) && node[key].length > 0);
|
|
4443
4542
|
function mintlifyNavigationApiReferences(config) {
|
|
4444
4543
|
const navigation = objectValue2(config.navigation) ?? config;
|
|
@@ -4450,12 +4549,14 @@ function mintlifyNavigationApiReferences(config) {
|
|
|
4450
4549
|
const value = specSource(node[kind]);
|
|
4451
4550
|
const directory = objectValue2(node[kind])?.directory;
|
|
4452
4551
|
if (value) {
|
|
4552
|
+
const operations = typeof directory === "string" && directory.trim() ? listedOperations(node) : [];
|
|
4453
4553
|
references.push({
|
|
4454
4554
|
value,
|
|
4455
4555
|
kind,
|
|
4456
4556
|
tabLabel,
|
|
4457
4557
|
...sibling,
|
|
4458
|
-
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {}
|
|
4558
|
+
...typeof directory === "string" && directory.trim() ? { directory: directory.trim() } : {},
|
|
4559
|
+
...operations.length > 0 ? { operations } : {}
|
|
4459
4560
|
});
|
|
4460
4561
|
}
|
|
4461
4562
|
}
|
|
@@ -7177,8 +7278,49 @@ function splitOpenApiRef(raw) {
|
|
|
7177
7278
|
const specRef = match[1] ? unquote(match[1]) : void 0;
|
|
7178
7279
|
return { ...specRef ? { specRef } : {}, operation: match[2] };
|
|
7179
7280
|
}
|
|
7281
|
+
function canonicalSpecRef(specRef) {
|
|
7282
|
+
return specRef.trim().split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^(?:\.?\/+)+/, "");
|
|
7283
|
+
}
|
|
7284
|
+
function isRemoteSpecRef(specRef) {
|
|
7285
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(specRef);
|
|
7286
|
+
}
|
|
7180
7287
|
function specRefBaseName(specRef) {
|
|
7181
|
-
return specRef
|
|
7288
|
+
return canonicalSpecRef(specRef).split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
|
|
7289
|
+
}
|
|
7290
|
+
function withoutSpecExtension(value) {
|
|
7291
|
+
return value.replace(/\.(?:json|ya?ml)$/i, "");
|
|
7292
|
+
}
|
|
7293
|
+
function resolveSpecRef(specRef, candidates, anyFolder = true) {
|
|
7294
|
+
const key = canonicalSpecRef(specRef);
|
|
7295
|
+
const byId = /* @__PURE__ */ new Map();
|
|
7296
|
+
for (const candidate of candidates) {
|
|
7297
|
+
const id = candidate.sourcePath ?? candidate.filename;
|
|
7298
|
+
if (!byId.has(id)) byId.set(id, candidate);
|
|
7299
|
+
}
|
|
7300
|
+
const unique = [...byId.values()];
|
|
7301
|
+
const exact = unique.find((candidate) => candidate.sourcePath === key);
|
|
7302
|
+
if (exact) return { kind: "match", spec: exact };
|
|
7303
|
+
if (!anyFolder && key.includes("/")) return { kind: "none" };
|
|
7304
|
+
const name = specRefBaseName(key);
|
|
7305
|
+
const names = unique.map((candidate) => specRefBaseName(candidate.sourcePath ?? candidate.filename));
|
|
7306
|
+
for (const fits of [unique.filter((_, index) => names[index] === name), unique.filter((_, index) => withoutSpecExtension(names[index]) === name)]) {
|
|
7307
|
+
if (fits.length === 1) return { kind: "match", spec: fits[0] };
|
|
7308
|
+
if (fits.length > 1) return { kind: "ambiguous", candidates: fits };
|
|
7309
|
+
}
|
|
7310
|
+
return { kind: "none" };
|
|
7311
|
+
}
|
|
7312
|
+
function localSpecRef(openapi) {
|
|
7313
|
+
const specRef = openapi ? splitOpenApiRef(openapi)?.specRef : void 0;
|
|
7314
|
+
return specRef && !isRemoteSpecRef(specRef) ? specRef : void 0;
|
|
7315
|
+
}
|
|
7316
|
+
function specNamedByPage(openapi, specs) {
|
|
7317
|
+
const specRef = localSpecRef(openapi);
|
|
7318
|
+
const resolved = specRef ? resolveSpecRef(specRef, specs) : void 0;
|
|
7319
|
+
return resolved?.kind === "match" ? resolved.spec : void 0;
|
|
7320
|
+
}
|
|
7321
|
+
function namesSeveralSpecs(openapi, specs) {
|
|
7322
|
+
const specRef = localSpecRef(openapi);
|
|
7323
|
+
return Boolean(specRef) && resolveSpecRef(specRef, specs).kind === "ambiguous";
|
|
7182
7324
|
}
|
|
7183
7325
|
function withSpecRef(ref, specRef) {
|
|
7184
7326
|
return `${specRef} ${ref.operation}`;
|
|
@@ -7186,36 +7328,71 @@ function withSpecRef(ref, specRef) {
|
|
|
7186
7328
|
|
|
7187
7329
|
// src/spec-exclude.ts
|
|
7188
7330
|
import { parse as parseYaml4, stringify as stringifyYaml } from "yaml";
|
|
7189
|
-
function specRefMatches(ref, specPath,
|
|
7190
|
-
const prefix =
|
|
7331
|
+
function specRefMatches(ref, specPath, isDefault) {
|
|
7332
|
+
const prefix = splitOpenApiRef(ref)?.specRef;
|
|
7191
7333
|
if (!prefix) return isDefault;
|
|
7192
|
-
const wanted = prefix.
|
|
7334
|
+
const wanted = (isRemoteSpecRef(prefix) ? prefix.trim() : canonicalSpecRef(prefix)).toLowerCase();
|
|
7193
7335
|
const lowerPath = specPath.toLowerCase();
|
|
7194
|
-
|
|
7336
|
+
const stem = withoutSpecExtension(lowerPath);
|
|
7337
|
+
return lowerPath === wanted || lowerPath.endsWith(`/${wanted}`) || stem === wanted || stem.endsWith(`/${wanted}`);
|
|
7338
|
+
}
|
|
7339
|
+
function resolvedSources(ref, specs) {
|
|
7340
|
+
const specRef = splitOpenApiRef(ref)?.specRef;
|
|
7341
|
+
if (!specRef || isRemoteSpecRef(specRef)) return { unique: false, sources: [] };
|
|
7342
|
+
const resolved = resolveSpecRef(specRef, specs);
|
|
7343
|
+
if (resolved.kind === "match") return { unique: true, sources: [resolved.spec.sourcePath] };
|
|
7344
|
+
return { unique: false, sources: resolved.kind === "ambiguous" ? resolved.candidates.map((candidate) => candidate.sourcePath) : [] };
|
|
7345
|
+
}
|
|
7346
|
+
function refWithholdsFrom(ref, spec, specs) {
|
|
7347
|
+
const { unique, sources } = resolvedSources(ref, specs);
|
|
7348
|
+
return unique ? sources[0] === spec.sourcePath : specRefMatches(ref, spec.sourcePath, true) || sources.includes(spec.sourcePath);
|
|
7349
|
+
}
|
|
7350
|
+
function refKeepsIn(ref, spec, specs) {
|
|
7351
|
+
const specRef = splitOpenApiRef(ref)?.specRef;
|
|
7352
|
+
if (!specRef) return false;
|
|
7353
|
+
if (isRemoteSpecRef(specRef)) return specRefMatches(ref, spec.sourcePath, false);
|
|
7354
|
+
const { unique, sources } = resolvedSources(ref, specs);
|
|
7355
|
+
return unique && sources[0] === spec.sourcePath;
|
|
7195
7356
|
}
|
|
7196
7357
|
function operationKey(operation) {
|
|
7197
7358
|
const [first, ...rest] = operation.trim().split(/\s+/);
|
|
7198
7359
|
return `${first.toLowerCase()} ${rest.join(" ")}`;
|
|
7199
7360
|
}
|
|
7200
|
-
function keysFor(
|
|
7361
|
+
function keysFor(refs, names) {
|
|
7201
7362
|
return [...new Set(
|
|
7202
|
-
refs.filter(
|
|
7363
|
+
refs.filter(names).map((ref) => splitOpenApiRef(ref)?.operation).filter((op) => !!op).map(operationKey)
|
|
7203
7364
|
)];
|
|
7204
7365
|
}
|
|
7205
|
-
function withheldOperationKeys(spec, withheldRefs, keptRefs) {
|
|
7206
|
-
const kept = keysFor(spec,
|
|
7207
|
-
return { withheld: keysFor(spec,
|
|
7366
|
+
function withheldOperationKeys(spec, withheldRefs, keptRefs, specs = [spec]) {
|
|
7367
|
+
const kept = keysFor(keptRefs, (ref) => refKeepsIn(ref, spec, specs));
|
|
7368
|
+
return { withheld: keysFor(withheldRefs, (ref) => refWithholdsFrom(ref, spec, specs)).filter((key) => !kept.includes(key)), kept };
|
|
7369
|
+
}
|
|
7370
|
+
function withoutByteOrderMark(content) {
|
|
7371
|
+
return content.length >= 3 && content[0] === 239 && content[1] === 187 && content[2] === 191 ? content.subarray(3) : content;
|
|
7372
|
+
}
|
|
7373
|
+
function parseSpecDocument(text3) {
|
|
7374
|
+
const source = text3.replace(/^\uFEFF/, "");
|
|
7375
|
+
try {
|
|
7376
|
+
return { format: "json", document: JSON.parse(source) };
|
|
7377
|
+
} catch (jsonError) {
|
|
7378
|
+
try {
|
|
7379
|
+
return { format: "yaml", document: parseYaml4(source) };
|
|
7380
|
+
} catch (yamlError) {
|
|
7381
|
+
throw source.trimStart().startsWith("{") ? jsonError : yamlError;
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7208
7384
|
}
|
|
7209
7385
|
function markExcluded(content, operationKeys, keptKeys = []) {
|
|
7210
7386
|
const untouched = { content, excluded: [] };
|
|
7211
7387
|
if (operationKeys.length === 0) return untouched;
|
|
7212
7388
|
const text3 = content.toString("utf8");
|
|
7213
|
-
let
|
|
7389
|
+
let parsed;
|
|
7214
7390
|
try {
|
|
7215
|
-
|
|
7391
|
+
parsed = parseSpecDocument(text3);
|
|
7216
7392
|
} catch {
|
|
7217
7393
|
return untouched;
|
|
7218
7394
|
}
|
|
7395
|
+
const doc = parsed.document;
|
|
7219
7396
|
if (!doc || typeof doc !== "object") return untouched;
|
|
7220
7397
|
const excluded = [];
|
|
7221
7398
|
for (const key of operationKeys) {
|
|
@@ -7238,7 +7415,7 @@ function markExcluded(content, operationKeys, keptKeys = []) {
|
|
|
7238
7415
|
}
|
|
7239
7416
|
}
|
|
7240
7417
|
if (excluded.length === 0) return untouched;
|
|
7241
|
-
const out =
|
|
7418
|
+
const out = parsed.format === "json" ? `${JSON.stringify(doc, null, 2)}
|
|
7242
7419
|
` : stringifyYaml(doc);
|
|
7243
7420
|
return { content: Buffer.from(out), excluded };
|
|
7244
7421
|
}
|
|
@@ -7469,6 +7646,150 @@ function migrationImageAttribute(value) {
|
|
|
7469
7646
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/\{/g, "{").replace(/\}/g, "}").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
7470
7647
|
}
|
|
7471
7648
|
|
|
7649
|
+
// src/redirects.ts
|
|
7650
|
+
var LISTED = 10;
|
|
7651
|
+
function exactRoute(route) {
|
|
7652
|
+
return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route.replace(/\/+$/, "") || "/" : null;
|
|
7653
|
+
}
|
|
7654
|
+
function literalRoute(route) {
|
|
7655
|
+
return exactRoute(route)?.toLowerCase() ?? null;
|
|
7656
|
+
}
|
|
7657
|
+
function resolveDotSegments(route) {
|
|
7658
|
+
const segments = [];
|
|
7659
|
+
for (const segment of route.split("/").slice(1)) {
|
|
7660
|
+
const dots = segment.replace(/%2e/gi, ".");
|
|
7661
|
+
if (dots === "..") segments.pop();
|
|
7662
|
+
else if (dots !== ".") segments.push(segment);
|
|
7663
|
+
}
|
|
7664
|
+
return `/${segments.join("/")}`;
|
|
7665
|
+
}
|
|
7666
|
+
function destinationRoute(destination) {
|
|
7667
|
+
const route = destination.startsWith("//") ? null : exactRoute(destination.split(/[?#]/, 1)[0]);
|
|
7668
|
+
return route === null ? null : resolveDotSegments(route);
|
|
7669
|
+
}
|
|
7670
|
+
function pageRoutes(pages) {
|
|
7671
|
+
return pages.flatMap((page) => [`/${page.id}`, `/${page.id.replace(/\/index$/, "")}`]);
|
|
7672
|
+
}
|
|
7673
|
+
function servedRoutes(pages) {
|
|
7674
|
+
return new Set(pageRoutes(pages).map((route) => route.toLowerCase()));
|
|
7675
|
+
}
|
|
7676
|
+
function list(sources) {
|
|
7677
|
+
return `${sources.slice(0, LISTED).join(", ")}${sources.length > LISTED ? ", ..." : ""}`;
|
|
7678
|
+
}
|
|
7679
|
+
var ROUTE_PATTERN_OPERATORS = /[+?()[\]{}]/;
|
|
7680
|
+
function dropUnroutableRedirects(config, warnings) {
|
|
7681
|
+
if (!config.redirects?.length) return config;
|
|
7682
|
+
const unmatchable = [];
|
|
7683
|
+
const redirects = config.redirects.filter((redirect) => {
|
|
7684
|
+
const source = exactRoute(redirect.source);
|
|
7685
|
+
if (source && source === exactRoute(redirect.destination)) {
|
|
7686
|
+
warnings.push({ code: "unsupported-config", message: `Self-redirect from ${redirect.source} to ${redirect.destination} was omitted.` });
|
|
7687
|
+
return false;
|
|
7688
|
+
}
|
|
7689
|
+
if (!ROUTE_PATTERN_OPERATORS.test(redirect.source)) return true;
|
|
7690
|
+
unmatchable.push(redirect.source);
|
|
7691
|
+
return false;
|
|
7692
|
+
});
|
|
7693
|
+
if (unmatchable.length > 0) {
|
|
7694
|
+
warnings.push({
|
|
7695
|
+
code: "unsupported-config",
|
|
7696
|
+
message: `Skipped ${unmatchable.length} redirect${unmatchable.length === 1 ? "" : "s"} from ${list(unmatchable)}: ${unmatchable.length === 1 ? "that address has" : "those addresses have"} a character (+ ? ( ) [ ] { }) that Next.js reads as route syntax and cannot match literally, so visits to ${unmatchable.length === 1 ? "it" : "them"} will not redirect.`
|
|
7697
|
+
});
|
|
7698
|
+
}
|
|
7699
|
+
if (redirects.length === config.redirects.length) return config;
|
|
7700
|
+
const result = { ...config, redirects };
|
|
7701
|
+
if (redirects.length === 0) delete result.redirects;
|
|
7702
|
+
return result;
|
|
7703
|
+
}
|
|
7704
|
+
function redirectChains() {
|
|
7705
|
+
const kept = /* @__PURE__ */ new Map();
|
|
7706
|
+
const terminal = /* @__PURE__ */ new Set();
|
|
7707
|
+
const has = (source) => kept.has(source) || terminal.has(source);
|
|
7708
|
+
const add = (source, destination) => {
|
|
7709
|
+
if (destination === null) terminal.add(source);
|
|
7710
|
+
else kept.set(source, destination);
|
|
7711
|
+
};
|
|
7712
|
+
const closesLoop = (source, destination) => {
|
|
7713
|
+
let end = destination;
|
|
7714
|
+
for (let steps = 0; kept.has(end); steps++) {
|
|
7715
|
+
if (steps >= kept.size) return true;
|
|
7716
|
+
end = kept.get(end);
|
|
7717
|
+
}
|
|
7718
|
+
for (let step = destination; step !== end; ) {
|
|
7719
|
+
const next = kept.get(step);
|
|
7720
|
+
kept.set(step, end);
|
|
7721
|
+
step = next;
|
|
7722
|
+
}
|
|
7723
|
+
return end === source;
|
|
7724
|
+
};
|
|
7725
|
+
return { has, add, closesLoop };
|
|
7726
|
+
}
|
|
7727
|
+
function dropConflictingRedirects(config, pages, warnings) {
|
|
7728
|
+
if (!config.redirects?.length) return config;
|
|
7729
|
+
const served = servedRoutes(pages);
|
|
7730
|
+
const servedAsWritten = new Set(pageRoutes(pages));
|
|
7731
|
+
const hidden = [];
|
|
7732
|
+
const hiddenByCase = [];
|
|
7733
|
+
const looping = [];
|
|
7734
|
+
const loopingByCase = [];
|
|
7735
|
+
const folded = redirectChains();
|
|
7736
|
+
const asWritten = redirectChains();
|
|
7737
|
+
const redirects = config.redirects.filter((redirect) => {
|
|
7738
|
+
const source = literalRoute(redirect.source);
|
|
7739
|
+
if (!source) return true;
|
|
7740
|
+
const writtenSource = exactRoute(redirect.source);
|
|
7741
|
+
if (served.has(source)) {
|
|
7742
|
+
const hides = servedAsWritten.has(writtenSource) ? hidden : hiddenByCase;
|
|
7743
|
+
hides.push(redirect.source);
|
|
7744
|
+
return false;
|
|
7745
|
+
}
|
|
7746
|
+
const writtenDestination = destinationRoute(redirect.destination);
|
|
7747
|
+
const destination = writtenDestination?.toLowerCase() ?? null;
|
|
7748
|
+
const firesFolded = !folded.has(source);
|
|
7749
|
+
const firesAsWritten = !asWritten.has(writtenSource);
|
|
7750
|
+
const loopsFolded = firesFolded && destination !== null && folded.closesLoop(source, destination);
|
|
7751
|
+
const loopsAsWritten = firesAsWritten && writtenDestination !== null && asWritten.closesLoop(writtenSource, writtenDestination);
|
|
7752
|
+
if (loopsFolded || loopsAsWritten) {
|
|
7753
|
+
const loops = loopsAsWritten ? looping : loopingByCase;
|
|
7754
|
+
loops.push(`${redirect.source} -> ${redirect.destination}`);
|
|
7755
|
+
return false;
|
|
7756
|
+
}
|
|
7757
|
+
if (firesFolded) folded.add(source, destination);
|
|
7758
|
+
if (firesAsWritten) asWritten.add(writtenSource, writtenDestination);
|
|
7759
|
+
return true;
|
|
7760
|
+
});
|
|
7761
|
+
if (hidden.length > 0) {
|
|
7762
|
+
warnings.push({
|
|
7763
|
+
code: "unsupported-config",
|
|
7764
|
+
message: `Skipped ${hidden.length} redirect${hidden.length === 1 ? "" : "s"} from ${list(hidden)}: ${hidden.length === 1 ? "that URL is" : "those URLs are"} also ${hidden.length === 1 ? "a page" : "pages"} in this site, and a redirect would hide ${hidden.length === 1 ? "it" : "them"}. To redirect instead, delete the page and add the redirect to docs.json.`
|
|
7765
|
+
});
|
|
7766
|
+
}
|
|
7767
|
+
if (hiddenByCase.length > 0) {
|
|
7768
|
+
const one = hiddenByCase.length === 1;
|
|
7769
|
+
warnings.push({
|
|
7770
|
+
code: "unsupported-config",
|
|
7771
|
+
message: `Skipped ${hiddenByCase.length} redirect${one ? "" : "s"} from ${list(hiddenByCase)}: ${one ? "that URL differs" : "those URLs differ"} only in letter case from ${one ? "a page" : "pages"} in this site, so on a server that ignores letter case ${one ? "it" : "they"} would hide ${one ? "that page" : "those pages"}. If your host matches URLs case-sensitively, add ${one ? "it" : "them"} back to docs.json.`
|
|
7772
|
+
});
|
|
7773
|
+
}
|
|
7774
|
+
if (looping.length > 0) {
|
|
7775
|
+
warnings.push({
|
|
7776
|
+
code: "unsupported-config",
|
|
7777
|
+
message: `Skipped ${looping.length} redirect${looping.length === 1 ? "" : "s"} that would send visitors in a circle: ${list(looping)}. Check these redirects in the source docs.`
|
|
7778
|
+
});
|
|
7779
|
+
}
|
|
7780
|
+
if (loopingByCase.length > 0) {
|
|
7781
|
+
const one = loopingByCase.length === 1;
|
|
7782
|
+
warnings.push({
|
|
7783
|
+
code: "unsupported-config",
|
|
7784
|
+
message: `Skipped ${loopingByCase.length} redirect${one ? "" : "s"} that would send visitors in a circle on a server that ignores letter case, since the addresses involved differ only in letter case: ${list(loopingByCase)}. If your host matches URLs case-sensitively, add ${one ? "it" : "them"} back to docs.json.`
|
|
7785
|
+
});
|
|
7786
|
+
}
|
|
7787
|
+
if (redirects.length === config.redirects.length) return config;
|
|
7788
|
+
const result = { ...config, redirects };
|
|
7789
|
+
if (redirects.length === 0) delete result.redirects;
|
|
7790
|
+
return result;
|
|
7791
|
+
}
|
|
7792
|
+
|
|
7472
7793
|
// src/url.ts
|
|
7473
7794
|
var DEFAULT_MAX_PAGES = 1e3;
|
|
7474
7795
|
var DEFAULT_MAX_TOTAL_BYTES = 1e8;
|
|
@@ -8443,13 +8764,13 @@ function migrationHtmlArticle(body) {
|
|
|
8443
8764
|
const sourceId = select.attr("id");
|
|
8444
8765
|
if (sourceId) projection.attr("id", sourceId);
|
|
8445
8766
|
projection.append($("<p></p>").text(select.attr("aria-label")));
|
|
8446
|
-
const
|
|
8767
|
+
const list2 = $("<ul></ul>");
|
|
8447
8768
|
for (const url of urls) {
|
|
8448
8769
|
const item = $("<li></li>").append($("<a></a>").attr("href", url.href).text(url.label));
|
|
8449
8770
|
if (url.isSelected) item.append(" (selected)");
|
|
8450
|
-
|
|
8771
|
+
list2.append(item);
|
|
8451
8772
|
}
|
|
8452
|
-
projection.append(
|
|
8773
|
+
projection.append(list2);
|
|
8453
8774
|
select.replaceWith(projection);
|
|
8454
8775
|
});
|
|
8455
8776
|
return { $, root };
|
|
@@ -8691,8 +9012,8 @@ function readDocusaurusSidebar(document) {
|
|
|
8691
9012
|
const $ = load2(document.body);
|
|
8692
9013
|
const sidebar = $("ul.theme-doc-sidebar-menu").first();
|
|
8693
9014
|
if (sidebar.length === 0) return null;
|
|
8694
|
-
function readList(
|
|
8695
|
-
return $(
|
|
9015
|
+
function readList(list2) {
|
|
9016
|
+
return $(list2).children("li").toArray().flatMap((item) => {
|
|
8696
9017
|
const directLink = $(item).children("a.menu__link").first();
|
|
8697
9018
|
const collapsibleLink = $(item).children(".menu__list-item-collapsible").children("a.menu__link").first();
|
|
8698
9019
|
const link = directLink.length > 0 ? directLink : collapsibleLink;
|
|
@@ -9255,6 +9576,7 @@ async function migrateUrl(options) {
|
|
|
9255
9576
|
};
|
|
9256
9577
|
}
|
|
9257
9578
|
if (platform === "mintlify") docsConfig = addMintlifyDirectoryRedirects(docsConfig, pages);
|
|
9579
|
+
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), pages, warnings);
|
|
9258
9580
|
return {
|
|
9259
9581
|
sourceUrl: source.toString(),
|
|
9260
9582
|
sourceKind: "url",
|
|
@@ -10294,22 +10616,56 @@ function normalizedReferenceKey(value) {
|
|
|
10294
10616
|
function exactReferenceKey(value) {
|
|
10295
10617
|
return value.split(/[?#]/, 1)[0].replace(/^\/+/, "").replace(/\\/g, "/").replace(/\.(?:mdx?|rst|txt)$/i, "");
|
|
10296
10618
|
}
|
|
10297
|
-
function
|
|
10298
|
-
const
|
|
10299
|
-
|
|
10619
|
+
function containerSpec(node) {
|
|
10620
|
+
const value = node.openapi;
|
|
10621
|
+
if (value === void 0) return void 0;
|
|
10622
|
+
const source = value && typeof value === "object" && !Array.isArray(value) ? value.source : value;
|
|
10623
|
+
return typeof source === "string" && source.trim() && !splitOpenApiRef(source) ? source.trim() : null;
|
|
10624
|
+
}
|
|
10625
|
+
function navigationSpecContext(config, pageIdentity) {
|
|
10626
|
+
const context = { operations: [], pageOwners: /* @__PURE__ */ new Map(), pageIdentity };
|
|
10627
|
+
const visit = (node, owner, listed) => {
|
|
10300
10628
|
if (typeof node === "string") {
|
|
10301
|
-
if (splitOpenApiRef(node))
|
|
10302
|
-
|
|
10629
|
+
if (splitOpenApiRef(node)) context.operations.push({ ref: node, owner });
|
|
10630
|
+
else if (listed) {
|
|
10631
|
+
const page = pageIdentity(node);
|
|
10632
|
+
if (page !== null) context.pageOwners.set(page, (context.pageOwners.get(page) ?? /* @__PURE__ */ new Set()).add(owner));
|
|
10633
|
+
}
|
|
10634
|
+
} else if (Array.isArray(node)) node.forEach((child) => visit(child, owner, true));
|
|
10303
10635
|
else if (node && typeof node === "object") {
|
|
10304
10636
|
if (navigationGateReason(node)) return;
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10637
|
+
const own2 = containerSpec(node);
|
|
10638
|
+
Object.values(node).forEach((child) => visit(child, own2 === void 0 ? owner : own2, false));
|
|
10639
|
+
}
|
|
10640
|
+
};
|
|
10641
|
+
visit(config?.navigation, void 0, false);
|
|
10642
|
+
return context;
|
|
10643
|
+
}
|
|
10644
|
+
function keptOperationRefs(published, navigation, defaultSpec, specs) {
|
|
10645
|
+
const kept = [];
|
|
10646
|
+
const unowned = [];
|
|
10647
|
+
const source = (owner) => {
|
|
10648
|
+
if (!owner || isRemoteSpecRef(owner)) return owner;
|
|
10649
|
+
const resolved = resolveSpecRef(owner, specs);
|
|
10650
|
+
return resolved.kind === "match" ? resolved.spec.sourcePath ?? resolved.spec.filename : owner;
|
|
10651
|
+
};
|
|
10652
|
+
const add = (raw, owner) => {
|
|
10653
|
+
const ref = splitOpenApiRef(raw);
|
|
10654
|
+
if (!ref) return;
|
|
10655
|
+
if (ref.specRef) kept.push(raw);
|
|
10656
|
+
else if (owner) kept.push(withSpecRef(ref, owner));
|
|
10657
|
+
else unowned.push(ref.operation);
|
|
10658
|
+
};
|
|
10659
|
+
for (const { ref, file } of published) {
|
|
10660
|
+
const page = navigation.pageIdentity(file);
|
|
10661
|
+
const owners = new Set([...(page === null ? void 0 : navigation.pageOwners.get(page)) ?? [void 0]].map((owner) => source(owner === void 0 ? defaultSpec : owner)));
|
|
10662
|
+
add(ref, owners.size === 1 ? [...owners][0] : null);
|
|
10663
|
+
}
|
|
10664
|
+
for (const { ref, owner } of navigation.operations) add(ref, source(owner === void 0 ? defaultSpec : owner));
|
|
10665
|
+
return { kept, unowned };
|
|
10666
|
+
}
|
|
10667
|
+
function excludeWithheldOperations(spec, withheldRefs, keptRefs, specs) {
|
|
10668
|
+
const { withheld, kept } = withheldOperationKeys(spec, withheldRefs, keptRefs, specs);
|
|
10313
10669
|
return markExcluded(spec.content, withheld, kept);
|
|
10314
10670
|
}
|
|
10315
10671
|
var TOKEN_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\/`*!?#&]+/;
|
|
@@ -10402,7 +10758,7 @@ function readSpecFile(absolutePath, relativePath, warnings) {
|
|
|
10402
10758
|
});
|
|
10403
10759
|
return void 0;
|
|
10404
10760
|
}
|
|
10405
|
-
return readFileSync6(absolutePath);
|
|
10761
|
+
return withoutByteOrderMark(readFileSync6(absolutePath));
|
|
10406
10762
|
}
|
|
10407
10763
|
function specAssetPath(filename) {
|
|
10408
10764
|
return `${SPEC_DIRECTORY}/${filename}`;
|
|
@@ -10410,6 +10766,23 @@ function specAssetPath(filename) {
|
|
|
10410
10766
|
function specAsset(filename, content) {
|
|
10411
10767
|
return { path: specAssetPath(filename), content, projectRelative: true };
|
|
10412
10768
|
}
|
|
10769
|
+
function createSpecAssetNamer() {
|
|
10770
|
+
const nameBySource = /* @__PURE__ */ new Map();
|
|
10771
|
+
const sourceByName = /* @__PURE__ */ new Map();
|
|
10772
|
+
return ({ sourcePath, filename, tabLabel }) => {
|
|
10773
|
+
let name = nameBySource.get(sourcePath);
|
|
10774
|
+
if (name === void 0) {
|
|
10775
|
+
name = filename;
|
|
10776
|
+
if (sourceByName.has(name.toLowerCase())) {
|
|
10777
|
+
name = sourcePath.replace(/\//g, "-");
|
|
10778
|
+
while (sourceByName.has(name.toLowerCase())) name = `${(tabLabel ?? "default").replace(/\W+/g, "-")}-${name}`;
|
|
10779
|
+
}
|
|
10780
|
+
nameBySource.set(sourcePath, name);
|
|
10781
|
+
sourceByName.set(name.toLowerCase(), sourcePath);
|
|
10782
|
+
}
|
|
10783
|
+
return name;
|
|
10784
|
+
};
|
|
10785
|
+
}
|
|
10413
10786
|
function mintlifyTopLevelApiReferences(config) {
|
|
10414
10787
|
const api = config?.api && typeof config.api === "object" && !Array.isArray(config.api) ? config.api : null;
|
|
10415
10788
|
if (!api) return [];
|
|
@@ -10421,27 +10794,49 @@ function mintlifyTopLevelApiReferences(config) {
|
|
|
10421
10794
|
}
|
|
10422
10795
|
return references;
|
|
10423
10796
|
}
|
|
10424
|
-
function
|
|
10797
|
+
function specFileCandidates(files) {
|
|
10798
|
+
return files.filter((file) => /\.(?:json|ya?ml)$/i.test(file.relativePath)).map((file) => ({ sourcePath: file.relativePath, filename: specRefBaseName(file.relativePath) }));
|
|
10799
|
+
}
|
|
10800
|
+
function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, nameSpec, pages = []) {
|
|
10425
10801
|
if (!mintlifyConfig) return [];
|
|
10426
10802
|
const references = [
|
|
10427
10803
|
...mintlifyTopLevelApiReferences(mintlifyConfig),
|
|
10428
10804
|
...mintlifyNavigationApiReferences(mintlifyConfig)
|
|
10429
10805
|
];
|
|
10430
|
-
const
|
|
10431
|
-
const
|
|
10806
|
+
const known = new Set(references.map((reference) => canonicalSpecRef(reference.value)));
|
|
10807
|
+
const specFiles = specFileCandidates(files);
|
|
10808
|
+
const namedFiles = /* @__PURE__ */ new Map();
|
|
10432
10809
|
for (const page of pages) {
|
|
10433
10810
|
const specRef = page.openapi ? splitOpenApiRef(page.openapi)?.specRef : void 0;
|
|
10434
10811
|
if (!specRef || /^[a-z][a-z0-9+.-]*:/i.test(specRef)) continue;
|
|
10435
|
-
const
|
|
10436
|
-
|
|
10812
|
+
const named = namedFiles.get(specRef) ?? resolveSpecRef(specRef, specFiles, false);
|
|
10813
|
+
namedFiles.set(specRef, named);
|
|
10814
|
+
const key = named.kind === "match" ? named.spec.sourcePath : "";
|
|
10815
|
+
if (!key || known.has(key)) continue;
|
|
10437
10816
|
known.add(key);
|
|
10438
10817
|
references.push({ value: key, kind: "openapi", tabLabel: `OpenAPI: ${key}`, hidden: true, pageOnly: true });
|
|
10439
10818
|
}
|
|
10440
10819
|
const seen = /* @__PURE__ */ new Set();
|
|
10820
|
+
const remoteByKey = /* @__PURE__ */ new Map();
|
|
10821
|
+
const mountDirectory = (remote, reference) => {
|
|
10822
|
+
const directory = reference.directory?.replace(/^\/+|\/+$/g, "");
|
|
10823
|
+
if (!remote || !directory) return;
|
|
10824
|
+
const mount = remote.directories?.find((existing) => existing.directory === directory);
|
|
10825
|
+
if (!mount) {
|
|
10826
|
+
remote.directories = [...remote.directories ?? [], { directory, ...reference.operations ? { operations: reference.operations } : {} }];
|
|
10827
|
+
} else if (mount.operations && reference.operations) {
|
|
10828
|
+
mount.operations = [.../* @__PURE__ */ new Set([...mount.operations, ...reference.operations])];
|
|
10829
|
+
} else {
|
|
10830
|
+
delete mount.operations;
|
|
10831
|
+
}
|
|
10832
|
+
};
|
|
10441
10833
|
const specs = [];
|
|
10442
10834
|
for (const reference of references) {
|
|
10443
10835
|
const dedupeKey = `${reference.kind}:${reference.value}:${reference.tabLabel ?? ""}`;
|
|
10444
|
-
if (seen.has(dedupeKey))
|
|
10836
|
+
if (seen.has(dedupeKey)) {
|
|
10837
|
+
mountDirectory(remoteByKey.get(dedupeKey), reference);
|
|
10838
|
+
continue;
|
|
10839
|
+
}
|
|
10445
10840
|
seen.add(dedupeKey);
|
|
10446
10841
|
const tabSuffix = reference.tabLabel ? ` (tab "${reference.tabLabel}")` : "";
|
|
10447
10842
|
if (reference.kind === "asyncapi") {
|
|
@@ -10452,7 +10847,10 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
10452
10847
|
continue;
|
|
10453
10848
|
}
|
|
10454
10849
|
if (/^https:\/\//i.test(reference.value)) {
|
|
10455
|
-
|
|
10850
|
+
const remote = { url: reference.value, ...reference.tabLabel ? { tabLabel: reference.tabLabel } : {}, ...reference.parentTab ? { parentTab: reference.parentTab } : {}, ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {} };
|
|
10851
|
+
mountDirectory(remote, reference);
|
|
10852
|
+
remoteSpecs.push(remote);
|
|
10853
|
+
remoteByKey.set(dedupeKey, remote);
|
|
10456
10854
|
warnings.push({
|
|
10457
10855
|
code: "unsupported-config",
|
|
10458
10856
|
message: `The remote OpenAPI spec "${reference.value}"${tabSuffix} requires a network download before this import is complete.`,
|
|
@@ -10464,7 +10862,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
10464
10862
|
warnings.push({ code: "unsupported-config", message: `The OpenAPI spec "${reference.value}"${tabSuffix} uses insecure HTTP and was not imported.`, source: reference.value });
|
|
10465
10863
|
continue;
|
|
10466
10864
|
}
|
|
10467
|
-
const key = reference.value
|
|
10865
|
+
const key = canonicalSpecRef(reference.value);
|
|
10468
10866
|
const match = files.find((file) => file.relativePath === key);
|
|
10469
10867
|
if (!match) {
|
|
10470
10868
|
warnings.push({
|
|
@@ -10493,21 +10891,7 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs, p
|
|
|
10493
10891
|
directory: reference.directory
|
|
10494
10892
|
});
|
|
10495
10893
|
}
|
|
10496
|
-
const
|
|
10497
|
-
const filenameSources = /* @__PURE__ */ new Map();
|
|
10498
|
-
for (const spec of specs) {
|
|
10499
|
-
const groupKey = spec.tabLabel ?? "\0default";
|
|
10500
|
-
const owner = filenameOwners.get(spec.filename);
|
|
10501
|
-
if (owner !== void 0 && owner !== groupKey && filenameSources.get(spec.filename) !== spec.sourcePath) {
|
|
10502
|
-
let disambiguated = spec.sourcePath.replace(/\//g, "-");
|
|
10503
|
-
while (filenameOwners.has(disambiguated) && filenameOwners.get(disambiguated) !== groupKey) {
|
|
10504
|
-
disambiguated = `${groupKey.replace(/\W+/g, "-")}-${disambiguated}`;
|
|
10505
|
-
}
|
|
10506
|
-
spec.filename = disambiguated;
|
|
10507
|
-
}
|
|
10508
|
-
filenameOwners.set(spec.filename, groupKey);
|
|
10509
|
-
filenameSources.set(spec.filename, spec.sourcePath);
|
|
10510
|
-
}
|
|
10894
|
+
for (const spec of specs) spec.filename = nameSpec(spec);
|
|
10511
10895
|
return specs;
|
|
10512
10896
|
}
|
|
10513
10897
|
function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings, explainMissing = () => "no such file was found") {
|
|
@@ -10520,12 +10904,13 @@ function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings, explain
|
|
|
10520
10904
|
}
|
|
10521
10905
|
continue;
|
|
10522
10906
|
}
|
|
10523
|
-
const key = ref.specRef
|
|
10524
|
-
const
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
if (
|
|
10528
|
-
|
|
10907
|
+
const key = canonicalSpecRef(ref.specRef);
|
|
10908
|
+
const resolved = resolveSpecRef(ref.specRef, specs);
|
|
10909
|
+
if (resolved.kind === "match") {
|
|
10910
|
+
page.openapi = withSpecRef(ref, specAssetPath(resolved.spec.filename));
|
|
10911
|
+
} else if (resolved.kind === "ambiguous") {
|
|
10912
|
+
const candidates = resolved.candidates.map((spec) => spec.sourcePath ?? spec.filename).join(", ");
|
|
10913
|
+
warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the OpenAPI spec "${ref.specRef}", which fits more than one spec (${candidates}), so it was left as written and the page will not render an endpoint. Write the full path of the spec in the page's openapi frontmatter.`, source: page.source });
|
|
10529
10914
|
} else {
|
|
10530
10915
|
warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the OpenAPI spec "${ref.specRef}", which was not migrated (${explainMissing(key)}); the page will not render an endpoint.`, source: page.source });
|
|
10531
10916
|
}
|
|
@@ -10552,10 +10937,15 @@ function thallyOperationSlugSegments(path, method) {
|
|
|
10552
10937
|
function slugifyApiSpecId(value) {
|
|
10553
10938
|
return value.toLowerCase().replace(/[^a-z0-9/]+/g, "-").replace(/(^-|-$)+/g, "").replace(/\//g, "-") || value.toLowerCase();
|
|
10554
10939
|
}
|
|
10940
|
+
function apiTabSpecIds(tabs) {
|
|
10941
|
+
return tabs.filter((tab) => !tab.hidden && tab.api).map((tab, index) => ({ tab, specId: index === 0 ? "default" : slugifyApiSpecId(tab.tab) }));
|
|
10942
|
+
}
|
|
10943
|
+
var flagged = (value) => value === true || value === "true";
|
|
10944
|
+
var isWithheld = (node) => flagged(node["x-hidden"]) || flagged(node["x-excluded"]);
|
|
10555
10945
|
function parseOpenApiOperationsForLinking(content) {
|
|
10556
10946
|
let document;
|
|
10557
10947
|
try {
|
|
10558
|
-
document =
|
|
10948
|
+
document = parseSpecDocument(content.toString("utf8")).document;
|
|
10559
10949
|
} catch {
|
|
10560
10950
|
return [];
|
|
10561
10951
|
}
|
|
@@ -10564,10 +10954,13 @@ function parseOpenApiOperationsForLinking(content) {
|
|
|
10564
10954
|
const operations = [];
|
|
10565
10955
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
10566
10956
|
if (!pathItem || typeof pathItem !== "object") continue;
|
|
10957
|
+
const item = pathItem;
|
|
10958
|
+
if (isWithheld(item)) continue;
|
|
10567
10959
|
for (const method of OPENAPI_HTTP_METHODS) {
|
|
10568
|
-
const operation =
|
|
10960
|
+
const operation = item[method];
|
|
10569
10961
|
if (!operation || typeof operation !== "object") continue;
|
|
10570
10962
|
const op = operation;
|
|
10963
|
+
if (isWithheld(op)) continue;
|
|
10571
10964
|
const tags = Array.isArray(op.tags) ? op.tags.filter((tag) => typeof tag === "string") : [];
|
|
10572
10965
|
operations.push({
|
|
10573
10966
|
method,
|
|
@@ -10580,19 +10973,39 @@ function parseOpenApiOperationsForLinking(content) {
|
|
|
10580
10973
|
}
|
|
10581
10974
|
return operations;
|
|
10582
10975
|
}
|
|
10583
|
-
function
|
|
10976
|
+
function servedOperations(source, parsed) {
|
|
10977
|
+
let operations = parsed.get(source.filename);
|
|
10978
|
+
if (!operations) {
|
|
10979
|
+
operations = parseOpenApiOperationsForLinking(source.content);
|
|
10980
|
+
parsed.set(source.filename, operations);
|
|
10981
|
+
}
|
|
10982
|
+
if (!source.operations) return operations;
|
|
10983
|
+
const byKey = new Map(operations.map((operation) => [`${operation.method} ${operation.path}`, operation]));
|
|
10984
|
+
return source.operations.flatMap((reference) => {
|
|
10985
|
+
const [method, ...path] = reference.trim().split(/\s+/);
|
|
10986
|
+
const operation = byKey.get(`${method.toLowerCase()} ${path.join(" ")}`);
|
|
10987
|
+
return operation ? [operation] : [];
|
|
10988
|
+
});
|
|
10989
|
+
}
|
|
10990
|
+
var mountPrefix = (directory) => directory.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10991
|
+
function apiOperationLinkMap(sources, docsConfig, otherMounts = []) {
|
|
10584
10992
|
const map = /* @__PURE__ */ new Map();
|
|
10585
10993
|
const prefixLandings = /* @__PURE__ */ new Map();
|
|
10586
10994
|
const mintlifyPages = /* @__PURE__ */ new Map();
|
|
10587
10995
|
const duplicatePages = [];
|
|
10588
|
-
const
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
const
|
|
10593
|
-
|
|
10996
|
+
const parsedByFile = /* @__PURE__ */ new Map();
|
|
10997
|
+
const bound = apiTabSpecIds(docsConfig.tabs).flatMap(({ tab, specId }) => sources.filter((entry) => tab.api?.source === specAssetPath(entry.filename) && (!entry.tab || entry.tab === tab.tab)).map((source) => ({ source, specId })));
|
|
10998
|
+
const mounts = new Set([...bound.map(({ source }) => source.prefix ? mountPrefix(source.prefix) : ""), ...otherMounts.map(mountPrefix)].filter(Boolean));
|
|
10999
|
+
const underDeeperMount = (route, own2) => {
|
|
11000
|
+
const lower = route.toLowerCase();
|
|
11001
|
+
return [...mounts].some((mount) => mount.length > own2.length && (lower === mount || lower.startsWith(`${mount}/`)));
|
|
11002
|
+
};
|
|
11003
|
+
bound.forEach(({ source, specId }) => {
|
|
11004
|
+
if (!source.prefix) return;
|
|
11005
|
+
const base = source.prefix.replace(/^\/+|\/+$/g, "");
|
|
11006
|
+
const prefix = mountPrefix(source.prefix);
|
|
10594
11007
|
if (prefix) prefixLandings.set(prefix, specId === "default" ? "/api" : `/api/${specId}`);
|
|
10595
|
-
for (const operation of
|
|
11008
|
+
for (const operation of servedOperations(source, parsedByFile)) {
|
|
10596
11009
|
const thallyHref = `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
|
|
10597
11010
|
const leafCandidates = /* @__PURE__ */ new Set();
|
|
10598
11011
|
if (operation.operationId) {
|
|
@@ -10608,31 +11021,66 @@ function apiOperationLinkMap(sources, docsConfig) {
|
|
|
10608
11021
|
const tagSegment = mintlifyOperationSlugSegment(operation.tag ?? "default");
|
|
10609
11022
|
const mintlifyTag = mintlifyUrlSlug(operation.tag ?? "default");
|
|
10610
11023
|
const mintlifyLeaf = mintlifyUrlSlug(operation.summary?.trim() || operation.operationId?.trim() || `${operation.method} ${operation.path}`);
|
|
10611
|
-
const canonical2 = `${
|
|
10612
|
-
if (mintlifyTag && mintlifyLeaf) {
|
|
11024
|
+
const canonical2 = `${base}/${mintlifyTag}/${mintlifyLeaf}`.replace(/^\/+|\/+$/g, "");
|
|
11025
|
+
if (mintlifyTag && mintlifyLeaf && !underDeeperMount(canonical2, prefix)) {
|
|
10613
11026
|
if (!mintlifyPages.has(canonical2)) mintlifyPages.set(canonical2, thallyHref);
|
|
10614
11027
|
else if (mintlifyPages.get(canonical2) !== thallyHref && !duplicatePages.includes(canonical2)) duplicatePages.push(canonical2);
|
|
11028
|
+
if (!map.has(canonical2.toLowerCase())) map.set(canonical2.toLowerCase(), thallyHref);
|
|
11029
|
+
}
|
|
11030
|
+
const tagRoot = `${base}/${mintlifyTag}`.replace(/^\/+|\/+$/g, "");
|
|
11031
|
+
if (mintlifyTag && !underDeeperMount(tagRoot, prefix)) {
|
|
11032
|
+
if (!mintlifyPages.has(tagRoot)) mintlifyPages.set(tagRoot, thallyHref);
|
|
11033
|
+
if (!map.has(tagRoot.toLowerCase())) map.set(tagRoot.toLowerCase(), thallyHref);
|
|
10615
11034
|
}
|
|
10616
|
-
if (mintlifyTag && mintlifyLeaf && !map.has(canonical2.toLowerCase())) map.set(canonical2.toLowerCase(), thallyHref);
|
|
10617
|
-
const tagRoot = `${source.prefix}/${mintlifyTag}`.replace(/^\/+|\/+$/g, "");
|
|
10618
|
-
if (mintlifyTag && !mintlifyPages.has(tagRoot)) mintlifyPages.set(tagRoot, thallyHref);
|
|
10619
|
-
if (mintlifyTag && !map.has(tagRoot.toLowerCase())) map.set(tagRoot.toLowerCase(), thallyHref);
|
|
10620
11035
|
for (const leaf of leafCandidates) {
|
|
10621
11036
|
if (!leaf) continue;
|
|
10622
|
-
const tagged = `${
|
|
10623
|
-
const flat = `${
|
|
10624
|
-
if (!map.has(tagged)) map.set(tagged, thallyHref);
|
|
10625
|
-
if (!map.has(flat)) map.set(flat, thallyHref);
|
|
11037
|
+
const tagged = `${base}/${tagSegment}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
11038
|
+
const flat = `${base}/${leaf}`.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
11039
|
+
if (!map.has(tagged) && !underDeeperMount(tagged, prefix)) map.set(tagged, thallyHref);
|
|
11040
|
+
if (!map.has(flat) && !underDeeperMount(flat, prefix)) map.set(flat, thallyHref);
|
|
10626
11041
|
}
|
|
10627
11042
|
}
|
|
10628
11043
|
});
|
|
10629
11044
|
return { operationLinks: map, prefixLandings, mintlifyPages, duplicatePages };
|
|
10630
11045
|
}
|
|
10631
|
-
function
|
|
11046
|
+
function mintlifyEndpointRedirects(maps, pages, existing, warnings) {
|
|
11047
|
+
const { mintlifyPages, duplicatePages } = maps;
|
|
11048
|
+
if (duplicatePages.length > 0) {
|
|
11049
|
+
const listed = duplicatePages.slice(0, 10).map((url) => `/${url}`).join(", ");
|
|
11050
|
+
warnings.push({
|
|
11051
|
+
code: "unsupported-config",
|
|
11052
|
+
message: `${duplicatePages.length} Mintlify endpoint URL${duplicatePages.length === 1 ? " is" : "s are"} shared by more than one operation (same tag and summary), so only the first operation is redirected from each: ${listed}${duplicatePages.length > 10 ? ", ..." : ""}.`
|
|
11053
|
+
});
|
|
11054
|
+
}
|
|
11055
|
+
const taken = servedRoutes(pages);
|
|
11056
|
+
for (const redirect of existing) taken.add(literalRoute(redirect.source) ?? redirect.source);
|
|
11057
|
+
const candidates = [...mintlifyPages].filter(([source]) => !taken.has(literalRoute(`/${source}`) ?? `/${source}`));
|
|
11058
|
+
const redirects = candidates.slice(0, MAX_ENDPOINT_REDIRECTS).map(([source, destination]) => ({ source: `/${source}`, destination }));
|
|
11059
|
+
const capped = candidates.length - redirects.length;
|
|
11060
|
+
if (capped > 0) {
|
|
11061
|
+
warnings.push({
|
|
11062
|
+
code: "unsupported-config",
|
|
11063
|
+
message: `Created ${redirects.length} of ${candidates.length} Mintlify endpoint redirects; skipped ${capped} because a site is limited to ${MAX_ENDPOINT_REDIRECTS} generated endpoint redirects. Old links to skipped endpoint URLs will not redirect.`
|
|
11064
|
+
});
|
|
11065
|
+
}
|
|
11066
|
+
return redirects;
|
|
11067
|
+
}
|
|
11068
|
+
function linkApiMounts(sources, docsConfig, pages, warnings, reservedMounts = []) {
|
|
11069
|
+
const maps = apiOperationLinkMap(sources, docsConfig, reservedMounts);
|
|
11070
|
+
rewriteApiLinksInPages(pages, maps.operationLinks, maps.prefixLandings, warnings, { mounts: reservedMounts, redirects: docsConfig.redirects });
|
|
11071
|
+
return mintlifyEndpointRedirects(maps, pages, docsConfig.redirects ?? [], warnings);
|
|
11072
|
+
}
|
|
11073
|
+
function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings, answered = {}) {
|
|
10632
11074
|
if (operationLinks.size === 0 && prefixLandings.size === 0) return;
|
|
10633
11075
|
const unmatched = /* @__PURE__ */ new Set();
|
|
11076
|
+
const served = servedRoutes(pages);
|
|
11077
|
+
for (const { source } of answered.redirects ?? []) {
|
|
11078
|
+
const route = literalRoute(source);
|
|
11079
|
+
if (route) served.add(route);
|
|
11080
|
+
}
|
|
11081
|
+
const others = (answered.mounts ?? []).map(mountPrefix).filter(Boolean);
|
|
10634
11082
|
for (const page of pages) {
|
|
10635
|
-
page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, unmatched);
|
|
11083
|
+
page.body = rewriteApiOperationLinks(page.body, operationLinks, prefixLandings, served, unmatched, others);
|
|
10636
11084
|
}
|
|
10637
11085
|
if (unmatched.size === 0) return;
|
|
10638
11086
|
const sorted = [...unmatched].sort();
|
|
@@ -10641,7 +11089,7 @@ function rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings)
|
|
|
10641
11089
|
message: `${sorted.length} link(s) to API endpoints matched no endpoint in the OpenAPI spec, so they now point to the API section's overview page instead: ${sorted.slice(0, 10).join(", ")}${sorted.length > 10 ? `, and ${sorted.length - 10} more` : ""}. Update those links if you want them to reach a specific endpoint.`
|
|
10642
11090
|
});
|
|
10643
11091
|
}
|
|
10644
|
-
function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
|
|
11092
|
+
function rewriteApiOperationLinks(body, linkMap, prefixLandings, served, unmatched, otherMounts) {
|
|
10645
11093
|
if (linkMap.size === 0 && prefixLandings.size === 0) return body;
|
|
10646
11094
|
const orderedPrefixes = [...prefixLandings.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
10647
11095
|
let codeFence = null;
|
|
@@ -10655,8 +11103,12 @@ function rewriteApiOperationLinks(body, linkMap, prefixLandings, unmatched) {
|
|
|
10655
11103
|
if (codeFence) return line;
|
|
10656
11104
|
const rewriteTarget = (target2) => {
|
|
10657
11105
|
const suffixIndex = target2.search(/[?#]/);
|
|
10658
|
-
const
|
|
11106
|
+
const literal = suffixIndex >= 0 ? target2.slice(0, suffixIndex) : target2;
|
|
11107
|
+
if (served.has(literalRoute(literal) ?? literal)) return target2;
|
|
11108
|
+
const path = literal.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
10659
11109
|
const suffix = suffixIndex >= 0 ? target2.slice(suffixIndex) : "";
|
|
11110
|
+
const owner = [...prefixLandings.keys(), ...otherMounts].filter((mount) => path === mount || path.startsWith(`${mount}/`)).reduce((longest, mount) => longest === null || mount.length > longest.length ? mount : longest, null);
|
|
11111
|
+
if (owner !== null && !prefixLandings.has(owner)) return target2;
|
|
10660
11112
|
const mapped = linkMap.get(path);
|
|
10661
11113
|
if (mapped) return `${mapped}${suffix}`;
|
|
10662
11114
|
for (const [prefix, landing] of orderedPrefixes) {
|
|
@@ -11789,9 +12241,12 @@ function injectOpenApiSpecs(config, specs, warnings) {
|
|
|
11789
12241
|
const apiTab = spec.tabLabel ? tabs.find((tab) => tab.tab === spec.tabLabel) : tabs.find((tab) => tab.tab.toLowerCase().includes("api"));
|
|
11790
12242
|
if (apiTab) {
|
|
11791
12243
|
if (apiTab.api && warnings) {
|
|
12244
|
+
const source = specAssetPath(spec.filename);
|
|
12245
|
+
const pageBound = spec.pageReferenced && !tabs.some((tab) => tab.api?.source === source);
|
|
12246
|
+
if (pageBound) insertApiTab(tabs, { tab: `OpenAPI: ${spec.sourcePath ?? spec.filename}`, hidden: true, api: { source } });
|
|
11792
12247
|
warnings.push({
|
|
11793
12248
|
code: "unsupported-config",
|
|
11794
|
-
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "${
|
|
12249
|
+
message: `Tab "${apiTab.tab}" already uses another OpenAPI spec, and Thally supports one API spec per tab, so "${source}" ${pageBound ? "gets no endpoint list of its own; the pages that name it still show their endpoint. Put it in its own tab to list all its endpoints." : "was not added. Put it in its own tab to include it."}`
|
|
11795
12250
|
});
|
|
11796
12251
|
continue;
|
|
11797
12252
|
}
|
|
@@ -12028,6 +12483,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12028
12483
|
const sourceRefPages = [];
|
|
12029
12484
|
const sourceRefAssets = [];
|
|
12030
12485
|
const remoteApiSpecs = [];
|
|
12486
|
+
const localApiMounts = [];
|
|
12031
12487
|
const referencedAssetPaths = /* @__PURE__ */ new Map();
|
|
12032
12488
|
const addAssetReference = (assetPath, referencingPage) => {
|
|
12033
12489
|
const referrers = referencedAssetPaths.get(assetPath);
|
|
@@ -12049,6 +12505,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12049
12505
|
const gateByPath = /* @__PURE__ */ new Map();
|
|
12050
12506
|
const withheldSpecRefs = [];
|
|
12051
12507
|
const publishedSpecRefs = [];
|
|
12508
|
+
const publishedSpecPages = [];
|
|
12052
12509
|
const withheldPaths = /* @__PURE__ */ new Set();
|
|
12053
12510
|
const isDocFile = (file) => [".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase());
|
|
12054
12511
|
const isGateCandidate = (file) => isDocFile(file) && !file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
|
|
@@ -12085,6 +12542,7 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12085
12542
|
const verdict = classifyPageGate(file);
|
|
12086
12543
|
gateByPath.set(file.absolutePath, verdict);
|
|
12087
12544
|
if (verdict.openapi) (verdict.reason ? withheldSpecRefs : publishedSpecRefs).push(verdict.openapi);
|
|
12545
|
+
if (verdict.openapi && !verdict.reason) publishedSpecPages.push({ ref: verdict.openapi, file: file.relativePath });
|
|
12088
12546
|
if (verdict.reason) withheldPaths.add(file.absolutePath);
|
|
12089
12547
|
}
|
|
12090
12548
|
for (const file of mintignoreFilteredFiles) {
|
|
@@ -12188,6 +12646,23 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12188
12646
|
let discovered = files.length;
|
|
12189
12647
|
const localeConfig = docsConfig.i18n;
|
|
12190
12648
|
const defaultPageIds = new Set([...referenceMap.values()].filter((reference) => !reference.locale || reference.locale === localeConfig?.defaultLocale).map((reference) => reference.navigationId));
|
|
12649
|
+
const resolvePageIdentity = (path) => {
|
|
12650
|
+
const referenced = exactReferenceMap.get(exactReferenceKey(path)) ?? referenceMap.get(normalizedReferenceKey(path));
|
|
12651
|
+
let locale = referenced?.locale;
|
|
12652
|
+
let navigationId = referenced?.navigationId ?? pageIdFromReference(path, platform === "mintlify");
|
|
12653
|
+
if (!referenced && localeConfig) {
|
|
12654
|
+
for (const entry of localeConfig.locales) {
|
|
12655
|
+
const localized = mintlifyLocalizedReference(path, entry.code, defaultPageIds);
|
|
12656
|
+
if (localized === path) continue;
|
|
12657
|
+
locale = entry.code;
|
|
12658
|
+
navigationId = pageIdFromReference(localized, platform === "mintlify");
|
|
12659
|
+
break;
|
|
12660
|
+
}
|
|
12661
|
+
}
|
|
12662
|
+
if (!navigationId) return null;
|
|
12663
|
+
const isDefaultLocale = !locale || locale === localeConfig?.defaultLocale;
|
|
12664
|
+
return { locale, navigationId, id: isDefaultLocale ? navigationId : `${locale}/${navigationId}` };
|
|
12665
|
+
};
|
|
12191
12666
|
const routeAliases = [];
|
|
12192
12667
|
const pageFiles = [...files].sort((left, right) => {
|
|
12193
12668
|
const leftOrder = referenceOrder.get(normalizedReferenceKey(left.relativePath)) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -12283,7 +12758,6 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12283
12758
|
}
|
|
12284
12759
|
continue;
|
|
12285
12760
|
}
|
|
12286
|
-
const key = normalizedReferenceKey(file.relativePath);
|
|
12287
12761
|
let pageGate;
|
|
12288
12762
|
{
|
|
12289
12763
|
pageGate = gateByPath.get(file.absolutePath);
|
|
@@ -12333,25 +12807,13 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12333
12807
|
}
|
|
12334
12808
|
}
|
|
12335
12809
|
const isRestrictedPage = Boolean(pageGate?.reason);
|
|
12336
|
-
const
|
|
12337
|
-
|
|
12338
|
-
let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
|
|
12339
|
-
if (!referenced && localeConfig) {
|
|
12340
|
-
for (const entry of localeConfig.locales) {
|
|
12341
|
-
const localized = mintlifyLocalizedReference(file.relativePath, entry.code, defaultPageIds);
|
|
12342
|
-
if (localized === file.relativePath) continue;
|
|
12343
|
-
locale = entry.code;
|
|
12344
|
-
navigationId = pageIdFromReference(localized, platform === "mintlify");
|
|
12345
|
-
break;
|
|
12346
|
-
}
|
|
12347
|
-
}
|
|
12348
|
-
if (!navigationId) {
|
|
12810
|
+
const identity = resolvePageIdentity(file.relativePath);
|
|
12811
|
+
if (!identity) {
|
|
12349
12812
|
skipped++;
|
|
12350
12813
|
warnings.push({ code: "invalid-page", message: "Page path could not be normalized safely.", source: file.relativePath });
|
|
12351
12814
|
continue;
|
|
12352
12815
|
}
|
|
12353
|
-
const
|
|
12354
|
-
const id = isDefaultLocale ? navigationId : `${locale}/${navigationId}`;
|
|
12816
|
+
const { locale, navigationId, id } = identity;
|
|
12355
12817
|
let raw = inlineMdxSnippets(
|
|
12356
12818
|
mintlifyProjectRoot ? importUndeclaredSnippetComponents(readFileSync6(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, mintlifyProjectRoot, snippetExports, snippetAliases, inlineGate, warnings) : injectDocusaurusGlobalImports(readFileSync6(file.absolutePath, "utf8"), globalDocusaurusImports),
|
|
12357
12819
|
file.absolutePath,
|
|
@@ -12994,14 +13456,27 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
12994
13456
|
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.routeSegments.join("/") })),
|
|
12995
13457
|
docsConfig
|
|
12996
13458
|
);
|
|
12997
|
-
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings);
|
|
13459
|
+
rewriteApiLinksInPages(pages, operationLinks, prefixLandings, warnings, { redirects: docsConfig.redirects });
|
|
12998
13460
|
}
|
|
12999
13461
|
} else if (platform === "mintlify") {
|
|
13000
13462
|
const warnSharedSpec = (path, excluded = []) => {
|
|
13001
13463
|
warnings.push({ code: "gated-page", message: sharedSpecMessage(path, excluded), source: path });
|
|
13002
13464
|
};
|
|
13003
|
-
const
|
|
13004
|
-
const allSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs, pages.filter((page) => !restrictedPages.has(page)));
|
|
13465
|
+
const nameSpec = createSpecAssetNamer();
|
|
13466
|
+
const allSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs, nameSpec, pages.filter((page) => !restrictedPages.has(page)));
|
|
13467
|
+
const navigation = navigationSpecContext(mintlifyConfig, (path) => resolvePageIdentity(path)?.id ?? null);
|
|
13468
|
+
const defaultSources = /* @__PURE__ */ new Set([
|
|
13469
|
+
...allSpecs.filter((spec) => !spec.pageOnly && spec.tabLabel === void 0).map((spec) => spec.sourcePath),
|
|
13470
|
+
...remoteApiSpecs.filter((spec) => spec.tabLabel === void 0).map((spec) => spec.url)
|
|
13471
|
+
]);
|
|
13472
|
+
const keptOperations = (fallbackSource) => {
|
|
13473
|
+
const defaultSpec = defaultSources.size === 0 ? fallbackSource : defaultSources.size === 1 ? [...defaultSources][0] : void 0;
|
|
13474
|
+
const candidates = fallbackSource ? [{ sourcePath: fallbackSource, filename: basename3(fallbackSource) }, ...allSpecs] : allSpecs;
|
|
13475
|
+
const { kept, unowned } = keptOperationRefs(publishedSpecPages, navigation, defaultSpec, candidates);
|
|
13476
|
+
return { kept: kept.filter((ref) => !namesSeveralSpecs(ref, allSpecs)), unowned };
|
|
13477
|
+
};
|
|
13478
|
+
let { kept: keptSpecRefs, unowned: unownedOperations } = keptOperations();
|
|
13479
|
+
const withheldKeys = /* @__PURE__ */ new Set();
|
|
13005
13480
|
for (const remote of remoteApiSpecs) {
|
|
13006
13481
|
const { withheld, kept } = withheldOperationKeys({ sourcePath: remote.url, filename: remote.url }, withheldSpecRefs, keptSpecRefs);
|
|
13007
13482
|
if (withheld.length > 0) {
|
|
@@ -13011,9 +13486,10 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13011
13486
|
}
|
|
13012
13487
|
const withheldOperations = /* @__PURE__ */ new Map();
|
|
13013
13488
|
for (const spec of allSpecs) {
|
|
13014
|
-
const result = excludeWithheldOperations(spec, withheldSpecRefs, keptSpecRefs);
|
|
13489
|
+
const result = excludeWithheldOperations(spec, withheldSpecRefs, keptSpecRefs, allSpecs);
|
|
13015
13490
|
spec.content = result.content;
|
|
13016
13491
|
withheldOperations.set(spec, result.excluded);
|
|
13492
|
+
result.excluded.forEach((operation) => withheldKeys.add(operationKey(operation)));
|
|
13017
13493
|
}
|
|
13018
13494
|
const resolvedSpecs = allSpecs.filter((spec) => !spec.pageOnly);
|
|
13019
13495
|
const pageOnlySpecs = allSpecs.filter((spec) => spec.pageOnly);
|
|
@@ -13021,53 +13497,41 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13021
13497
|
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) {
|
|
13022
13498
|
assets.push(specAsset(spec.filename, spec.content));
|
|
13023
13499
|
}
|
|
13024
|
-
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath,
|
|
13500
|
+
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, true))) warnSharedSpec(spec.sourcePath, withheldOperations.get(spec));
|
|
13025
13501
|
}
|
|
13026
13502
|
let pageSpecs = resolvedSpecs;
|
|
13027
13503
|
if (resolvedSpecs.length > 0) {
|
|
13504
|
+
const namedByPages = new Set(pages.filter((page) => !restrictedPages.has(page)).map((page) => specNamedByPage(page.openapi, resolvedSpecs)));
|
|
13028
13505
|
for (const spec of resolvedSpecs) {
|
|
13029
|
-
spec.
|
|
13506
|
+
spec.pageReferenced = namedByPages.has(spec);
|
|
13507
|
+
spec.generatedNavigation = Boolean(spec.directory) && !publishedSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, true));
|
|
13030
13508
|
}
|
|
13031
13509
|
docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
|
|
13032
|
-
|
|
13033
|
-
resolvedSpecs.
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
});
|
|
13043
|
-
}
|
|
13044
|
-
const taken = /* @__PURE__ */ new Set([...pages.map((page) => page.id), ...(docsConfig.redirects ?? []).map((redirect) => redirect.source.replace(/^\//, ""))]);
|
|
13045
|
-
const candidates = [...mintlifyPages].filter(([source]) => !taken.has(source));
|
|
13046
|
-
const endpointRedirects = candidates.slice(0, MAX_ENDPOINT_REDIRECTS).map(([source, destination]) => ({ source: `/${source}`, destination }));
|
|
13047
|
-
const alreadyTaken = mintlifyPages.size - candidates.length;
|
|
13048
|
-
const capped = candidates.length - endpointRedirects.length;
|
|
13049
|
-
if (alreadyTaken + capped > 0) {
|
|
13050
|
-
const reasons = [
|
|
13051
|
-
alreadyTaken > 0 ? `${alreadyTaken} because the URL already has a page or redirect` : "",
|
|
13052
|
-
capped > 0 ? `${capped} because a site is limited to ${MAX_ENDPOINT_REDIRECTS} generated endpoint redirects` : ""
|
|
13053
|
-
].filter(Boolean).join(" and ");
|
|
13054
|
-
warnings.push({
|
|
13055
|
-
code: "unsupported-config",
|
|
13056
|
-
message: `Created ${endpointRedirects.length} of ${mintlifyPages.size} Mintlify endpoint redirects; skipped ${alreadyTaken + capped}: ${reasons}. Old links to skipped endpoint URLs will not redirect.`
|
|
13057
|
-
});
|
|
13510
|
+
if (remoteApiSpecs.length > 0) {
|
|
13511
|
+
localApiMounts.push(...resolvedSpecs.flatMap((spec) => spec.directory ? [{ filename: spec.filename, directory: spec.directory, ...spec.tabLabel ? { tab: spec.tabLabel } : {} }] : []));
|
|
13512
|
+
} else {
|
|
13513
|
+
const endpointRedirects = linkApiMounts(
|
|
13514
|
+
resolvedSpecs.map((spec) => ({ filename: spec.filename, content: spec.content, prefix: spec.directory, ...spec.tabLabel ? { tab: spec.tabLabel } : {} })),
|
|
13515
|
+
docsConfig,
|
|
13516
|
+
pages,
|
|
13517
|
+
warnings
|
|
13518
|
+
);
|
|
13519
|
+
if (endpointRedirects.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...endpointRedirects] };
|
|
13058
13520
|
}
|
|
13059
|
-
if (endpointRedirects.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...endpointRedirects] };
|
|
13060
13521
|
} else {
|
|
13061
13522
|
const fallback = findOpenApi(files);
|
|
13062
13523
|
const fallbackContent = fallback ? readSpecFile(fallback.absolutePath, fallback.relativePath, warnings) : void 0;
|
|
13063
13524
|
if (fallback && fallbackContent) {
|
|
13064
|
-
const
|
|
13065
|
-
const matches = (ref) => specRefMatches(ref, fallback.relativePath, filename, true);
|
|
13525
|
+
const matches = (ref) => specRefMatches(ref, fallback.relativePath, true);
|
|
13066
13526
|
if (withheldSpecRefs.some(matches) && !publishedSpecRefs.some(matches)) {
|
|
13067
|
-
quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${specAssetPath(
|
|
13527
|
+
quarantinedFiles.push({ path: `${QUARANTINE_DIRECTORY}/assets/${specAssetPath(basename3(fallback.relativePath))}`, content: fallbackContent });
|
|
13068
13528
|
withheldAssetCount++;
|
|
13069
13529
|
} else {
|
|
13070
|
-
const
|
|
13530
|
+
const filename = nameSpec({ sourcePath: fallback.relativePath, filename: basename3(fallback.relativePath) });
|
|
13531
|
+
const fallbackSpec = { sourcePath: fallback.relativePath, filename, content: fallbackContent };
|
|
13532
|
+
({ kept: keptSpecRefs, unowned: unownedOperations } = keptOperations(fallback.relativePath));
|
|
13533
|
+
const pruned = excludeWithheldOperations(fallbackSpec, withheldSpecRefs, keptSpecRefs, [fallbackSpec, ...allSpecs]);
|
|
13534
|
+
pruned.excluded.forEach((operation) => withheldKeys.add(operationKey(operation)));
|
|
13071
13535
|
if (!assets.some((asset) => asset.path === specAssetPath(filename))) {
|
|
13072
13536
|
assets.push(specAsset(filename, pruned.content));
|
|
13073
13537
|
}
|
|
@@ -13079,13 +13543,22 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13079
13543
|
}
|
|
13080
13544
|
for (const spec of pageOnlySpecs) {
|
|
13081
13545
|
if (!assets.some((asset) => asset.path === specAssetPath(spec.filename))) assets.push(specAsset(spec.filename, spec.content));
|
|
13082
|
-
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath,
|
|
13546
|
+
if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, true))) warnSharedSpec(spec.sourcePath, withheldOperations.get(spec));
|
|
13083
13547
|
}
|
|
13084
13548
|
if (pageOnlySpecs.length > 0) {
|
|
13085
13549
|
docsConfig = injectOpenApiSpecs(docsConfig, pageOnlySpecs, warnings);
|
|
13086
13550
|
pageSpecs = [...pageSpecs, ...pageOnlySpecs];
|
|
13087
13551
|
}
|
|
13552
|
+
const notKeptPublic = [...new Set(unownedOperations.filter((operation) => withheldKeys.has(operationKey(operation))))];
|
|
13553
|
+
if (notKeptPublic.length > 0) {
|
|
13554
|
+
warnings.push({
|
|
13555
|
+
code: "gated-page",
|
|
13556
|
+
message: `${notKeptPublic.length} operation(s) withheld from a published OpenAPI spec (${notKeptPublic.join(", ")}) are also named by a published page or navigation entry that does not say which spec it means, so that reference did not keep them public. Write the spec in its openapi value (for example "openapi.json GET /x") to publish the operation.`
|
|
13557
|
+
});
|
|
13558
|
+
}
|
|
13088
13559
|
const explainMissingSpec = (key) => {
|
|
13560
|
+
const fits = resolveSpecRef(key, specFileCandidates(files), false);
|
|
13561
|
+
if (fits.kind === "ambiguous") return `several spec files fit that name (${fits.candidates.map((spec) => spec.sourcePath).join(", ")}); write the full path in the page's openapi frontmatter`;
|
|
13089
13562
|
const sameName = scannedFiles.find((file) => file.relativePath.toLowerCase() === key.toLowerCase());
|
|
13090
13563
|
if (sameName && sameName.relativePath !== key) return `paths are case-sensitive; the file is at "${sameName.relativePath}"`;
|
|
13091
13564
|
if (sameName && mintignoreMatcher?.ignores(sameName.relativePath)) return "the file is excluded by .mintignore";
|
|
@@ -13099,8 +13572,9 @@ ${warnings.map((warning) => `- ${warning.message}`).join("\n")}`);
|
|
|
13099
13572
|
if (docsConfig.tabs.some((tab) => tab.api)) docsConfig = { ...docsConfig, api: { ...docsConfig.api, specLink: false } };
|
|
13100
13573
|
}
|
|
13101
13574
|
if (platform === "mintlify") {
|
|
13102
|
-
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source));
|
|
13103
|
-
const
|
|
13575
|
+
const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source.toLowerCase()));
|
|
13576
|
+
const served = servedRoutes(pages);
|
|
13577
|
+
const aliases = routeAliases.filter((redirect) => !sources.has(redirect.source.toLowerCase()) && !served.has(literalRoute(redirect.source) ?? redirect.source));
|
|
13104
13578
|
if (aliases.length > 0) docsConfig = { ...docsConfig, redirects: [...docsConfig.redirects ?? [], ...aliases] };
|
|
13105
13579
|
docsConfig = addMintlifyHomepageRedirects(addMintlifyDirectoryRedirects(docsConfig, pages), pages);
|
|
13106
13580
|
const reachablePaths = /* @__PURE__ */ new Set([
|
|
@@ -13314,26 +13788,7 @@ ${body}`;
|
|
|
13314
13788
|
}
|
|
13315
13789
|
}
|
|
13316
13790
|
if (docsConfig.redirects) {
|
|
13317
|
-
docsConfig =
|
|
13318
|
-
...docsConfig,
|
|
13319
|
-
redirects: docsConfig.redirects.filter((redirect) => {
|
|
13320
|
-
const literalRoute = (route) => route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? route.replace(/\/+$/, "") || "/" : null;
|
|
13321
|
-
const sourceRoute = literalRoute(redirect.source);
|
|
13322
|
-
if (sourceRoute && sourceRoute === literalRoute(redirect.destination)) {
|
|
13323
|
-
warnings.push({
|
|
13324
|
-
code: "unsupported-config",
|
|
13325
|
-
message: `Self-redirect from ${redirect.source} to ${redirect.destination} was omitted.`
|
|
13326
|
-
});
|
|
13327
|
-
return false;
|
|
13328
|
-
}
|
|
13329
|
-
if (!/[+?()[\]{}]/.test(redirect.source)) return true;
|
|
13330
|
-
warnings.push({
|
|
13331
|
-
code: "unsupported-config",
|
|
13332
|
-
message: `Redirect from ${redirect.source} contains route-pattern syntax Next.js cannot express literally and was omitted.`
|
|
13333
|
-
});
|
|
13334
|
-
return false;
|
|
13335
|
-
})
|
|
13336
|
-
};
|
|
13791
|
+
docsConfig = dropConflictingRedirects(dropUnroutableRedirects(docsConfig, warnings), [...pages, ...sourceRefPages], warnings);
|
|
13337
13792
|
}
|
|
13338
13793
|
if (pages.length === 0) {
|
|
13339
13794
|
const reasons = warnings.map((warning) => warning.message);
|
|
@@ -13443,10 +13898,10 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13443
13898
|
const sourceFavicon = mintlifyConfig?.favicon ?? fernRawConfig?.favicon ?? docusaurusSettings?.favicon;
|
|
13444
13899
|
const logo = brandVariants(sourceLogo);
|
|
13445
13900
|
const favicon = brandVariants(sourceFavicon);
|
|
13446
|
-
const logoLight = publicBrandPath(logo.light);
|
|
13447
13901
|
const logoDark = publicBrandPath(logo.dark);
|
|
13448
|
-
const
|
|
13902
|
+
const logoLight = publicBrandPath(logo.light) ?? logoDark;
|
|
13449
13903
|
const faviconDark = publicBrandPath(favicon.dark);
|
|
13904
|
+
const faviconLight = publicBrandPath(favicon.light) ?? faviconDark;
|
|
13450
13905
|
if (logoLight) docsConfig = {
|
|
13451
13906
|
...docsConfig,
|
|
13452
13907
|
navbar: {
|
|
@@ -13475,15 +13930,30 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13475
13930
|
const decoration = background.decoration;
|
|
13476
13931
|
const image = publicBrandPath(background.image);
|
|
13477
13932
|
const imageDark = publicBrandPath(background.imageDark);
|
|
13478
|
-
|
|
13479
|
-
...docsConfig,
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
...image ? { image } : {},
|
|
13484
|
-
...imageDark ? { imageDark } : {}
|
|
13485
|
-
}
|
|
13933
|
+
const mapped = {
|
|
13934
|
+
...docsConfig.background,
|
|
13935
|
+
...decoration === "none" || decoration === "grid" || decoration === "gradient" ? { decoration } : {},
|
|
13936
|
+
...image ? { image } : {},
|
|
13937
|
+
...imageDark ? { imageDark } : {}
|
|
13486
13938
|
};
|
|
13939
|
+
if (Object.keys(mapped).length > 0) docsConfig = { ...docsConfig, background: mapped };
|
|
13940
|
+
const canvas = background.color && typeof background.color === "object" ? background.color : {};
|
|
13941
|
+
for (const mode of ["light", "dark"]) {
|
|
13942
|
+
if (canvas[mode] === void 0) continue;
|
|
13943
|
+
const hex = typeof canvas[mode] === "string" ? cssColorToHex(canvas[mode]) : void 0;
|
|
13944
|
+
if (!hex) {
|
|
13945
|
+
warnings.push({
|
|
13946
|
+
code: "unsupported-config",
|
|
13947
|
+
message: `background.color.${mode} ${JSON.stringify(canvas[mode])?.slice(0, 40)} is not a hex or rgb() colour (for example #121317) and was dropped, so that mode keeps Thally's default canvas. Use a hex value in docs.json to set it.`,
|
|
13948
|
+
source: "docs.json"
|
|
13949
|
+
});
|
|
13950
|
+
continue;
|
|
13951
|
+
}
|
|
13952
|
+
docsConfig = {
|
|
13953
|
+
...docsConfig,
|
|
13954
|
+
colors: { ...docsConfig.colors, [mode]: { ...docsConfig.colors?.[mode], background: hex } }
|
|
13955
|
+
};
|
|
13956
|
+
}
|
|
13487
13957
|
}
|
|
13488
13958
|
}
|
|
13489
13959
|
if (platform === "docusaurus") {
|
|
@@ -13493,13 +13963,23 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13493
13963
|
...docsConfig,
|
|
13494
13964
|
favicon: { light: faviconLight, ...faviconDark ? { dark: faviconDark } : {} }
|
|
13495
13965
|
};
|
|
13496
|
-
const
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
];
|
|
13966
|
+
const notImported = (variants) => [variants.light, variants.dark].filter((value) => typeof value === "string" && !publicBrandPath(value));
|
|
13967
|
+
const missingLogo = notImported(logo);
|
|
13968
|
+
const missingFavicon = notImported(favicon);
|
|
13969
|
+
const missingBrand = [...missingLogo, ...missingFavicon];
|
|
13970
|
+
const substituted = [
|
|
13971
|
+
...missingLogo.length > 0 && logoLight ? [`logo (${logoLight})`] : [],
|
|
13972
|
+
...missingFavicon.length > 0 && faviconLight ? [`favicon (${faviconLight})`] : []
|
|
13973
|
+
].map((asset) => ` The imported ${asset} is used for both light and dark mode.`).join("");
|
|
13500
13974
|
if (missingBrand.length > 0) warnings.push({
|
|
13501
13975
|
code: "unsupported-config",
|
|
13502
|
-
message: `Brand asset(s) were referenced but not imported: ${[...new Set(missingBrand)].join(", ")}. Add these files under public/ or update docs.json
|
|
13976
|
+
message: `Brand asset(s) were referenced but not imported: ${[...new Set(missingBrand)].join(", ")}. Add these files under public/ or update docs.json.${substituted}`
|
|
13977
|
+
});
|
|
13978
|
+
const logoHref = sourceLogo && typeof sourceLogo === "object" ? sourceLogo.href : void 0;
|
|
13979
|
+
if (typeof logoHref === "string" && logoHref.trim() && logoHref.trim() !== "/") warnings.push({
|
|
13980
|
+
code: "unsupported-config",
|
|
13981
|
+
message: `logo.href ${JSON.stringify(logoHref.trim().slice(0, 120))} was dropped: the header logo always links to the docs home page. Add it as a navbar link in docs.json if readers need it.`,
|
|
13982
|
+
source: mintlifyConfig ? "docs.json" : "docs.yml"
|
|
13503
13983
|
});
|
|
13504
13984
|
if (platform === "mintlify") {
|
|
13505
13985
|
docsConfig = { ...docsConfig, markdown: { enabled: true } };
|
|
@@ -13532,7 +14012,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13532
14012
|
message: `Fern global theme ${JSON.stringify(fernRawConfig["global-theme"])} could not be fetched during local migration; local overrides and Thally default styling tokens were applied.`,
|
|
13533
14013
|
source: "global-theme"
|
|
13534
14014
|
});
|
|
13535
|
-
if (fernRawConfig?.logo && !fernRawConfig["global-theme"] && !logoLight && !logo.light) warnings.push({
|
|
14015
|
+
if (fernRawConfig?.logo && !fernRawConfig["global-theme"] && !logoLight && !logo.light && !logo.dark) warnings.push({
|
|
13536
14016
|
code: "unsupported-config",
|
|
13537
14017
|
message: "Fern supplied a logo through its global theme without a local asset; add a logo path to docs.json after import."
|
|
13538
14018
|
});
|
|
@@ -13546,6 +14026,7 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
13546
14026
|
assets,
|
|
13547
14027
|
...importedSourceRefs.length > 0 ? { sourceRefs: importedSourceRefs } : {},
|
|
13548
14028
|
...remoteApiSpecs.length > 0 ? { remoteApiSpecs } : {},
|
|
14029
|
+
...remoteApiSpecs.length > 0 && localApiMounts.length > 0 ? { localApiMounts } : {},
|
|
13549
14030
|
...componentMigrator ? { componentFiles: componentMigrator.files() } : {},
|
|
13550
14031
|
...quarantinedFiles.length > 0 ? { quarantinedFiles } : {},
|
|
13551
14032
|
...droppedGatedFiles.length > 0 ? { droppedGatedPages: droppedGatedFiles.length } : {},
|
|
@@ -15314,7 +15795,6 @@ import { createHash as createHash4 } from "crypto";
|
|
|
15314
15795
|
import { lookup } from "dns/promises";
|
|
15315
15796
|
import { request } from "https";
|
|
15316
15797
|
import { isIP } from "net";
|
|
15317
|
-
import { parse as parseYaml7 } from "yaml";
|
|
15318
15798
|
var MAX_SPEC_BYTES2 = 25e6;
|
|
15319
15799
|
var MAX_REDIRECTS = 3;
|
|
15320
15800
|
function isPublicAddress(address, family) {
|
|
@@ -15408,9 +15888,7 @@ async function downloadSpec(value, fetcher) {
|
|
|
15408
15888
|
}
|
|
15409
15889
|
throw new Error("Remote OpenAPI spec redirected too many times.");
|
|
15410
15890
|
}
|
|
15411
|
-
function parseOpenApi(
|
|
15412
|
-
const text3 = content.toString("utf8");
|
|
15413
|
-
const parsed = extension === "yaml" || !text3.trimStart().startsWith("{") ? parseYaml7(text3) : JSON.parse(text3);
|
|
15891
|
+
function parseOpenApi(parsed) {
|
|
15414
15892
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
|
|
15415
15893
|
const document = parsed;
|
|
15416
15894
|
if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
|
|
@@ -15423,10 +15901,11 @@ function parseOpenApi(content, extension) {
|
|
|
15423
15901
|
if (paths.length > 5e3) throw new Error("Remote OpenAPI spec has too many paths to import.");
|
|
15424
15902
|
const operations = [];
|
|
15425
15903
|
for (const [path, value] of paths) {
|
|
15426
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
15904
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || isWithheld(value)) continue;
|
|
15427
15905
|
for (const [method, raw] of Object.entries(value)) {
|
|
15428
15906
|
if (!/^(get|post|put|patch|delete|head|options|trace)$/i.test(method) || !raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
15429
15907
|
const operation = raw;
|
|
15908
|
+
if (isWithheld(operation)) continue;
|
|
15430
15909
|
const tags = Array.isArray(operation.tags) ? operation.tags : [];
|
|
15431
15910
|
operations.push({
|
|
15432
15911
|
method: method.toLowerCase(),
|
|
@@ -15441,9 +15920,8 @@ function parseOpenApi(content, extension) {
|
|
|
15441
15920
|
function slug(value) {
|
|
15442
15921
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
15443
15922
|
}
|
|
15444
|
-
function operationTarget(operation) {
|
|
15445
|
-
|
|
15446
|
-
return `/api/default/${[...path, operation.method].join("/")}`;
|
|
15923
|
+
function operationTarget(specId, operation) {
|
|
15924
|
+
return `/api/${specId}/${thallyOperationSlugSegments(operation.path, operation.method).join("/")}`;
|
|
15447
15925
|
}
|
|
15448
15926
|
async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
15449
15927
|
if (!bundle.remoteApiSpecs?.length) return bundle;
|
|
@@ -15453,28 +15931,32 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15453
15931
|
const warnings = [...bundle.warnings];
|
|
15454
15932
|
const pages = bundle.pages.map((page) => ({ ...page }));
|
|
15455
15933
|
const authoredLinks = /* @__PURE__ */ new Set();
|
|
15934
|
+
const served = servedRoutes(pages);
|
|
15935
|
+
const endpointUrls = /* @__PURE__ */ new Map();
|
|
15456
15936
|
for (const page of bundle.pages) {
|
|
15457
15937
|
for (const match of page.body.matchAll(/\/[a-z0-9_/-]+/gi)) {
|
|
15458
15938
|
const segments = match[0].split("/").filter(Boolean);
|
|
15459
15939
|
if (segments.length < 3) continue;
|
|
15460
15940
|
const section = segments[0].toLowerCase();
|
|
15461
|
-
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0]);
|
|
15941
|
+
if (section.includes("api") || section.includes("reference")) authoredLinks.add(match[0].replace(/\/+$/, ""));
|
|
15462
15942
|
}
|
|
15463
15943
|
}
|
|
15944
|
+
const bound = [];
|
|
15464
15945
|
for (const reference of bundle.remoteApiSpecs) {
|
|
15465
15946
|
for (let index = warnings.length - 1; index >= 0; index--) {
|
|
15466
15947
|
if (warnings[index].source === reference.url && warnings[index].message.includes("requires a network download")) warnings.splice(index, 1);
|
|
15467
15948
|
}
|
|
15468
15949
|
try {
|
|
15469
|
-
const
|
|
15470
|
-
const extension = /\.ya?ml$/i.test(url.pathname) ? "yaml" : "json";
|
|
15471
|
-
const downloaded = await downloadSpec(reference.url, fetcher);
|
|
15950
|
+
const downloaded = withoutByteOrderMark(await downloadSpec(reference.url, fetcher));
|
|
15472
15951
|
const pruned = markExcluded(downloaded, reference.withheldOperations ?? [], reference.keptOperations);
|
|
15473
15952
|
const body = pruned.content;
|
|
15474
|
-
if (reference.withheldOperations?.length
|
|
15475
|
-
|
|
15953
|
+
if (reference.withheldOperations?.length && !warnings.some((warning) => warning.code === "gated-page" && warning.source === reference.url)) {
|
|
15954
|
+
warnings.push({ code: "gated-page", message: sharedSpecMessage(reference.url, pruned.excluded), source: reference.url });
|
|
15955
|
+
}
|
|
15956
|
+
const { format: extension, document } = parseSpecDocument(body.toString("utf8"));
|
|
15957
|
+
const operations = parseOpenApi(document);
|
|
15476
15958
|
const filename = `openapi-${createHash4("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
|
|
15477
|
-
assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
15959
|
+
if (!assets.some((asset) => asset.path === `openapi/${filename}`)) assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
|
|
15478
15960
|
for (const page of pages) {
|
|
15479
15961
|
const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
|
|
15480
15962
|
if (ref?.specRef === reference.url) page.openapi = withSpecRef(ref, `openapi/${filename}`);
|
|
@@ -15482,21 +15964,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15482
15964
|
const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
|
|
15483
15965
|
if (tab) tab.api = { source: `openapi/${filename}` };
|
|
15484
15966
|
else insertApiTab(tabs, { tab: reference.tabLabel ?? "API Reference", ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {}, api: { source: `openapi/${filename}` } }, reference.parentTab);
|
|
15485
|
-
|
|
15486
|
-
for (const operation of operations) {
|
|
15487
|
-
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
15488
|
-
if (routes.has(key)) routes.set(key, null);
|
|
15489
|
-
else routes.set(key, operationTarget(operation));
|
|
15490
|
-
}
|
|
15491
|
-
const existingSources = new Set(redirects.map((redirect) => redirect.source));
|
|
15492
|
-
for (const source of authoredLinks) {
|
|
15493
|
-
const parts = source.split("/").filter(Boolean);
|
|
15494
|
-
const key = parts.slice(-2).join("/");
|
|
15495
|
-
const destination = routes.get(key);
|
|
15496
|
-
if (!destination || existingSources.has(source)) continue;
|
|
15497
|
-
redirects.push({ source, destination, permanent: false });
|
|
15498
|
-
existingSources.add(source);
|
|
15499
|
-
}
|
|
15967
|
+
bound.push({ reference, filename, content: body, operations, tabName: tab?.tab ?? reference.tabLabel ?? "API Reference" });
|
|
15500
15968
|
} catch (error) {
|
|
15501
15969
|
warnings.push({
|
|
15502
15970
|
code: "fetch-failed",
|
|
@@ -15505,11 +15973,59 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
|
|
|
15505
15973
|
});
|
|
15506
15974
|
}
|
|
15507
15975
|
}
|
|
15976
|
+
const directories = [
|
|
15977
|
+
...(bundle.localApiMounts ?? []).map(({ directory }) => directory),
|
|
15978
|
+
...bundle.remoteApiSpecs.flatMap((reference) => (reference.directories ?? []).map(({ directory }) => directory))
|
|
15979
|
+
];
|
|
15980
|
+
const endpointRedirects = linkApiMounts(
|
|
15981
|
+
[
|
|
15982
|
+
...(bundle.localApiMounts ?? []).map(({ filename, directory, tab }) => ({
|
|
15983
|
+
filename,
|
|
15984
|
+
content: Buffer.from(assets.find((asset) => asset.path === `openapi/${filename}`)?.content ?? []),
|
|
15985
|
+
prefix: directory,
|
|
15986
|
+
...tab ? { tab } : {}
|
|
15987
|
+
})),
|
|
15988
|
+
...bound.flatMap(({ reference, filename, content, tabName }) => (reference.directories ?? []).map(({ directory, operations: listed }) => ({ filename, content, prefix: directory, tab: tabName, ...listed ? { operations: listed } : {} })))
|
|
15989
|
+
],
|
|
15990
|
+
{ ...bundle.docsConfig, tabs, redirects },
|
|
15991
|
+
pages,
|
|
15992
|
+
warnings,
|
|
15993
|
+
directories
|
|
15994
|
+
);
|
|
15995
|
+
for (const redirect of endpointRedirects) {
|
|
15996
|
+
redirects.push(redirect);
|
|
15997
|
+
endpointUrls.set(literalRoute(redirect.source) ?? redirect.source, redirect.destination);
|
|
15998
|
+
}
|
|
15999
|
+
const existingSources = new Set(redirects.map((redirect) => literalRoute(redirect.source) ?? redirect.source.toLowerCase()));
|
|
16000
|
+
const mounts = directories.map(mountPrefix).filter(Boolean);
|
|
16001
|
+
const underMount = (source) => mounts.some((prefix) => source.toLowerCase() === `/${prefix}` || source.toLowerCase().startsWith(`/${prefix}/`));
|
|
16002
|
+
const guesses = /* @__PURE__ */ new Map();
|
|
16003
|
+
const specIds = apiTabSpecIds(tabs);
|
|
16004
|
+
for (const { reference, filename, operations, tabName } of bound) {
|
|
16005
|
+
const specId = specIds.find(({ tab }) => tab.tab === tabName && tab.api?.source === `openapi/${filename}`)?.specId;
|
|
16006
|
+
const listed = reference.directories?.length && reference.directories.every((directory) => directory.operations) ? new Set(reference.directories.flatMap((directory) => directory.operations ?? []).map((name) => name.trim().split(/\s+/)).map(([method, ...path]) => `${method.toLowerCase()} ${path.join(" ")}`)) : null;
|
|
16007
|
+
for (const operation of operations) {
|
|
16008
|
+
if (listed && !listed.has(`${operation.method} ${operation.path}`)) continue;
|
|
16009
|
+
const key = `${slug(operation.tag)}/${slug(operation.summary)}`;
|
|
16010
|
+
guesses.set(key, guesses.has(key) || !specId ? null : operationTarget(specId, operation));
|
|
16011
|
+
}
|
|
16012
|
+
}
|
|
16013
|
+
for (const source of authoredLinks) {
|
|
16014
|
+
const destination = guesses.get(source.split("/").filter(Boolean).slice(-2).join("/"));
|
|
16015
|
+
if (!destination || existingSources.has(literalRoute(source) ?? source.toLowerCase()) || served.has(literalRoute(source) ?? source) || underMount(source)) continue;
|
|
16016
|
+
redirects.push({ source, destination, permanent: false });
|
|
16017
|
+
existingSources.add(literalRoute(source) ?? source.toLowerCase());
|
|
16018
|
+
}
|
|
16019
|
+
const direct = redirects.map((redirect) => {
|
|
16020
|
+
const endpoint = endpointUrls.get(literalRoute(redirect.destination) ?? "");
|
|
16021
|
+
return endpoint ? { ...redirect, destination: endpoint } : redirect;
|
|
16022
|
+
});
|
|
15508
16023
|
return {
|
|
15509
16024
|
...bundle,
|
|
15510
16025
|
pages,
|
|
15511
16026
|
assets,
|
|
15512
|
-
|
|
16027
|
+
// Redirects added above meet the same checks as any other: none may fail the build, hide a page that exists, or loop.
|
|
16028
|
+
docsConfig: dropConflictingRedirects(dropUnroutableRedirects({ ...bundle.docsConfig, tabs, ...direct.length > 0 ? { redirects: direct } : {} }, warnings), pages, warnings),
|
|
15513
16029
|
warnings
|
|
15514
16030
|
};
|
|
15515
16031
|
}
|