@thallylabs/migrate 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -326,8 +326,8 @@ function leadingPageHeading(body) {
326
326
  const root = descriptionParser.parse(body);
327
327
  const first = root.children?.find((node) => node.type !== "mdxjsEsm" && node.type !== "html");
328
328
  if (first?.type !== "heading" || first.depth !== 1) return void 0;
329
- const text = (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" ? "" : node.value ?? (node.children ?? []).map(text).join("");
330
- const title = text(first).replace(/\s+/g, " ").trim();
329
+ const text2 = (node) => node.type === "mdxTextExpression" || node.type === "mdxFlowExpression" ? "" : node.value ?? (node.children ?? []).map(text2).join("");
330
+ const title = text2(first).replace(/\s+/g, " ").trim();
331
331
  const start = first.position?.start.offset;
332
332
  const end = first.position?.end.offset;
333
333
  return title && start !== void 0 && end !== void 0 ? { title, start, end } : void 0;
@@ -400,16 +400,16 @@ function collectEsmBindingsByPattern(source, declared) {
400
400
  var FENCE_OPEN = /^(`{3,}|~{3,})/;
401
401
  function maskInlineSpansForMath(line) {
402
402
  const blocks = [];
403
- const stash = (text) => {
404
- blocks.push(text);
403
+ const stash = (text2) => {
404
+ blocks.push(text2);
405
405
  return `\0${blocks.length - 1}\0`;
406
406
  };
407
407
  const codeMasked = line.split(/(`[^`]*`)/).map((segment, index) => index % 2 === 1 ? stash(segment) : segment).join("");
408
408
  const tagMasked = codeMasked.replace(/<\/?[a-zA-Z][^<>]*>/g, stash);
409
- const masked = [tagMasked, tagMasked].reduce((text) => text.replace(/(?<!\$)\{[^{}]*\}/g, stash), tagMasked);
409
+ const masked = [tagMasked, tagMasked].reduce((text2) => text2.replace(/(?<!\$)\{[^{}]*\}/g, stash), tagMasked);
410
410
  return {
411
411
  masked,
412
- unmask: (text) => text.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
412
+ unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
413
413
  };
414
414
  }
415
415
  var TEX_SIGNAL = /\\[a-zA-Z]|[\^_{]/;
@@ -562,11 +562,11 @@ function splitFrontmatterBlock(raw) {
562
562
  return { front: bom + source.slice(0, end), body: source.slice(end) };
563
563
  }
564
564
  var MIGRATED_COMPONENT_TAG = /^Migrated[0-9a-f]{12}$/;
565
- function isSelfClosingJsx(text) {
566
- return /\/>\s*$/.test(text);
565
+ function isSelfClosingJsx(text2) {
566
+ return /\/>\s*$/.test(text2);
567
567
  }
568
- function renameTagOccurrences(text, name, newName) {
569
- let result = text;
568
+ function renameTagOccurrences(text2, name, newName) {
569
+ let result = text2;
570
570
  const openIndex = result.indexOf(`<${name}`);
571
571
  if (openIndex >= 0) result = `${result.slice(0, openIndex + 1)}${newName}${result.slice(openIndex + 1 + name.length)}`;
572
572
  const closeIndex = result.lastIndexOf(`</${name}`);
@@ -612,9 +612,9 @@ function replaceUnknownComponents(raw, warn, options = {}) {
612
612
  if (start === void 0 || end === void 0) return "";
613
613
  const isJsx = node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
614
614
  const tagRoot = node.name ? node.name.split(".")[0] : void 0;
615
- const text = body.slice(start, end);
615
+ const text2 = body.slice(start, end);
616
616
  if (isJsx && tagRoot === "Link" && !declared.has("Link")) {
617
- if (isSelfClosingJsx(text) || !node.children?.length) return renameTagOccurrences(text, node.name, "a");
617
+ if (isSelfClosingJsx(text2) || !node.children?.length) return renameTagOccurrences(text2, node.name, "a");
618
618
  const openEnd = node.children[0].position?.start.offset ?? start;
619
619
  const closeStart = node.children.at(-1).position?.end.offset ?? end;
620
620
  return renameTagOccurrences(body.slice(start, openEnd), node.name, "a") + renderChildrenSpan(node.children, openEnd, closeStart, body, renderNode) + renameTagOccurrences(body.slice(closeStart, end), node.name, "a");
@@ -624,7 +624,7 @@ function replaceUnknownComponents(raw, warn, options = {}) {
624
624
  warned.add(node.name);
625
625
  warn(node.name);
626
626
  }
627
- if (isSelfClosingJsx(text)) return "";
627
+ if (isSelfClosingJsx(text2)) return "";
628
628
  if (!node.children?.length) return "<div></div>";
629
629
  const openEnd = node.children[0].position?.start.offset ?? start;
630
630
  const closeStart = node.children.at(-1).position?.end.offset ?? end;
@@ -635,7 +635,7 @@ ${children}
635
635
 
636
636
  </div>` : `<div>${children}</div>`;
637
637
  }
638
- if (!node.children?.length) return text;
638
+ if (!node.children?.length) return text2;
639
639
  return renderChildrenSpan(node.children, start, end, body, renderNode);
640
640
  };
641
641
  return front + renderNode(root);
@@ -643,8 +643,8 @@ ${children}
643
643
  function maskCode(body) {
644
644
  const source = body.replace(/\u0000/g, "");
645
645
  const blocks = [];
646
- const stash = (text) => {
647
- blocks.push(text);
646
+ const stash = (text2) => {
647
+ blocks.push(text2);
648
648
  return `\0${blocks.length - 1}\0`;
649
649
  };
650
650
  const lines = [];
@@ -671,7 +671,7 @@ function maskCode(body) {
671
671
  if (fenceBuffer.length) lines.push(stash(fenceBuffer.join("\n")));
672
672
  return {
673
673
  masked: lines.join("\n"),
674
- unmask: (text) => text.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
674
+ unmask: (text2) => text2.replace(/\u0000(\d+)\u0000/g, (_match, index) => blocks[Number(index)])
675
675
  };
676
676
  }
677
677
  function replaceOutsideCode(body, transform) {
@@ -690,7 +690,7 @@ function rewriteFernRelativePageLinks(body, currentSourcePath, routesBySourcePat
690
690
  const route = candidates.map((candidate) => routesBySourcePath.get(candidate)).find(Boolean);
691
691
  return route ? `/${route.replace(/^\/+/, "")}${suffix}` : target;
692
692
  };
693
- return replaceOutsideCode(body, (text) => text.replace(
693
+ return replaceOutsideCode(body, (text2) => text2.replace(
694
694
  /(\]\()(<[^>\n]+>|[^\s)]+)(?=\s|\))/g,
695
695
  (_match, opening, target) => `${opening}${target.startsWith("<") && target.endsWith(">") ? `<${rewrite(target.slice(1, -1))}>` : rewrite(target)}`
696
696
  ).replace(
@@ -1290,11 +1290,26 @@ function parseMarkdownPage(input) {
1290
1290
  hidden: parsed.data.hidden === true ? true : void 0,
1291
1291
  noindex: parsed.data.noindex === true || parsed.data.noindex === "true" ? true : void 0,
1292
1292
  openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
1293
+ ...apiFrontmatter(parsed.data, input.warn),
1293
1294
  body,
1294
1295
  source: input.source,
1295
1296
  ...parsed.error ? { frontmatterError: parsed.error } : {}
1296
1297
  };
1297
1298
  }
1299
+ var AUTH_METHODS = /* @__PURE__ */ new Set(["bearer", "basic", "key", "none"]);
1300
+ function apiFrontmatter(data, warn) {
1301
+ const result = {};
1302
+ if (data.api !== void 0 && data.api !== null) {
1303
+ if (typeof data.api === "string" && data.api.trim()) result.api = data.api.trim();
1304
+ else warn?.(`The page's "api" frontmatter is not a "METHOD url-or-path" string and was dropped.`);
1305
+ }
1306
+ if (data.authMethod !== void 0 && data.authMethod !== null) {
1307
+ const method = typeof data.authMethod === "string" ? data.authMethod.trim().toLowerCase() : "";
1308
+ if (AUTH_METHODS.has(method)) result.authMethod = method;
1309
+ else warn?.(`The page's "authMethod" frontmatter ${JSON.stringify(data.authMethod)} is not one of bearer, basic, key, none and was dropped.`);
1310
+ }
1311
+ return result;
1312
+ }
1298
1313
 
1299
1314
  // src/path.ts
1300
1315
  import { isAbsolute, relative, resolve, sep } from "path";
@@ -2165,7 +2180,7 @@ const MintlifyComponents = {
2165
2180
  }
2166
2181
  function applyReplacements(source, replacements) {
2167
2182
  return replacements.sort((a, b) => b.start - a.start).reduce(
2168
- (text, edit) => text.slice(0, edit.start) + edit.value + text.slice(edit.end),
2183
+ (text2, edit) => text2.slice(0, edit.start) + edit.value + text2.slice(edit.end),
2169
2184
  source
2170
2185
  );
2171
2186
  }
@@ -2454,14 +2469,14 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2454
2469
  }
2455
2470
  return;
2456
2471
  }
2457
- const text = content.toString("utf8");
2458
- const syntax = ts2.transpileModule(text, {
2472
+ const text2 = content.toString("utf8");
2473
+ const syntax = ts2.transpileModule(text2, {
2459
2474
  fileName: path,
2460
2475
  reportDiagnostics: true,
2461
2476
  compilerOptions: { jsx: ts2.JsxEmit.Preserve, target: ts2.ScriptTarget.ESNext, module: ts2.ModuleKind.ESNext }
2462
2477
  });
2463
2478
  if (syntax.diagnostics?.some((diagnostic) => diagnostic.category === ts2.DiagnosticCategory.Error)) throw new Error("component source contains unsupported JavaScript or TypeScript syntax");
2464
- const ast = sourceFile(text, path);
2479
+ const ast = sourceFile(text2, path);
2465
2480
  if (ast.statements.some((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression) && statement.expression.text === "use server")) throw new Error("server-only component modules require manual migration");
2466
2481
  const edits = [];
2467
2482
  function dependency(literal, importDeclaration) {
@@ -2509,7 +2524,7 @@ function createComponentMigrator(siteRoot, confinementRoot, warnings, sourceIden
2509
2524
  ts2.forEachChild(node, inspect);
2510
2525
  }
2511
2526
  inspect(ast);
2512
- const replaced = applyReplacements(text, edits);
2527
+ const replaced = applyReplacements(text2, edits);
2513
2528
  const shebangMatch = /^#!.*\r?\n/.exec(replaced);
2514
2529
  const prologue = "// @ts-nocheck\n'use client';\n\n";
2515
2530
  staged.set(destination, {
@@ -3016,10 +3031,10 @@ ${inlineSource}` });
3016
3031
  const start = node.position.start.offset;
3017
3032
  const end = node.position.end.offset;
3018
3033
  if (extracted.some((item) => start >= item.node.position.start.offset && end <= item.node.position.end.offset)) return;
3019
- const text = content.slice(start, end);
3020
- const opening = text.indexOf(`<${node.name}`);
3034
+ const text2 = content.slice(start, end);
3035
+ const opening = text2.indexOf(`<${node.name}`);
3021
3036
  if (opening >= 0) edits.push({ start: start + opening + 1, end: start + opening + 1 + node.name.length, value: replacement });
3022
- const closing = text.lastIndexOf(`</${node.name}`);
3037
+ const closing = text2.lastIndexOf(`</${node.name}`);
3023
3038
  if (closing >= 0) edits.push({ start: start + closing + 2, end: start + closing + 2 + node.name.length, value: replacement });
3024
3039
  });
3025
3040
  walk(tree, (node) => {
@@ -3906,6 +3921,39 @@ function projectedCompatibleConfig(config, warnings) {
3906
3921
  ...typeof feedback?.thumbsRating === "boolean" ? { feedback: { thumbsRating: feedback.thumbsRating } } : {}
3907
3922
  };
3908
3923
  }
3924
+ function projectedApiMdx(config, warnings) {
3925
+ const mdx = objectValue2(objectValue2(config.api)?.mdx);
3926
+ if (!mdx) return {};
3927
+ const warn = (message) => warnings.push({ code: "unsupported-config", message });
3928
+ const rawServers = mdx.server === void 0 ? [] : Array.isArray(mdx.server) ? mdx.server : [mdx.server];
3929
+ const servers = rawServers.filter((entry) => {
3930
+ const valid = typeof entry === "string" && /^https?:\/\/[^\s/@?#]+(?:\/[^\s?#]*)?$/i.test(entry.trim());
3931
+ if (!valid) warn(`api.mdx.server entry ${JSON.stringify(entry)} is not an absolute http(s) URL and was dropped.`);
3932
+ return valid;
3933
+ }).map((entry) => entry.trim());
3934
+ const auth = objectValue2(mdx.auth);
3935
+ const method = typeof auth?.method === "string" ? auth.method.toLowerCase() : void 0;
3936
+ const name = typeof auth?.name === "string" && auth.name.trim() ? auth.name.trim() : void 0;
3937
+ let projectedAuth;
3938
+ if (mdx.auth !== void 0 && auth && method !== void 0) {
3939
+ if (method === "bearer" || method === "basic" || method === "key" && name) {
3940
+ projectedAuth = { method, ...name ? { name } : {} };
3941
+ } else {
3942
+ warn(`api.mdx.auth method ${JSON.stringify(auth.method)} is not supported (bearer, basic, or key with a name) and was dropped.`);
3943
+ }
3944
+ } else if (mdx.auth !== void 0 && !auth) {
3945
+ warn("api.mdx.auth is not an object and was dropped.");
3946
+ }
3947
+ if (servers.length === 0 && !projectedAuth) return {};
3948
+ return {
3949
+ api: {
3950
+ mdx: {
3951
+ ...servers.length > 0 ? { server: servers.length === 1 && typeof mdx.server === "string" ? servers[0] : servers } : {},
3952
+ ...projectedAuth ? { auth: projectedAuth } : {}
3953
+ }
3954
+ }
3955
+ };
3956
+ }
3909
3957
  var NEXT_REDIRECT_PARAM_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
3910
3958
  function translateRedirectWildcards(source, destination) {
3911
3959
  const sourceSegments = source.split("/");
@@ -4093,6 +4141,7 @@ function projectMintlifyNavigation(config, options = {}) {
4093
4141
  tabs,
4094
4142
  ...projectionTrace.rootContainerKind === "dropdowns" ? { navigation: { display: "dropdown" } } : {},
4095
4143
  ...projectedCompatibleConfig(config, warnings),
4144
+ ...projectedApiMdx(config, warnings),
4096
4145
  ...i18n ? { i18n } : {},
4097
4146
  ...redirects.length > 0 ? { redirects } : {}
4098
4147
  },
@@ -5141,9 +5190,9 @@ function announcementMarkdown(message) {
5141
5190
  links.set(marker, destination && label ? `[${escapeText(label)}](${destination})` : escapeText(label));
5142
5191
  document(element).replaceWith(marker);
5143
5192
  });
5144
- let text = escapeText(document("body").text());
5145
- for (const [marker, link] of links) text = text.replaceAll(marker, link);
5146
- return text.trim();
5193
+ let text2 = escapeText(document("body").text());
5194
+ for (const [marker, link] of links) text2 = text2.replaceAll(marker, link);
5195
+ return text2.trim();
5147
5196
  }
5148
5197
  function objectValue4(value) {
5149
5198
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -5661,6 +5710,199 @@ function projectFernNavigation(input) {
5661
5710
  };
5662
5711
  }
5663
5712
 
5713
+ // src/openapi-ref.ts
5714
+ function unquote(value) {
5715
+ const trimmed = value.trim();
5716
+ const quote = trimmed[0];
5717
+ return (quote === '"' || quote === "'") && trimmed.length >= 2 && trimmed.endsWith(quote) ? trimmed.slice(1, -1).trim() : trimmed;
5718
+ }
5719
+ function splitOpenApiRef(raw) {
5720
+ const value = unquote(raw);
5721
+ const match = /^(?:(.+?)\s+)?((?:webhook\s+[^\s/]\S*)|(?:[A-Za-z]+\s+\/.*))$/i.exec(value);
5722
+ if (!match) return null;
5723
+ const specRef = match[1] ? unquote(match[1]) : void 0;
5724
+ return { ...specRef ? { specRef } : {}, operation: match[2] };
5725
+ }
5726
+ function specRefBaseName(specRef) {
5727
+ return specRef.trim().replace(/\\/g, "/").split(/[?#]/, 1)[0].split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
5728
+ }
5729
+ function withSpecRef(ref, specRef) {
5730
+ return `${specRef} ${ref.operation}`;
5731
+ }
5732
+
5733
+ // src/analytics.ts
5734
+ var GA4_ID = /^G-[A-Z0-9]{4,20}$/;
5735
+ var GTM_ID = /^GTM-[A-Z0-9]{4,12}$/;
5736
+ var POSTHOG_KEY = /^phc_[A-Za-z0-9]{20,64}$/;
5737
+ var HOSTNAME_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
5738
+ var HOSTNAME = new RegExp(`^${HOSTNAME_LABEL}(?:\\.${HOSTNAME_LABEL})*$`);
5739
+ var SAFE_PATH = /^\/[A-Za-z0-9._~\-/]*$/;
5740
+ var MAX_PLAUSIBLE_DOMAINS = 10;
5741
+ var SUPPORTED = ["ga4", "gtm", "posthog", "plausible"];
5742
+ var LEGACY_GA4_ALIAS = "googleAnalytics";
5743
+ function own(record, key) {
5744
+ return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
5745
+ }
5746
+ function isRecord(value) {
5747
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5748
+ }
5749
+ function redact(value) {
5750
+ if (typeof value !== "string") return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
5751
+ return value.length === 0 ? "empty string" : `"${value.trim().slice(0, 4).replace(/[^\x20-\x7e]/g, "?")}..." (${value.length} chars)`;
5752
+ }
5753
+ function isPublicHostname(hostname) {
5754
+ return !/(^|\.)localhost$/.test(hostname) && !/(^|\.)\d+$/.test(hostname) && !hostname.includes(":") && !hostname.startsWith("[");
5755
+ }
5756
+ function normalizeHttpsUrl(value, allowPath) {
5757
+ if (!/^https:\/\//i.test(value) || /[?#\\]/.test(value)) return null;
5758
+ let url;
5759
+ try {
5760
+ url = new URL(value);
5761
+ } catch {
5762
+ return null;
5763
+ }
5764
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) return null;
5765
+ if (!HOSTNAME.test(url.hostname) || !isPublicHostname(url.hostname) || url.port === "0") return null;
5766
+ const fullPath = /^https:\/\/[^/]*(\/.*)?$/i.exec(value)?.[1] ?? "";
5767
+ if (fullPath.includes("//")) return null;
5768
+ const path = fullPath.replace(/\/+$/, "");
5769
+ if (path && (!allowPath || !SAFE_PATH.test(path) || path.split("/").some((s) => s === ".." || s === "."))) return null;
5770
+ return `${url.origin}${path}`;
5771
+ }
5772
+ function normalizeDomains(value) {
5773
+ const parts = value.split(",").map((part) => part.trim().toLowerCase());
5774
+ if (parts.length > MAX_PLAUSIBLE_DOMAINS) return null;
5775
+ return parts.every((part) => part.length <= 253 && HOSTNAME.test(part)) ? parts.join(",") : null;
5776
+ }
5777
+ function normalizeServer(value) {
5778
+ if (/^https:\/\//i.test(value)) {
5779
+ const origin2 = normalizeHttpsUrl(value, false);
5780
+ return origin2 ? origin2.replace(/^https:\/\//, "") : null;
5781
+ }
5782
+ const bare = /^([a-z0-9.-]+)(?::(\d{1,5}))?$/i.exec(value);
5783
+ if (!bare || bare[2] !== void 0 && (Number(bare[2]) < 1 || Number(bare[2]) > 65535)) return null;
5784
+ const origin = normalizeHttpsUrl(`https://${bare[1].toLowerCase()}${bare[2] === void 0 ? "" : `:${Number(bare[2])}`}`, false);
5785
+ return origin ? origin.replace(/^https:\/\//, "") : null;
5786
+ }
5787
+ function text(value) {
5788
+ return typeof value === "string" && value.trim() && value.length <= 2048 ? value.trim() : null;
5789
+ }
5790
+ function projectMintlifyIntegrations(config) {
5791
+ if (!isRecord(config)) return { warnings: [] };
5792
+ try {
5793
+ return project(config);
5794
+ } catch {
5795
+ return { warnings: [{ code: "unsupported-config", message: "Mintlify analytics configuration could not be read; analytics were not imported." }] };
5796
+ }
5797
+ }
5798
+ function project(config) {
5799
+ const warnings = [];
5800
+ const warn = (message) => warnings.push({ code: "unsupported-config", message });
5801
+ const sources = [];
5802
+ for (const [label, key] of [["integrations", "integrations"], ["analytics", "analytics"]]) {
5803
+ const value = own(config, key);
5804
+ if (value === void 0) continue;
5805
+ if (isRecord(value)) sources.push({ label, block: value });
5806
+ else warn(`Mintlify ${label} must be an object (found ${redact(value)}); analytics from it were not imported.`);
5807
+ }
5808
+ const integrations = {};
5809
+ const pick = (name) => {
5810
+ const found = [];
5811
+ for (const source of sources) {
5812
+ let value = own(source.block, name);
5813
+ if (value === void 0 && name === "ga4" && source.label === "analytics") value = own(source.block, LEGACY_GA4_ALIAS);
5814
+ if (value !== void 0) found.push({ label: `${source.label}.${name}`, value });
5815
+ }
5816
+ if (found.length > 1) {
5817
+ warn(`Mintlify ${found[0].label} and ${found[1].label} are both set; using ${found[0].label} for ${name}.`);
5818
+ }
5819
+ return found;
5820
+ };
5821
+ const field = (label, block, key, required) => {
5822
+ const raw = own(block, key);
5823
+ if (raw === void 0) {
5824
+ if (required) warn(`Mintlify ${label}.${key} is missing; ${label} was not imported.`);
5825
+ return required ? null : void 0;
5826
+ }
5827
+ const value = text(raw);
5828
+ if (value === null) warn(`Mintlify ${label}.${key} is not a usable string (found ${redact(raw)}); ${label} was not imported.`);
5829
+ return value;
5830
+ };
5831
+ const objectFor = (entry) => {
5832
+ if (isRecord(entry.value)) return entry.value;
5833
+ warn(`Mintlify ${entry.label} must be an object (found ${redact(entry.value)}); provider was not imported.`);
5834
+ return null;
5835
+ };
5836
+ const ga4 = pick("ga4")[0];
5837
+ const ga4Block = ga4 ? objectFor(ga4) : null;
5838
+ if (ga4 && ga4Block) {
5839
+ const id = field(ga4.label, ga4Block, "measurementId", true);
5840
+ if (id && GA4_ID.test(id)) integrations.ga4 = { measurementId: id };
5841
+ else if (id) warn(`Mintlify ${ga4.label}.measurementId ${redact(id)} is not a GA4 ID (G-XXXXXXXXXX); Universal Analytics and lowercase IDs are not imported.`);
5842
+ }
5843
+ const gtm = pick("gtm")[0];
5844
+ const gtmBlock = gtm ? objectFor(gtm) : null;
5845
+ if (gtm && gtmBlock) {
5846
+ const id = field(gtm.label, gtmBlock, "tagId", true);
5847
+ if (id && GTM_ID.test(id)) integrations.gtm = { tagId: id };
5848
+ else if (id) warn(`Mintlify ${gtm.label}.tagId ${redact(id)} is not a GTM container ID (GTM-XXXXXXX); GTM was not imported.`);
5849
+ }
5850
+ const plausible = pick("plausible")[0];
5851
+ const plausibleBlock = plausible ? objectFor(plausible) : null;
5852
+ if (plausible && plausibleBlock) {
5853
+ const domainRaw = field(plausible.label, plausibleBlock, "domain", true);
5854
+ const serverRaw = field(plausible.label, plausibleBlock, "server", false);
5855
+ const domain = domainRaw ? normalizeDomains(domainRaw) : null;
5856
+ const server = serverRaw ? normalizeServer(serverRaw) : serverRaw;
5857
+ if (domainRaw && !domain) warn(`Mintlify ${plausible.label}.domain ${redact(domainRaw)} must be hostname(s) without a protocol or path; Plausible was not imported.`);
5858
+ if (serverRaw && !server) warn(`Mintlify ${plausible.label}.server ${redact(serverRaw)} must be a hostname; Plausible was not imported.`);
5859
+ if (domain && server !== null) integrations.plausible = { domain, ...server ? { server } : {} };
5860
+ }
5861
+ const posthog = pick("posthog")[0];
5862
+ const posthogBlock = posthog ? objectFor(posthog) : null;
5863
+ if (posthog && posthogBlock) {
5864
+ const key = field(posthog.label, posthogBlock, "apiKey", true);
5865
+ const hostRaw = field(posthog.label, posthogBlock, "apiHost", false);
5866
+ const host = hostRaw ? normalizeHttpsUrl(hostRaw, true) : hostRaw;
5867
+ const recording = own(posthogBlock, "sessionRecording");
5868
+ if (recording !== void 0 && typeof recording !== "boolean") warn(`Mintlify ${posthog.label}.sessionRecording must be a boolean (found ${redact(recording)}); session recording disabled.`);
5869
+ if (key && !POSTHOG_KEY.test(key)) warn(`Mintlify ${posthog.label}.apiKey ${redact(key)} is not a PostHog project key (phc_...); PostHog was not imported.`);
5870
+ if (hostRaw && !host) warn(`Mintlify ${posthog.label}.apiHost ${redact(hostRaw)} must be an https URL without credentials, query, or fragment; PostHog was not imported.`);
5871
+ if (key && POSTHOG_KEY.test(key) && host !== null) {
5872
+ integrations.posthog = {
5873
+ apiKey: key,
5874
+ ...host ? { apiHost: host } : {},
5875
+ // Fail closed: a present non-boolean value turns recording off.
5876
+ ...recording !== void 0 ? { sessionRecording: recording === true } : {}
5877
+ };
5878
+ }
5879
+ }
5880
+ const unsupported = /* @__PURE__ */ new Set();
5881
+ for (const source of sources) {
5882
+ for (const key of Object.keys(source.block)) {
5883
+ if (SUPPORTED.includes(key)) continue;
5884
+ if (key === LEGACY_GA4_ALIAS && source.label === "analytics") continue;
5885
+ unsupported.add(/^[A-Za-z0-9_-]{1,40}$/.test(key) ? key : "(invalid name)");
5886
+ }
5887
+ }
5888
+ if (unsupported.size > 0) {
5889
+ const names = [...unsupported].slice(0, 30);
5890
+ warn(`Mintlify analytics/integration providers Thally cannot render were not imported: ${names.join(", ")}${unsupported.size > names.length ? ", ..." : ""}. Supported: ga4, gtm, posthog, plausible.`);
5891
+ }
5892
+ return { ...Object.keys(integrations).length > 0 ? { integrations } : {}, warnings };
5893
+ }
5894
+ function validateIntegrations(integrations) {
5895
+ if (integrations === void 0) return [];
5896
+ const result = projectMintlifyIntegrations({ integrations });
5897
+ const messages = result.warnings.map(
5898
+ (warning) => warning.message.replace(/^Mintlify analytics\/integration providers Thally cannot render were not imported: /, "docs.json integrations: Thally does not support these providers, so they will not be rendered: ").replace(/^Mintlify /, "docs.json ").replace(/\b(?:was|were|are) not imported\b/g, "will not be rendered")
5899
+ );
5900
+ if (result.integrations?.ga4 && result.integrations.gtm) {
5901
+ messages.push("docs.json integrations enable both ga4 and gtm; a GA4 tag inside the GTM container would double-count page views.");
5902
+ }
5903
+ return messages;
5904
+ }
5905
+
5664
5906
  // src/repository.ts
5665
5907
  var MAX_SOURCE_FILES = 5e3;
5666
5908
  var MAX_PAGE_BYTES = 2e6;
@@ -6477,8 +6719,8 @@ var PATH_SEPARATORS = /[\s"'()<>[\]{}=,;:|\\`*!?#&]+/;
6477
6719
  function publicAssetKey(repoRelative) {
6478
6720
  return repoRelative.split("/", 1)[0].toLowerCase() === "public" ? repoRelative.slice("public/".length) : repoRelative;
6479
6721
  }
6480
- function addPathReferences(text, baseDir, exact, loose) {
6481
- for (const word of text.split(PATH_SEPARATORS)) {
6722
+ function addPathReferences(text2, baseDir, exact, loose) {
6723
+ for (const word of text2.split(PATH_SEPARATORS)) {
6482
6724
  let token = word.replace(/\.+$/, "");
6483
6725
  if (!/\.[a-z0-9]{2,5}$/i.test(token) || token.startsWith("//")) continue;
6484
6726
  if (token.includes("%")) {
@@ -6520,8 +6762,8 @@ function classifyApiSpec(path) {
6520
6762
  } catch {
6521
6763
  return "unknown";
6522
6764
  }
6523
- const text = buffer.toString("utf8", 0, bytesRead);
6524
- const topLevel = text.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
6765
+ const text2 = buffer.toString("utf8", 0, bytesRead);
6766
+ const topLevel = text2.match(/^(?:openapi|swagger|asyncapi)\s*:/m)?.[0] ?? text2.match(/^\s*\{\s*"(?:openapi|swagger|asyncapi)"\s*:/m)?.[0];
6525
6767
  if (!topLevel) return "unknown";
6526
6768
  return /asyncapi/i.test(topLevel) ? "asyncapi" : "openapi";
6527
6769
  }
@@ -6619,6 +6861,27 @@ function resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteSpecs) {
6619
6861
  }
6620
6862
  return specs;
6621
6863
  }
6864
+ function rewriteMintlifyPageSpecRefs(pages, specs, remoteUrls, warnings) {
6865
+ for (const page of pages) {
6866
+ const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
6867
+ if (!ref?.specRef) continue;
6868
+ if (/^https?:\/\//i.test(ref.specRef)) {
6869
+ if (!remoteUrls.has(ref.specRef)) {
6870
+ warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the remote OpenAPI spec "${ref.specRef}", which is not referenced from docs.json and was not migrated; the page will not render an endpoint.`, source: page.source });
6871
+ }
6872
+ continue;
6873
+ }
6874
+ const key = ref.specRef.split(/[?#]/, 1)[0].replace(/\\/g, "/").replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
6875
+ const byPath = specs.find((spec) => spec.sourcePath === key);
6876
+ const byName = specs.filter((spec) => spec.filename.toLowerCase() === specRefBaseName(key));
6877
+ const match = byPath ?? (byName.length === 1 ? byName[0] : void 0);
6878
+ if (match) {
6879
+ page.openapi = withSpecRef(ref, specAssetPath(match.filename));
6880
+ } else {
6881
+ warnings.push({ code: "unsupported-config", message: `Page "${page.id}" references the OpenAPI spec "${ref.specRef}", which is not referenced from docs.json and was not migrated; the page will not render an endpoint. Add the spec to a tab's api setting.`, source: page.source });
6882
+ }
6883
+ }
6884
+ }
6622
6885
  var OPENAPI_HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head", "trace"];
6623
6886
  var FERN_CRUD_METHOD_NAMES = {
6624
6887
  findall: "list",
@@ -7009,12 +7272,12 @@ function externalizeMissingFernLinks(body, pageIds, sourceSiteUrl, externalized)
7009
7272
  return target;
7010
7273
  }
7011
7274
  };
7012
- return replaceOutsideCode(body, (text) => text.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
7275
+ return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()\/(?!\/)([^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(`/${target}`)}${after}`).replace(/(\b(?:href|to)=(['"]))\/(?!\/)([^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(`/${target}`)}${quote}`));
7013
7276
  }
7014
7277
  function stripFernBasePathFromLinks(body, basePath) {
7015
7278
  if (!basePath) return body;
7016
7279
  const escaped = basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7017
- return replaceOutsideCode(body, (text) => text.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
7280
+ return replaceOutsideCode(body, (text2) => text2.replace(new RegExp(`(\\]\\()${escaped}(?=\\/|[)#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")).replace(new RegExp(`(\\b(?:href|to)=["'])${escaped}(?=\\/|["'#?])`, "g"), (match, prefix, offset, source) => prefix + (source[offset + match.length] === "/" ? "" : "/")));
7018
7281
  }
7019
7282
  function rewriteMintlifyMountedLinks(body, pageIds) {
7020
7283
  const rewrite = (target) => {
@@ -7031,7 +7294,7 @@ function rewriteMintlifyMountedLinks(body, pageIds) {
7031
7294
  }
7032
7295
  return target;
7033
7296
  };
7034
- return replaceOutsideCode(body, (text) => text.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
7297
+ return replaceOutsideCode(body, (text2) => text2.replace(/(\]\()(\/[^\s)]+)(\))/g, (_match, before, target, after) => `${before}${rewrite(target)}${after}`).replace(/(\b(?:href|to)=(['"]))(\/[^'"\n]+)\2/g, (_match, before, quote, target) => `${before}${rewrite(target)}${quote}`));
7035
7298
  }
7036
7299
  function preserveLinkedAnchors(pages) {
7037
7300
  const pageById = new Map(pages.map((page) => [page.id, page]));
@@ -7055,10 +7318,10 @@ function preserveLinkedAnchors(pages) {
7055
7318
  if (id !== currentId) crossPageRequests.add(`${id}#${fragment}`);
7056
7319
  };
7057
7320
  for (const page of pages) {
7058
- replaceOutsideCode(page.body, (text) => {
7059
- for (const match of text.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
7060
- for (const match of text.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
7061
- return text;
7321
+ replaceOutsideCode(page.body, (text2) => {
7322
+ for (const match of text2.matchAll(/\]\(([^)\s]+)\)/g)) addTarget(match[1], page.id);
7323
+ for (const match of text2.matchAll(/\b(?:href|to)=(['"])([^'"\n]+)\1/g)) addTarget(match[2], page.id);
7324
+ return text2;
7062
7325
  });
7063
7326
  }
7064
7327
  const comparable = (value) => value.replace(/&(?:lt|gt|amp|quot);/g, " ").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
@@ -7082,9 +7345,9 @@ function preserveLinkedAnchors(pages) {
7082
7345
  return match ? { index, text: match[1] } : null;
7083
7346
  }).filter((entry) => entry !== null);
7084
7347
  const existing = /* @__PURE__ */ new Set();
7085
- replaceOutsideCode(page.body, (text) => {
7086
- for (const match of text.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
7087
- return text;
7348
+ replaceOutsideCode(page.body, (text2) => {
7349
+ for (const match of text2.matchAll(/\bid=(?:"([^"]+)"|'([^']+)')/g)) existing.add(match[1] ?? match[2]);
7350
+ return text2;
7088
7351
  });
7089
7352
  const candidateIndexes = /* @__PURE__ */ new Map();
7090
7353
  for (const heading of headings) {
@@ -7246,7 +7509,7 @@ function repositoryAssetHref(value, currentFile, siteRoot, onReferenced) {
7246
7509
  }
7247
7510
  }
7248
7511
  function rewriteFernIdRenameLinks(body, renames) {
7249
- return replaceOutsideCode(body, (text) => {
7512
+ return replaceOutsideCode(body, (text2) => {
7250
7513
  const rewriteTarget = (target) => {
7251
7514
  const suffixIndex = target.search(/[?#]/);
7252
7515
  const path = (suffixIndex >= 0 ? target.slice(0, suffixIndex) : target).replace(/^\/+/, "");
@@ -7254,7 +7517,7 @@ function rewriteFernIdRenameLinks(body, renames) {
7254
7517
  const renamed = renames.get(path);
7255
7518
  return renamed ? `/${renamed}${suffix}` : target;
7256
7519
  };
7257
- return text.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
7520
+ return text2.replace(/(\]\()\/([^\s)]+)(?=[\s)]|$)/g, (_match, prefix, target) => `${prefix}${rewriteTarget(`/${target}`)}`).replace(/(\bhref=")\/([^"]+)(")/g, (_match, prefix, target, suffix) => `${prefix}${rewriteTarget(`/${target}`)}${suffix}`);
7258
7521
  });
7259
7522
  }
7260
7523
  function rewriteRepositoryAssetLinks(body, currentFile, siteRoot, onReferenced) {
@@ -7986,6 +8249,7 @@ function migrateRepository(options) {
7986
8249
  const page = parseMarkdownPage({
7987
8250
  id,
7988
8251
  navigationId,
8252
+ warn: (message) => warnings.push({ code: "unsupported-config", message, source: relative5(repositoryDir, file.absolutePath).replace(/\\/g, "/") }),
7989
8253
  ...locale ? { locale } : {},
7990
8254
  raw,
7991
8255
  platform,
@@ -8446,6 +8710,7 @@ function migrateRepository(options) {
8446
8710
  }
8447
8711
  if (withheldSpecRefs.some((ref) => specRefMatches(ref, spec.sourcePath, spec.filename, true))) warnSharedSpec(spec.sourcePath);
8448
8712
  }
8713
+ let pageSpecs = resolvedSpecs;
8449
8714
  if (resolvedSpecs.length > 0) {
8450
8715
  docsConfig = injectOpenApiSpecs(docsConfig, resolvedSpecs, warnings);
8451
8716
  const { operationLinks, prefixLandings } = apiOperationLinkMap(
@@ -8466,10 +8731,12 @@ function migrateRepository(options) {
8466
8731
  assets.push(specAsset(filename, readFileSync5(fallback.absolutePath)));
8467
8732
  }
8468
8733
  docsConfig = injectOpenApiSpecs(docsConfig, [{ filename }]);
8734
+ pageSpecs = [{ filename, sourcePath: fallback.relativePath }];
8469
8735
  if (withheldSpecRefs.some(matches)) warnSharedSpec(fallback.relativePath);
8470
8736
  }
8471
8737
  }
8472
8738
  }
8739
+ rewriteMintlifyPageSpecRefs(pages, pageSpecs, new Set(remoteApiSpecs.map((spec) => spec.url)), warnings);
8473
8740
  }
8474
8741
  if (platform === "mintlify") {
8475
8742
  const sources = new Set((docsConfig.redirects ?? []).map((redirect) => redirect.source));
@@ -8778,6 +9045,9 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
8778
9045
  docsConfig = { ...docsConfig, navbar: { ...docsConfig.navbar, logo: null } };
8779
9046
  }
8780
9047
  if (mintlifyConfig) {
9048
+ const analytics = projectMintlifyIntegrations(mintlifyConfig);
9049
+ warnings.push(...analytics.warnings);
9050
+ if (analytics.integrations) docsConfig = { ...docsConfig, integrations: analytics.integrations };
8781
9051
  const appearance = mintlifyAppearance(mintlifyConfig, warnings);
8782
9052
  if (Object.keys(appearance).length > 0) {
8783
9053
  docsConfig = { ...docsConfig, appearance: { ...docsConfig.appearance, ...appearance } };
@@ -8889,6 +9159,8 @@ function renderPage(bundle, page) {
8889
9159
  page.hidden ? "hidden: true" : null,
8890
9160
  page.noindex ? "noindex: true" : null,
8891
9161
  page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
9162
+ page.api ? `api: ${yamlString(page.api)}` : null,
9163
+ page.authMethod ? `authMethod: ${yamlString(page.authMethod)}` : null,
8892
9164
  bundle.sourceKind === "url" ? `source: ${yamlString(page.source)}` : null,
8893
9165
  "---",
8894
9166
  "",
@@ -8896,6 +9168,9 @@ function renderPage(bundle, page) {
8896
9168
  ""
8897
9169
  ].filter((line) => line !== null).join("\n");
8898
9170
  }
9171
+ function isIntegrationsObject(value) {
9172
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9173
+ }
8899
9174
  function mergeMigrationConfig(existing, incoming) {
8900
9175
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
8901
9176
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
@@ -8966,7 +9241,15 @@ function mergeMigrationConfig(existing, incoming) {
8966
9241
  // Collection presentation belongs to the imported information
8967
9242
  // architecture; starter defaults must not coerce source dropdowns to tabs.
8968
9243
  navigation: incoming.navigation ?? existing.navigation,
8969
- ...i18n ? { i18n } : {}
9244
+ ...i18n ? { i18n } : {},
9245
+ // Existing providers win per provider; imported ones fill the gaps.
9246
+ // A malformed (string/array) existing value must not spread into index keys.
9247
+ ...isIntegrationsObject(existing.integrations) || isIntegrationsObject(incoming.integrations) ? {
9248
+ integrations: {
9249
+ ...isIntegrationsObject(incoming.integrations) ? incoming.integrations : {},
9250
+ ...isIntegrationsObject(existing.integrations) ? existing.integrations : {}
9251
+ }
9252
+ } : {}
8970
9253
  };
8971
9254
  }
8972
9255
  function renderMigrationFiles(bundle, options = {}) {
@@ -9957,7 +10240,7 @@ ${fence}
9957
10240
  }
9958
10241
  });
9959
10242
  const escape = turndown.escape.bind(turndown);
9960
- turndown.escape = (text) => escape(text).replace(/[<{]/g, (match) => `\\${match}`);
10243
+ turndown.escape = (text2) => escape(text2).replace(/[<{]/g, (match) => `\\${match}`);
9961
10244
  const body = turndown.turndown(root.html() ?? "").trim();
9962
10245
  if (!title || body.length < 40 && $("article").length === 0) return { page: null, links };
9963
10246
  return {
@@ -10510,6 +10793,11 @@ async function migrateUrl(options) {
10510
10793
  topLevelNavigation
10511
10794
  });
10512
10795
  if (sourceMintlifyProjection) warnings.push(...sourceMintlifyProjection.warnings);
10796
+ if (sourceMintlifyConfig) {
10797
+ const analytics = projectMintlifyIntegrations(sourceMintlifyConfig);
10798
+ warnings.push(...analytics.warnings);
10799
+ if (analytics.integrations) docsConfig.integrations = analytics.integrations;
10800
+ }
10513
10801
  if (docusaurusRedirects.length > 0) {
10514
10802
  docsConfig.redirects = [...new Map(docusaurusRedirects.map((redirect) => [
10515
10803
  `${redirect.source}:${redirect.destination}`,
@@ -10658,8 +10946,8 @@ async function downloadSpec(value, fetcher) {
10658
10946
  throw new Error("Remote OpenAPI spec redirected too many times.");
10659
10947
  }
10660
10948
  function parseOpenApi(content, extension) {
10661
- const text = content.toString("utf8");
10662
- const parsed = extension === "yaml" || !text.trimStart().startsWith("{") ? parseYaml6(text) : JSON.parse(text);
10949
+ const text2 = content.toString("utf8");
10950
+ const parsed = extension === "yaml" || !text2.trimStart().startsWith("{") ? parseYaml6(text2) : JSON.parse(text2);
10663
10951
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI spec is not an object.");
10664
10952
  const document = parsed;
10665
10953
  if (typeof document.openapi !== "string" || !/^3\./.test(document.openapi)) {
@@ -10700,6 +10988,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
10700
10988
  const tabs = bundle.docsConfig.tabs.map((tab) => ({ ...tab }));
10701
10989
  const redirects = [...bundle.docsConfig.redirects ?? []];
10702
10990
  const warnings = [...bundle.warnings];
10991
+ const pages = bundle.pages.map((page) => ({ ...page }));
10703
10992
  const authoredLinks = /* @__PURE__ */ new Set();
10704
10993
  for (const page of bundle.pages) {
10705
10994
  for (const match of page.body.matchAll(/\/[a-z0-9_/-]+/gi)) {
@@ -10720,6 +11009,10 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
10720
11009
  const operations = parseOpenApi(body, extension);
10721
11010
  const filename = `openapi-${createHash2("sha256").update(reference.url).digest("hex").slice(0, 12)}.${extension}`;
10722
11011
  assets.push({ path: `openapi/${filename}`, content: body, projectRelative: true });
11012
+ for (const page of pages) {
11013
+ const ref = page.openapi ? splitOpenApiRef(page.openapi) : null;
11014
+ if (ref?.specRef === reference.url) page.openapi = withSpecRef(ref, `openapi/${filename}`);
11015
+ }
10723
11016
  const tab = reference.tabLabel ? tabs.find((candidate) => candidate.tab === reference.tabLabel) : tabs.find((candidate) => candidate.tab.toLowerCase().includes("api"));
10724
11017
  if (tab) tab.api = { source: `openapi/${filename}` };
10725
11018
  else insertApiTab(tabs, { tab: reference.tabLabel ?? "API Reference", ...reference.icon ? { icon: reference.icon } : {}, ...reference.hidden ? { hidden: true } : {}, api: { source: `openapi/${filename}` } }, reference.parentTab);
@@ -10748,6 +11041,7 @@ async function hydrateRemoteApiSpecs(bundle, fetcher) {
10748
11041
  }
10749
11042
  return {
10750
11043
  ...bundle,
11044
+ pages,
10751
11045
  assets,
10752
11046
  docsConfig: { ...bundle.docsConfig, tabs, ...redirects.length > 0 ? { redirects } : {} },
10753
11047
  warnings
@@ -10769,11 +11063,13 @@ export {
10769
11063
  parseGitHubRepositoryUrl,
10770
11064
  parseMarkdownPage,
10771
11065
  projectFernNavigation,
11066
+ projectMintlifyIntegrations,
10772
11067
  projectMintlifyNavigation,
10773
11068
  pruneMissingNavigationPages,
10774
11069
  readFernConfig,
10775
11070
  readMintlifyConfig,
10776
11071
  renderMigrationFiles,
11072
+ validateIntegrations,
10777
11073
  validateMigrationUrl
10778
11074
  };
10779
11075
  //# sourceMappingURL=index.js.map