@thallylabs/migrate 0.2.43 → 0.2.44

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.
@@ -1,5 +1,5 @@
1
- import { h as MigrationCapture } from './url-run-CPzQfjRW.js';
2
- export { a as MigrationAsset, f as MigrationBundle, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, g as MigrationDocsConfig, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, d as MigrationFetcher, o as MigrationJournalAsset, b as MigrationNavigationGroup, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, M as MigrationPage, e as MigrationPlatform, c as MigrationWarning, R as RenderedMigrationFile, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-run-CPzQfjRW.js';
1
+ import { h as MigrationCapture } from './url-run-D-E4dr2h.js';
2
+ export { a as MigrationAsset, f as MigrationBundle, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, g as MigrationDocsConfig, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, d as MigrationFetcher, o as MigrationJournalAsset, b as MigrationNavigationGroup, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, M as MigrationPage, e as MigrationPlatform, c as MigrationWarning, R as RenderedMigrationFile, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-run-D-E4dr2h.js';
3
3
 
4
4
  interface UrlMigrationScope {
5
5
  scopePath: string;
@@ -56,6 +56,32 @@ function playgroundDisplay(value, where, warn) {
56
56
  return void 0;
57
57
  }
58
58
 
59
+ // src/redirects.ts
60
+ function trimTrailingSlashes(route) {
61
+ let end = route.length;
62
+ while (end > 0 && route[end - 1] === "/") end--;
63
+ return route.slice(0, end);
64
+ }
65
+ function exactRoute(route) {
66
+ return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? trimTrailingSlashes(route) || "/" : null;
67
+ }
68
+ function literalRoute(route) {
69
+ return exactRoute(route)?.toLowerCase() ?? null;
70
+ }
71
+ function resolveDotSegments(route) {
72
+ const segments = [];
73
+ for (const segment of route.split("/").slice(1)) {
74
+ const dots = segment.replace(/%2e/gi, ".");
75
+ if (dots === "..") segments.pop();
76
+ else if (dots !== ".") segments.push(segment);
77
+ }
78
+ return `/${segments.join("/")}`;
79
+ }
80
+ function destinationRoute(destination) {
81
+ const route = destination.startsWith("//") ? null : exactRoute(destination.split(/[?#]/, 1)[0]);
82
+ return route === null ? null : resolveDotSegments(route);
83
+ }
84
+
59
85
  // src/navigation.ts
60
86
  function titleCase(value) {
61
87
  return value.split(/[-_]/).map((word) => ["api", "cli", "sdk", "ui"].includes(word.toLowerCase()) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -1270,9 +1296,9 @@ function recoverFlightArticleCode($, root) {
1270
1296
  const projectionBudget = expansionBudget();
1271
1297
  recoverFlightTreeTabs($, root, projectionBudget);
1272
1298
  const renderedHeadings = root.find("h1,h2,h3,h4,h5,h6").toArray().filter((node) => $(node).attr("data-thally-static-disclosure-heading") === void 0);
1273
- const headingText = renderedHeadings.map((node) => labelText($(node).text()));
1299
+ const headingText2 = renderedHeadings.map((node) => labelText($(node).text()));
1274
1300
  const existingCode = root.find("pre").toArray().map((node) => renderedCode($, node));
1275
- if (!headingText.length || !existingCode.length) return;
1301
+ if (!headingText2.length || !existingCode.length) return;
1276
1302
  const prose = root.clone();
1277
1303
  prose.find("pre,button,script,style,nav,footer,aside").remove();
1278
1304
  const renderedProse = labelText(prose.text());
@@ -1285,7 +1311,7 @@ function recoverFlightArticleCode($, root) {
1285
1311
  const anchor = /\s+\[#([A-Za-z\d_.:-]+)\]$/.exec(text2);
1286
1312
  return anchor && $(renderedHeadings[index]).attr("id") === anchor[1] ? text2.slice(0, anchor.index) : text2;
1287
1313
  });
1288
- if (JSON.stringify(headings) !== JSON.stringify(headingText)) continue;
1314
+ if (JSON.stringify(headings) !== JSON.stringify(headingText2)) continue;
1289
1315
  let proseCursor = 0;
1290
1316
  let hasMatchingProse = true;
1291
1317
  for (const paragraph of nodes.filter((node) => node.type === "paragraph" || node.type === "tableCell")) {
@@ -1902,6 +1928,7 @@ import { createHash as createHash2 } from "crypto";
1902
1928
  import { lookup } from "dns/promises";
1903
1929
  import { request } from "https";
1904
1930
  import { isIP } from "net";
1931
+ import { createGunzip } from "zlib";
1905
1932
 
1906
1933
  // src/spec-exclude.ts
1907
1934
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
@@ -1911,6 +1938,7 @@ var HOSTNAME_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
1911
1938
  var HOSTNAME = new RegExp(`^${HOSTNAME_LABEL}(?:\\.${HOSTNAME_LABEL})*$`);
1912
1939
 
1913
1940
  // src/mdx.ts
1941
+ import { analyze } from "eslint-scope";
1914
1942
  import { posix } from "path";
1915
1943
  import { compileSync } from "@mdx-js/mdx";
1916
1944
  import { nameToEmoji } from "gemoji";
@@ -2398,6 +2426,14 @@ function parseFrontmatter(raw) {
2398
2426
  }
2399
2427
  }
2400
2428
 
2429
+ // src/source-styles.ts
2430
+ import postcss from "postcss";
2431
+ import selectorParser from "postcss-selector-parser";
2432
+ import ts2 from "typescript";
2433
+ var CONTENT_CONTAINER = ".thally-docs-prose";
2434
+ var SOURCE_PAGE_ATTRIBUTE = "data-thally-source-page";
2435
+ var SCOPE = `:where(${CONTENT_CONTAINER}, [${SOURCE_PAGE_ATTRIBUTE}])`;
2436
+
2401
2437
  // src/fence-meta.ts
2402
2438
  import remarkParse2 from "remark-parse";
2403
2439
  import { unified as unified2 } from "unified";
@@ -2495,11 +2531,31 @@ function titleFromId(id) {
2495
2531
  return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2496
2532
  }
2497
2533
  var descriptionParser = unified3().use(remarkParse3).use(remarkGfm2).use(remarkMdx2);
2498
- function plainText(node) {
2534
+ function plainText(node, elements) {
2535
+ if (elements && (node.type === "mdxJsxTextElement" || node.type === "mdxJsxFlowElement")) {
2536
+ elements.found = true;
2537
+ return "";
2538
+ }
2499
2539
  if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
2500
2540
  if (node.type === "image") return node.alt ?? "";
2501
2541
  if (node.type === "break") return " ";
2502
- return (node.children ?? []).map(plainText).join("");
2542
+ return (node.children ?? []).map((child) => plainText(child, elements)).join("");
2543
+ }
2544
+ function headingText(content, definitions, elements) {
2545
+ const references = [...content.matchAll(/\[([^\]\n]+)\]/g)].map((match) => definitions?.get(match[1].trim().replace(/\s+/g, " ").toLowerCase()) ?? "").join("\n");
2546
+ try {
2547
+ return plainText(descriptionParser.parse(`# ${content}
2548
+
2549
+ ${references}`), elements).trim();
2550
+ } catch {
2551
+ return content;
2552
+ }
2553
+ }
2554
+ function mdxTextWithoutInlineElements(content, definitions) {
2555
+ if (!/<[A-Za-z]/.test(content)) return void 0;
2556
+ const elements = { found: false };
2557
+ const text2 = headingText(content, definitions, elements);
2558
+ return elements.found && /[\p{L}\p{M}\p{N}]/u.test(text2) ? text2 : void 0;
2503
2559
  }
2504
2560
  function firstParagraph(content) {
2505
2561
  function findParagraph(node) {
@@ -2519,6 +2575,23 @@ function firstParagraph(content) {
2519
2575
  return "";
2520
2576
  }
2521
2577
  }
2578
+ var linkParser = unified3().use(remarkParse3);
2579
+ function withoutMarkdownLinks(text2) {
2580
+ const links = [];
2581
+ const collect = (node) => {
2582
+ if (node.type === "link") {
2583
+ const first = node.children?.[0]?.position?.start.offset;
2584
+ const last = node.children?.at(-1)?.position?.end.offset;
2585
+ const start = node.position?.start.offset;
2586
+ const end = node.position?.end.offset;
2587
+ if (start !== void 0 && end !== void 0) links.push({ start, end, textStart: first ?? start, textEnd: last ?? start });
2588
+ return;
2589
+ }
2590
+ for (const child of node.children ?? []) collect(child);
2591
+ };
2592
+ collect(linkParser.parse(text2));
2593
+ return links.reduceRight((result, link) => result.slice(0, link.start) + result.slice(link.textStart, link.textEnd) + result.slice(link.end), text2);
2594
+ }
2522
2595
  function docusaurusAdmonitionTag(kind) {
2523
2596
  if (kind === "danger") return "Error";
2524
2597
  if (kind === "info") return "Info";
@@ -2723,9 +2796,9 @@ ${trimmed.slice(0, marker).trimEnd()}`;
2723
2796
  function headingIdMarker(id) {
2724
2797
  return `{/* #${id} */}`;
2725
2798
  }
2726
- var HEADING_ID_WORD = /[\p{L}\p{M}\p{N}_+&/@\u2019\u201c\u201d\u2014\u2013\u2192]/u;
2799
+ var HEADING_ID_WORD = /[\p{L}\p{M}\p{N}_+&/@%=<>|\u2019\u201c\u201d\u2014\u2013\u2192\u2026]/u;
2727
2800
  function mintlifyHeadingSlug(text2) {
2728
- const chars = [...text2.replace(/[\u200b*]/g, "").normalize("NFC").trim().toLowerCase()];
2801
+ const chars = [...text2.replace(/[\u200b*]/g, "").replace(/(?<=[\p{L}\p{M}\p{N}_])\(s\)(?![\p{L}\p{M}\p{N}_])/gu, "s").normalize("NFC").trim().toLowerCase()];
2729
2802
  const isWord = (char) => char !== void 0 && HEADING_ID_WORD.test(char);
2730
2803
  let id = "";
2731
2804
  let afterDash = false;
@@ -2737,11 +2810,11 @@ function mintlifyHeadingSlug(text2) {
2737
2810
  if (isWord(char)) {
2738
2811
  id += char;
2739
2812
  afterDash = false;
2740
- } else if (/[\s-]/.test(char) || char === "." && isWord(after) || char === "(" && isWord(before) && (isWord(after) || after === "{") || char === ")" && after !== void 0 && !/\s/.test(after)) {
2813
+ } else if (/[\s-]/.test(char) || char === "." && (isWord(after) || after === ",") || char === "(" && isWord(before) && (isWord(after) || after === "{") || char === ")" && after !== void 0 && !/\s/.test(after)) {
2741
2814
  if (id && !afterDash) id += "-";
2742
2815
  afterDash = true;
2743
2816
  endsWithDash = true;
2744
- } else if (char === "{" || char === ":" || char === '"') {
2817
+ } else if (char === "{" || char === ":" || char === '"' || char === ",") {
2745
2818
  afterDash = false;
2746
2819
  }
2747
2820
  });
@@ -2751,17 +2824,77 @@ function thallyHeadingSlug(text2) {
2751
2824
  return text2.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/(^-|-$)/g, "");
2752
2825
  }
2753
2826
  function typesetQuotes(text2) {
2827
+ const opensClosed = text2.startsWith('"') && !text2.trimEnd().endsWith('"');
2754
2828
  return text2.split(/(`[^`]*`|<[^>]*>|\]\([^)]*\))/).map((part, index) => {
2755
2829
  if (index % 2) return part;
2756
- return part.replace(/(^|[\s([{\u2014\u2013])"/g, "$1\u201C").replace(/"/g, "\u201D").replace(/(^|[\s([{\u2014\u2013\u201c])'(?=\S)/g, "$1\u2018").replace(/'/g, "\u2019");
2830
+ return part.replace(/(^|[\s([{\u2014\u2013])"/g, (match, before) => opensClosed && index === 0 && before === "" ? match : `${before}\u201C`).replace(/"/g, "\u201D").replace(/(^|[\s([{\u2014\u2013\u201c])'(?=\S)(?!&lt;|<)/g, "$1\u2018").replace(/'/g, "\u2019");
2757
2831
  }).join("");
2758
2832
  }
2833
+ function typesetEllipses(text2) {
2834
+ return text2.split(/(`[^`]*`|<[^>]*>|\]\([^)]*\))/).map((part, index) => index % 2 ? part : part.replace(/\.{3}/g, "\u2026")).join("");
2835
+ }
2759
2836
  function plainHeadingText(source) {
2760
- return source.replace(/\{\/\*[\s\S]*?\*\/\}/g, " ").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/`([^`]*)`/g, "$1").replace(/(?<!\\)(\*\*|__|~~|\*)/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1");
2837
+ const unlinked = !source.includes("`") ? source : source.replace(/(`[^`]*`)|!?\[((?:`[^`]*`|[^\]`])*)\]\([^)]*\)/g, (_match, code, text2 = "") => code ?? text2);
2838
+ return unlinked.split(/(`[^`]*`)/).map((part, index) => index % 2 ? part.slice(1, -1) : part.replace(/\{\/\*[\s\S]*?\*\/\}/g, " ").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<\/?[A-Za-z][^>]*>/g, "").replace(/(?<!\\)(\*\*|__|~~|\*)/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/\\([\\`*_{}[\]()#+.!&<>|~-])/g, "$1")).join("");
2839
+ }
2840
+ function staticAttributeString(value) {
2841
+ if (typeof value === "string") return value;
2842
+ const expression = value?.value;
2843
+ if (typeof expression !== "string") return void 0;
2844
+ return expression.match(/^\s*(?:"([^"\\]*)"|'([^'\\]*)'|`((?:[^`\\$]|\$(?!\{))*)`)\s*$/)?.slice(1).find((part) => part !== void 0);
2845
+ }
2846
+ function pageIdClaims(body) {
2847
+ let tree;
2848
+ try {
2849
+ tree = descriptionParser.parse(body);
2850
+ } catch {
2851
+ return { free: () => false };
2852
+ }
2853
+ const owners = /* @__PURE__ */ new Map();
2854
+ const headingLines = /* @__PURE__ */ new Set();
2855
+ let claims = 0;
2856
+ const root = (id) => id.toLowerCase().replace(/-\d+$/, "");
2857
+ const claim = (owner, ...ids) => {
2858
+ for (const id of ids) {
2859
+ const key = root(id);
2860
+ const held = owners.get(key);
2861
+ if (!key) continue;
2862
+ if (!held) owners.set(key, { owner, shared: false });
2863
+ else if (held.owner !== owner) held.shared = true;
2864
+ }
2865
+ };
2866
+ const visit = (node) => {
2867
+ const line = node.position?.start.line ?? 0;
2868
+ if (node.type === "heading" && (node.depth ?? 0) >= 2) {
2869
+ headingLines.add(line);
2870
+ const text2 = typesetQuotes(plainText(node));
2871
+ claim(`heading ${line}`, mintlifyHeadingSlug(text2), thallyHeadingSlug(text2), thallyHeadingSlug(plainText(node, { found: false })));
2872
+ for (const child of node.children ?? []) {
2873
+ const authored = child.type === "mdxTextExpression" ? child.value?.match(/^\s*\/\*\s*#(\S+?)\s*\*\/\s*$/)?.[1] : void 0;
2874
+ if (authored) claim(`claim ${claims++}`, authored);
2875
+ }
2876
+ } else if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
2877
+ for (const { type, name, value } of node.attributes ?? []) {
2878
+ const text2 = type === "mdxJsxAttribute" ? staticAttributeString(value) : void 0;
2879
+ if (text2 === void 0) continue;
2880
+ if (name === "id") claim(`claim ${claims++}`, text2);
2881
+ else if (name === "title") claim(`claim ${claims++}`, mintlifyHeadingSlug(text2), thallyHeadingSlug(text2));
2882
+ }
2883
+ }
2884
+ for (const child of node.children ?? []) visit(child);
2885
+ };
2886
+ visit(tree);
2887
+ return {
2888
+ free: (id, line) => {
2889
+ const held = owners.get(root(id));
2890
+ return headingLines.has(line) && (!held || held.owner === `heading ${line}` && !held.shared);
2891
+ }
2892
+ };
2761
2893
  }
2762
2894
  function markMintlifyHeadings(body) {
2763
2895
  let fence;
2764
- return body.split("\n").map((line) => {
2896
+ let claims;
2897
+ return body.split("\n").map((line, index) => {
2765
2898
  const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
2766
2899
  if (marker) {
2767
2900
  if (!fence) fence = marker[1];
@@ -2775,8 +2908,13 @@ function markMintlifyHeadings(body) {
2775
2908
  const source = typesetQuotes(explicit ? explicit[1] : heading[2]);
2776
2909
  if (explicit) return `${heading[1]}${source}${explicit[2]}`;
2777
2910
  const text2 = plainHeadingText(source);
2778
- const id = mintlifyHeadingSlug(text2);
2779
- return `${heading[1]}${source}${id && id !== thallyHeadingSlug(text2) ? ` ${headingIdMarker(id)}` : ""}`;
2911
+ const id = mintlifyHeadingSlug(plainHeadingText(typesetEllipses(source)));
2912
+ let marked = id !== thallyHeadingSlug(text2);
2913
+ if (id && !marked) {
2914
+ const visible = mdxTextWithoutInlineElements(source);
2915
+ if (visible !== void 0 && id !== thallyHeadingSlug(visible)) marked = (claims ??= pageIdClaims(body)).free(id, index + 1);
2916
+ }
2917
+ return `${heading[1]}${source}${id && marked ? ` ${headingIdMarker(id)}` : ""}`;
2780
2918
  }).join("\n");
2781
2919
  }
2782
2920
  var GLOBAL_DOCUSARUS_COMPONENTS = /* @__PURE__ */ new Set([
@@ -2792,6 +2930,7 @@ function removeGlobalDocusaurusImports(body) {
2792
2930
  (statement, component) => GLOBAL_DOCUSARUS_COMPONENTS.has(component) ? "" : statement
2793
2931
  ));
2794
2932
  }
2933
+ var PLAIN_TEXT = new RegExp("^\\p{L}(?:[\\p{L}\\p{N} ,.;'?%$=+-]*[\\p{L}\\p{N}.])?$", "u");
2795
2934
  function leadingPageHeading(body) {
2796
2935
  if (!/^ {0,3}(?:#(?:[ \t]|$)|=+[ \t\r]*$)/m.test(body)) return void 0;
2797
2936
  try {
@@ -2904,6 +3043,27 @@ function normalizeFernFileTrees(body) {
2904
3043
  }
2905
3044
  ));
2906
3045
  }
3046
+ function unwrapHtmlCommentInMdxComment(body) {
3047
+ return replaceOutsideCode(body, (text2) => {
3048
+ const opener = /\{\s*\/\*\s*<!--/g;
3049
+ const closer = /\s*\*\/\s*\}/y;
3050
+ let out = "";
3051
+ let copied = 0;
3052
+ let dash = -2;
3053
+ let star = -2;
3054
+ for (let found = opener.exec(text2); found; found = opener.exec(text2)) {
3055
+ const inner = opener.lastIndex;
3056
+ if (dash !== -1 && dash < inner) dash = text2.indexOf("-->", inner);
3057
+ if (dash === -1) break;
3058
+ if (star !== -1 && star < inner) star = text2.indexOf("*/", inner);
3059
+ closer.lastIndex = dash + 3;
3060
+ if (star !== -1 && star < dash || !closer.test(text2)) continue;
3061
+ out += `${text2.slice(copied, found.index)}{/*${text2.slice(inner, dash)}*/}`;
3062
+ copied = opener.lastIndex = closer.lastIndex;
3063
+ }
3064
+ return out + text2.slice(copied);
3065
+ });
3066
+ }
2907
3067
  function normalizeFernCallouts(body) {
2908
3068
  return replaceOutsideCode(body, (whole) => {
2909
3069
  const openTags = [];
@@ -3468,6 +3628,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3468
3628
  rewritten = rewritten.replace(/^\n{2,}/, "\n");
3469
3629
  }
3470
3630
  if (runMintlify) rewritten = normalizeMintlifyParameterAnchors(rewritten);
3631
+ rewritten = unwrapHtmlCommentInMdxComment(rewritten);
3471
3632
  rewritten = replaceOutsideCode(rewritten, (segment) => {
3472
3633
  let result = convertHtmlStyleAttributes(segment).replace(/<!--([\s\S]*?)-->/g, (_match, content) => mdxComment(content)).replace(/<Danger(\s[^>]*)?>/g, "<Error$1>").replace(/<\/Danger>/g, "</Error>").replace(/<Warn(\s[^>]*)?>/g, "<Warning$1>").replace(/<\/Warn>/g, "</Warning>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
3473
3634
  if (runDocusaurus) {
@@ -3497,8 +3658,15 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3497
3658
  }
3498
3659
  return normalizeFlowParagraphContainers(normalized);
3499
3660
  }
3661
+ var SITE_PATH_LINK = /^\/(?:[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*\/?)?(?:\?(?:[A-Za-z0-9._~=&-]|%[0-9A-Fa-f]{2})*)?(?:#[A-Za-z0-9._~-]+)?$/;
3662
+ var MAX_SITE_PATH_LINK_LENGTH = 2e3;
3663
+ function sitePathLink(url) {
3664
+ if (url.length > MAX_SITE_PATH_LINK_LENGTH || url.includes("__ESC_") || !SITE_PATH_LINK.test(url)) return void 0;
3665
+ return url.split(/[?#]/, 1)[0].split("/").some((segment) => segment === "." || segment === "..") ? void 0 : url;
3666
+ }
3500
3667
  function externalPageUrl(value) {
3501
3668
  const url = typeof value === "string" ? value.trim() : "";
3669
+ if (url.startsWith("/")) return sitePathLink(url);
3502
3670
  if (!/^https?:\/\/[^\s]+$/i.test(url)) return void 0;
3503
3671
  try {
3504
3672
  new URL(url);
@@ -3546,7 +3714,9 @@ function parseMarkdownPage(input) {
3546
3714
  const body = removeUndefinedExpressions(injectReactHookImports(input.platform === "mintlify" ? normalizeMintlifyCodeFences(normalized, input.warn) : normalized), input.warn).trim();
3547
3715
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
3548
3716
  const heading = leadingPageHeading(body);
3549
- const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title ?? titleFromId(identity.navigationId);
3717
+ const authoredTitle = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title;
3718
+ const title = authoredTitle ?? titleFromId(identity.navigationId);
3719
+ const namesOperation = typeof parsed.data.openapi === "string" && Boolean(parsed.data.openapi.trim());
3550
3720
  const sidebarTitle = input.platform === "docusaurus" ? parsed.data.sidebar_label : parsed.data.sidebarTitle;
3551
3721
  const navTitle = typeof sidebarTitle === "string" && sidebarTitle.trim() ? sidebarTitle.trim() : typeof parsed.data.navTitle === "string" && parsed.data.navTitle.trim() ? parsed.data.navTitle.trim() : void 0;
3552
3722
  const icon = typeof parsed.data.icon === "string" && parsed.data.icon.trim() ? parsed.data.icon.trim() : void 0;
@@ -3559,18 +3729,25 @@ function parseMarkdownPage(input) {
3559
3729
  const badge = typeof parsed.data.tag === "string" && parsed.data.tag.trim() ? parsed.data.tag.trim() : typeof parsed.data.badge === "string" && parsed.data.badge.trim() ? parsed.data.badge.trim() : void 0;
3560
3730
  const sourceMode = typeof parsed.data.mode === "string" ? parsed.data.mode : void 0;
3561
3731
  const mode = sourceMode === "frame" ? "wide" : ["default", "wide", "custom", "center", "home"].includes(sourceMode ?? "") ? sourceMode : void 0;
3732
+ if (input.platform === "mintlify" && typeof parsed.data.url === "string" && parsed.data.url.trim().startsWith("/") && !externalPageUrl(parsed.data.url)) {
3733
+ const shown = parsed.data.url.trim();
3734
+ input.warn?.(`Page "${identity.id}": its "url" frontmatter ${JSON.stringify(shown.length > 80 ? `${shown.slice(0, 80)}...` : shown)} was not turned into a link, so the page was kept with its own content. Only a site path made of letters, digits and . _ ~ - segments, with an optional ?key=value query and #id fragment, can become a link.`);
3735
+ }
3562
3736
  const externalUrl = input.platform === "mintlify" ? externalPageUrl(parsed.data.url) : void 0;
3563
- const description = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : typeof parsed.data.openapi === "string" && parsed.data.openapi.trim() ? "" : firstParagraph(body);
3737
+ const authoredDescription = typeof parsed.data.description === "string" && parsed.data.description.trim() ? parsed.data.description.trim() : void 0;
3738
+ const description = authoredDescription ?? (namesOperation ? "" : firstParagraph(body));
3564
3739
  const page = {
3565
3740
  id: identity.id,
3566
3741
  navigationId: identity.navigationId,
3567
3742
  locale: identity.locale,
3568
3743
  title,
3744
+ ...authoredTitle === void 0 && namesOperation ? { titleFromFileName: true } : {},
3569
3745
  navTitle,
3570
3746
  icon,
3571
3747
  iconType,
3572
3748
  description,
3573
- ...input.platform === "docusaurus" || input.platform === "fern" ? { descriptionPlacement: "body" } : {},
3749
+ // A description taken from the body is the paragraph the body already shows, so it must not also appear under the title.
3750
+ ...input.platform === "docusaurus" || input.platform === "fern" || authoredDescription === void 0 && description !== "" ? { descriptionPlacement: "body" } : {},
3574
3751
  badge,
3575
3752
  keywords,
3576
3753
  mode,
@@ -3585,6 +3762,12 @@ function parseMarkdownPage(input) {
3585
3762
  source: input.source,
3586
3763
  ...parsed.error ? { frontmatterError: parsed.error } : {}
3587
3764
  };
3765
+ const writtenDescription = typeof parsed.data.description === "string" ? parsed.data.description.trim() : "";
3766
+ const plainDescription2 = withoutMarkdownLinks(writtenDescription);
3767
+ if (plainDescription2 !== writtenDescription) {
3768
+ page.description = plainDescription2;
3769
+ input.warn?.(`Page "${identity.id}": its description had links, which Thally shows as plain text; the link text was kept.`);
3770
+ }
3588
3771
  resolvePageDisplayHeading(page, input.platform);
3589
3772
  return page;
3590
3773
  }
@@ -3738,32 +3921,6 @@ function projectCapturedTabs($, root) {
3738
3921
  }
3739
3922
  }
3740
3923
 
3741
- // src/redirects.ts
3742
- function trimTrailingSlashes(route) {
3743
- let end = route.length;
3744
- while (end > 0 && route[end - 1] === "/") end--;
3745
- return route.slice(0, end);
3746
- }
3747
- function exactRoute(route) {
3748
- return route.startsWith("/") && !/[?:*#()[\]{}]/.test(route) ? trimTrailingSlashes(route) || "/" : null;
3749
- }
3750
- function literalRoute(route) {
3751
- return exactRoute(route)?.toLowerCase() ?? null;
3752
- }
3753
- function resolveDotSegments(route) {
3754
- const segments = [];
3755
- for (const segment of route.split("/").slice(1)) {
3756
- const dots = segment.replace(/%2e/gi, ".");
3757
- if (dots === "..") segments.pop();
3758
- else if (dots !== ".") segments.push(segment);
3759
- }
3760
- return `/${segments.join("/")}`;
3761
- }
3762
- function destinationRoute(destination) {
3763
- const route = destination.startsWith("//") ? null : exactRoute(destination.split(/[?#]/, 1)[0]);
3764
- return route === null ? null : resolveDotSegments(route);
3765
- }
3766
-
3767
3924
  // src/url.ts
3768
3925
  var MAX_LOCAL_RESPONSE_BYTES = 8e6;
3769
3926
  var MAX_EMBEDDED_SCHEMA_BYTES = 2e6;
@@ -4822,6 +4979,8 @@ ${fence}
4822
4979
  navigationId: id,
4823
4980
  title,
4824
4981
  description: description || plainDescription(body) || `Documentation imported from ${document.finalUrl.hostname}.`,
4982
+ // A description taken from the page text is already in the body; only one the site published is shown under the title.
4983
+ ...description ? {} : { descriptionPlacement: "body" },
4825
4984
  keywords: [],
4826
4985
  body,
4827
4986
  source: document.finalUrl.toString()
@@ -6651,11 +6810,11 @@ function rewriteMarkdownDestinations(body, rewrite) {
6651
6810
  import { createHash as createHash6 } from "crypto";
6652
6811
  import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
6653
6812
  import { basename, dirname as dirname2, extname as extname3, posix as posix2, relative as relative3, resolve as resolve3 } from "path";
6654
- import postcss from "postcss";
6655
- import selectorParser from "postcss-selector-parser";
6813
+ import postcss2 from "postcss";
6814
+ import selectorParser2 from "postcss-selector-parser";
6656
6815
  import remarkMdx5 from "remark-mdx";
6657
6816
  import remarkParse6 from "remark-parse";
6658
- import ts2 from "typescript";
6817
+ import ts3 from "typescript";
6659
6818
  import { unified as unified6 } from "unified";
6660
6819
  var parser3 = unified6().use(remarkParse6).use(remarkMdx5);
6661
6820
  var DOCUSAURUS_THEME_SHIMS = {
@@ -6700,29 +6859,29 @@ function hash2(value) {
6700
6859
  return createHash6("sha256").update(value).digest("hex").slice(0, 12);
6701
6860
  }
6702
6861
  function sourceFile(source, filename) {
6703
- return ts2.createSourceFile(filename, source, ts2.ScriptTarget.Latest, true, scriptKindFor(filename));
6862
+ return ts3.createSourceFile(filename, source, ts3.ScriptTarget.Latest, true, scriptKindFor(filename));
6704
6863
  }
6705
6864
  function mergeComponentRegistry(existing, incoming) {
6706
6865
  const fingerprint = hash2(incoming);
6707
6866
  const name = `MigratedRegistry${fingerprint}`;
6708
6867
  const registryPath = `./migrated-components-${fingerprint}`;
6709
6868
  const source = sourceFile(existing, "custom-components.tsx");
6710
- const alreadyImported = source.statements.some((statement) => ts2.isImportDeclaration(statement) && ts2.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
6869
+ const alreadyImported = source.statements.some((statement) => ts3.isImportDeclaration(statement) && ts3.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === registryPath);
6711
6870
  let initializer;
6712
6871
  for (const statement of source.statements) {
6713
- if (!ts2.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword)) continue;
6872
+ if (!ts3.isVariableStatement(statement) || !statement.modifiers?.some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword)) continue;
6714
6873
  for (const declaration of statement.declarationList.declarations) {
6715
- if (ts2.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
6874
+ if (ts3.isIdentifier(declaration.name) && declaration.name.text === "customComponents") initializer = declaration.initializer;
6716
6875
  }
6717
6876
  }
6718
- while (initializer && (ts2.isAsExpression(initializer) || ts2.isSatisfiesExpression(initializer) || ts2.isParenthesizedExpression(initializer))) initializer = initializer.expression;
6719
- if (!initializer || !ts2.isObjectLiteralExpression(initializer)) {
6877
+ while (initializer && (ts3.isAsExpression(initializer) || ts3.isSatisfiesExpression(initializer) || ts3.isParenthesizedExpression(initializer))) initializer = initializer.expression;
6878
+ if (!initializer || !ts3.isObjectLiteralExpression(initializer)) {
6720
6879
  throw new Error("Cannot safely merge src/mdx/custom-components.tsx: export customComponents as an object literal before importing components. The existing registry was preserved.");
6721
6880
  }
6722
6881
  const content = alreadyImported ? existing : applyReplacements(existing, [
6723
6882
  {
6724
- start: source.statements.filter((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
6725
- end: source.statements.filter((statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
6883
+ start: source.statements.filter((statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
6884
+ end: source.statements.filter((statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression)).at(-1)?.end ?? 0,
6726
6885
  value: `
6727
6886
  import { customComponents as ${name} } from ${JSON.stringify(registryPath)}
6728
6887
  `