@thallylabs/migrate 0.2.40 → 0.2.42

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-DFnoqEi-.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-DFnoqEi-.js';
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';
3
3
 
4
4
  interface UrlMigrationScope {
5
5
  scopePath: string;
@@ -1,6 +1,7 @@
1
1
  // src/url-run.ts
2
2
  import { createHash as createHash4 } from "crypto";
3
3
  import { load as load7 } from "cheerio";
4
+ import { parse as parse2, postprocess, preprocess } from "micromark";
4
5
  import remarkParse4 from "remark-parse";
5
6
  import remarkMdx4 from "remark-mdx";
6
7
  import { unified as unified4 } from "unified";
@@ -1815,7 +1816,10 @@ var VIDEO_ATTRIBUTES = /* @__PURE__ */ new Set([
1815
1816
  "width",
1816
1817
  "height",
1817
1818
  "preload",
1818
- "aria-label"
1819
+ "aria-label",
1820
+ // Publisher asset provenance does not change playback and is never emitted.
1821
+ // Keep this explicit: arbitrary data attributes may drive custom controls.
1822
+ "data-path"
1819
1823
  ]);
1820
1824
  var YOUTUBE_ATTRIBUTES = /* @__PURE__ */ new Set([
1821
1825
  "src",
@@ -2618,8 +2622,29 @@ ${trimmed.slice(0, marker).trimEnd()}`;
2618
2622
  function headingIdMarker(id) {
2619
2623
  return `{/* #${id} */}`;
2620
2624
  }
2625
+ var HEADING_ID_WORD = /[\p{L}\p{M}\p{N}_+&/@\u2019\u201c\u201d\u2014\u2013\u2192]/u;
2621
2626
  function mintlifyHeadingSlug(text2) {
2622
- return text2.replace(/\u200b/g, "").normalize("NFC").trim().toLowerCase().replace(/[.\s]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").replace(/[^\p{L}\p{M}\p{N}_\-+&/\u2019\u201c\u201d\u2014\u2013\u2192]/gu, "");
2627
+ const chars = [...text2.replace(/[\u200b*]/g, "").normalize("NFC").trim().toLowerCase()];
2628
+ const isWord = (char) => char !== void 0 && HEADING_ID_WORD.test(char);
2629
+ let id = "";
2630
+ let afterDash = false;
2631
+ let endsWithDash = false;
2632
+ chars.forEach((char, index) => {
2633
+ const before = chars[index - 1];
2634
+ const after = chars[index + 1];
2635
+ endsWithDash = false;
2636
+ if (isWord(char)) {
2637
+ id += char;
2638
+ afterDash = false;
2639
+ } else if (/[\s-]/.test(char) || char === "." && isWord(after) || char === "(" && isWord(before) && (isWord(after) || after === "{") || char === ")" && after !== void 0 && !/\s/.test(after)) {
2640
+ if (id && !afterDash) id += "-";
2641
+ afterDash = true;
2642
+ endsWithDash = true;
2643
+ } else if (char === "{" || char === ":" || char === '"') {
2644
+ afterDash = false;
2645
+ }
2646
+ });
2647
+ return endsWithDash ? id.replace(/-$/, "") : id;
2623
2648
  }
2624
2649
  function thallyHeadingSlug(text2) {
2625
2650
  return text2.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/(^-|-$)/g, "");
@@ -2631,7 +2656,7 @@ function typesetQuotes(text2) {
2631
2656
  }).join("");
2632
2657
  }
2633
2658
  function plainHeadingText(source) {
2634
- 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");
2659
+ 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");
2635
2660
  }
2636
2661
  function markMintlifyHeadings(body) {
2637
2662
  let fence;
@@ -3343,7 +3368,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3343
3368
  }
3344
3369
  if (runMintlify) rewritten = normalizeMintlifyParameterAnchors(rewritten);
3345
3370
  rewritten = replaceOutsideCode(rewritten, (segment) => {
3346
- 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(/<Check(\s[^>]*)?>/g, "<Note$1>").replace(/<\/Check>/g, "</Note>").replace(/<Tree\.Folder/g, "<Folder").replace(/<\/Tree\.Folder>/g, "</Folder>").replace(/<Tree\.File/g, "<File").replace(/<\/Tree\.File>/g, "</File>");
3371
+ 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>");
3347
3372
  if (runDocusaurus) {
3348
3373
  result = result.replace(/<TabItem\b([^<>]*)>/g, (_match, attributes) => {
3349
3374
  const title = attributes.match(/\blabel=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? attributes.match(/\bvalue=(?:"([^"]*)"|'([^']*)')/)?.slice(1).find(Boolean) ?? "Tab";
@@ -3352,7 +3377,7 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3352
3377
  result = normalizeDocusaurusLinkTags(result).replace(/<(?:DocCardList|TOCInline)\b[^<>]*\/>/g, "");
3353
3378
  }
3354
3379
  if (runMintlify) {
3355
- result = normalizeMintlifyUpdateLabels(result).replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
3380
+ result = normalizeMintlifyUpdateLabels(result).replace(/<FileTree(\s[^>]*)?>/g, "<Tree$1>").replace(/<\/FileTree>/g, "</Tree>").replace(/<GitHub\.Repo/g, "<GitHub").replace(/<\/GitHub\.Repo>/g, "</GitHub>").replace(/<Callout(\s[^<>]*)?>/g, (tag) => tag.replace(/(\s)type=(?:"[^"]*"|'[^']*'|\{[^{}]*\})/, '$1type="info"')).replace(/<Column(\s[^>]*)?>/g, "<div$1>").replace(/<\/Column>/g, "</div>");
3356
3381
  result = pinStepsTitleSize(result);
3357
3382
  }
3358
3383
  if (runFern) {
@@ -3371,6 +3396,16 @@ function normalizeMdx(body, platform, unwrapMdxCodeBlocks = true) {
3371
3396
  }
3372
3397
  return normalizeFlowParagraphContainers(normalized);
3373
3398
  }
3399
+ function externalPageUrl(value) {
3400
+ const url = typeof value === "string" ? value.trim() : "";
3401
+ if (!/^https?:\/\/[^\s]+$/i.test(url)) return void 0;
3402
+ try {
3403
+ new URL(url);
3404
+ return url;
3405
+ } catch {
3406
+ return void 0;
3407
+ }
3408
+ }
3374
3409
  function parseMarkdownPage(input) {
3375
3410
  const parsed = parseFrontmatter(input.raw);
3376
3411
  const fallbackIdentity = {
@@ -3395,6 +3430,7 @@ function parseMarkdownPage(input) {
3395
3430
  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;
3396
3431
  const sourceMode = typeof parsed.data.mode === "string" ? parsed.data.mode : void 0;
3397
3432
  const mode = sourceMode === "frame" ? "wide" : ["default", "wide", "custom", "center", "home"].includes(sourceMode ?? "") ? sourceMode : void 0;
3433
+ const externalUrl = input.platform === "mintlify" ? externalPageUrl(parsed.data.url) : void 0;
3398
3434
  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);
3399
3435
  const page = {
3400
3436
  id: identity.id,
@@ -3410,7 +3446,9 @@ function parseMarkdownPage(input) {
3410
3446
  keywords,
3411
3447
  mode,
3412
3448
  hidden: parsed.data.hidden === true ? true : void 0,
3413
- noindex: parsed.data.noindex === true || parsed.data.noindex === "true" ? true : void 0,
3449
+ // The path of a page that links elsewhere redirects, so it has no business in a sitemap.
3450
+ noindex: parsed.data.noindex === true || parsed.data.noindex === "true" || externalUrl ? true : void 0,
3451
+ ...externalUrl ? { externalUrl } : {},
3414
3452
  ...Object.keys(meta).length > 0 ? { meta } : {},
3415
3453
  openapi: typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0,
3416
3454
  ...apiFrontmatter(parsed.data, input.warn),
@@ -5182,9 +5220,9 @@ function apiOperationCodeControls(body, specification, operation) {
5182
5220
  }
5183
5221
 
5184
5222
  // src/url-run.ts
5185
- var CONVERTER_VERSION = "url-evidence-v6";
5223
+ var CONVERTER_VERSION = "url-evidence-v7";
5186
5224
  var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
5187
- var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
5225
+ var ACCEPT = "text/html,application/xhtml+xml";
5188
5226
  var MAX_RESPONSE_BYTES = 8e6;
5189
5227
  var MAX_ASSET_BYTES = 1e7;
5190
5228
  var MAX_RECORDS = 1e5;
@@ -5214,6 +5252,11 @@ function canonical(value, base) {
5214
5252
  return null;
5215
5253
  }
5216
5254
  }
5255
+ function sameRouteMarkdownRepresentation(alternate, primary) {
5256
+ const target2 = new URL(alternate), source = new URL(primary);
5257
+ const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
5258
+ return target2.origin === source.origin && target2.search === source.search && directory2(target2.pathname) === directory2(source.pathname) && /\.mdx?$/i.test(target2.pathname);
5259
+ }
5217
5260
  function classifyMigrationResponse(document) {
5218
5261
  if (document.status && document.status >= 400) return "unsupported";
5219
5262
  if (/\/(?:llms(?:-full)?|robots)\.txt$|\/sitemap[^/]*\.xml$|\/search(?:\.html)?$/i.test(document.finalUrl.pathname) || /\.(?:json|ya?ml)$/i.test(document.finalUrl.pathname) || /(?:xml|json)/i.test(document.contentType)) return "index";
@@ -5464,7 +5507,7 @@ function resolveMigrationRecord(records, record) {
5464
5507
  }
5465
5508
  return record;
5466
5509
  }
5467
- function navigationConfig(checkpoint, records) {
5510
+ function navigationConfig(checkpoint, records, occurrencePages = /* @__PURE__ */ new Map()) {
5468
5511
  const navigation = checkpoint.navigation;
5469
5512
  const shortcuts = [];
5470
5513
  const collectShortcuts = (nodes) => {
@@ -5489,7 +5532,7 @@ function navigationConfig(checkpoint, records) {
5489
5532
  const project = (nodes) => nodes.flatMap((node) => {
5490
5533
  const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
5491
5534
  const target2 = resolveMigrationRecord(records, record);
5492
- const page = target2?.page;
5535
+ const page = occurrencePages.get(node) ?? target2?.page;
5493
5536
  const children = project(node.children ?? []);
5494
5537
  if (children.length) {
5495
5538
  const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
@@ -5789,8 +5832,19 @@ async function runUrlMigration(options) {
5789
5832
  if (!record.evidence) continue;
5790
5833
  const final = canonical(record.evidence.finalUrl, sourceUrl);
5791
5834
  if (!final) continue;
5792
- if (record.outcome === "imported") enqueueArticleLinks(record.evidence.links, final, record.url);
5793
- else if (record.outcome === "excluded") {
5835
+ if (record.outcome === "imported") {
5836
+ enqueueArticleLinks(record.evidence.links, final, record.url);
5837
+ for (const reference of record.evidence.markdownRepresentations ?? []) {
5838
+ const alternate = canonical(reference, final);
5839
+ if (!alternate || !sameRouteMarkdownRepresentation(alternate, final)) continue;
5840
+ enqueue(alternate, final, "declared-markdown-representation");
5841
+ const sibling = records.get(alternate);
5842
+ if (sibling && sibling.representationOf !== record.url) {
5843
+ sibling.representationOf = record.url;
5844
+ changed.push(sibling);
5845
+ }
5846
+ }
5847
+ } else if (record.outcome === "excluded") {
5794
5848
  if (classifyMigrationResponse({ finalUrl: new URL(final), contentType: record.evidence.contentType, body: "" }) === "index") {
5795
5849
  for (const link of record.evidence.links) enqueue(link, final, record.url);
5796
5850
  }
@@ -5873,6 +5927,7 @@ async function runUrlMigration(options) {
5873
5927
  let claimedId;
5874
5928
  let claimedFinal;
5875
5929
  let markdownRepresentation;
5930
+ const markdownRepresentations = [];
5876
5931
  let exactKey;
5877
5932
  try {
5878
5933
  delete record.reason;
@@ -5965,13 +6020,13 @@ async function runUrlMigration(options) {
5965
6020
  $('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
5966
6021
  const alternate = canonical($(node).attr("href"), final);
5967
6022
  if (!alternate) return;
5968
- const target2 = new URL(alternate), primary = new URL(final);
5969
- const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
5970
- if (target2.origin !== primary.origin || target2.search !== primary.search || directory2(target2.pathname) !== directory2(primary.pathname) || !/\.mdx?$/i.test(target2.pathname)) return;
6023
+ if (!sameRouteMarkdownRepresentation(alternate, final)) return;
5971
6024
  enqueue(alternate, final, "declared-markdown-representation");
5972
6025
  const sibling = records.get(alternate);
6026
+ if (!sibling) return;
5973
6027
  sibling.representationOf = record.url;
5974
6028
  markdownRepresentation ??= alternate;
6029
+ markdownRepresentations.push(alternate);
5975
6030
  changed.push(sibling);
5976
6031
  });
5977
6032
  }
@@ -5984,7 +6039,8 @@ async function runUrlMigration(options) {
5984
6039
  }
5985
6040
  enqueueNavigation(observedNavigation, final);
5986
6041
  }
5987
- if (imported >= maxPages) {
6042
+ const replacesRepresentation = imported >= maxPages && markdownRepresentations.some((url) => records.get(url)?.page);
6043
+ if (imported >= maxPages && !replacesRepresentation) {
5988
6044
  record.outcome = "excluded";
5989
6045
  record.reason = "A discovery endpoint resolved to an article after the selected page limit.";
5990
6046
  return;
@@ -6003,14 +6059,17 @@ async function runUrlMigration(options) {
6003
6059
  const locale = language.locale;
6004
6060
  const navigationId = locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id;
6005
6061
  const identityId = locale ? `${locale}/${navigationId}` : id;
6006
- if (usedIds.has(identityId) && usedIds.get(identityId) !== record.url) {
6062
+ const owner = records.get(usedIds.get(identityId) ?? "");
6063
+ const sharesDeclaredRepresentation = owner && (owner.representationOf === record.url || record.representationOf === owner.url);
6064
+ if (owner && owner !== record && !sharesDeclaredRepresentation) {
6007
6065
  record.outcome = "failed";
6008
6066
  record.reason = `Destination collision for ${identityId}.`;
6009
6067
  return;
6010
6068
  }
6011
- usedIds.set(identityId, record.url);
6069
+ if (!owner || owner === record || owner.representationOf === record.url) usedIds.set(identityId, record.url);
6012
6070
  claimedId = identityId;
6013
6071
  const evidence = evidenceOf(document);
6072
+ if (markdownRepresentations.length) evidence.markdownRepresentations = markdownRepresentations;
6014
6073
  const page = convert(document, id);
6015
6074
  if (!page) {
6016
6075
  record.outcome = "failed";
@@ -6221,6 +6280,7 @@ ${difference}`;
6221
6280
  if (!primary || primary === record) continue;
6222
6281
  const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
6223
6282
  if (!winner) continue;
6283
+ if (winner.page) usedIds.set(winner.page.id, winner.url);
6224
6284
  const alternate = winner === primary ? record : primary;
6225
6285
  if (alternate.outcome === "alias" && alternate.aliasOf === winner.url) continue;
6226
6286
  if (alternate.outcome === "imported") {
@@ -6242,7 +6302,17 @@ ${difference}`;
6242
6302
  settleArticleIdentities();
6243
6303
  while (cursor < pending.length && !options.signal?.aborted && !budgetExhausted && cursor < (options.maxCaptures ?? Infinity)) {
6244
6304
  if (imported >= maxPages) {
6245
- const index = pending.findIndex((record, position) => position >= cursor && isDiscoveryIndex(new URL(record.url), source));
6305
+ const index = pending.findIndex((record, position) => {
6306
+ if (position < cursor || record.outcome !== "pending") return false;
6307
+ const url = new URL(record.url);
6308
+ if (isDiscoveryIndex(url, source)) return true;
6309
+ if (/\.mdx?$/i.test(url.pathname)) return false;
6310
+ return [".md", ".mdx", "/index.md", "/index.mdx"].some((extension) => {
6311
+ const alternate = new URL(url);
6312
+ alternate.pathname = alternate.pathname.replace(/\/index$/i, "") + extension;
6313
+ return records.get(alternate.href)?.outcome === "imported";
6314
+ });
6315
+ });
6246
6316
  if (index < 0) break;
6247
6317
  pending.splice(cursor, 0, pending.splice(index, 1)[0]);
6248
6318
  }
@@ -6266,19 +6336,60 @@ ${difference}`;
6266
6336
  }
6267
6337
  const navigationLabels = /* @__PURE__ */ new Map();
6268
6338
  const navigationIssues = [];
6339
+ const navigationOccurrences = /* @__PURE__ */ new Map();
6269
6340
  const labelPages = (nodes) => {
6270
6341
  for (const node of nodes) {
6271
6342
  const entry = node.url ? records.get(canonical(node.url, sourceUrl) ?? "") : void 0;
6272
6343
  const target2 = resolveMigrationRecord(records, entry);
6273
- if (target2?.page && node.label && !node.children?.length && !entry?.aliasAnchor) {
6274
- const previous = navigationLabels.get(target2.url);
6275
- if (previous && previous !== node.label) navigationIssues.push({ code: "unsupported-component", source: target2.url, message: `This page has multiple source navigation labels (${previous}, ${node.label}); review the shared sidebar title.` });
6276
- else navigationLabels.set(target2.url, node.label);
6344
+ if (entry && target2?.page && node.label && !node.children?.length && !entry.aliasAnchor) {
6345
+ const occurrences = navigationOccurrences.get(target2) ?? [];
6346
+ occurrences.push({ node, entry });
6347
+ navigationOccurrences.set(target2, occurrences);
6277
6348
  }
6278
6349
  labelPages(node.children ?? []);
6279
6350
  }
6280
6351
  };
6281
6352
  labelPages(checkpoint.navigation);
6353
+ const occurrencePages = /* @__PURE__ */ new Map();
6354
+ const navigationAliases = /* @__PURE__ */ new Map();
6355
+ const occupiedPageIds = new Set(checkpoint.records.flatMap((record) => record.page ? [projection.pageId(record.page)] : []));
6356
+ const navigationRedirects = [];
6357
+ for (const [target2, occurrences] of navigationOccurrences) {
6358
+ const final = target2.evidence && canonical(target2.evidence.finalUrl, sourceUrl);
6359
+ const primary = occurrences.find(({ entry }) => entry.url === final) ?? occurrences.find(({ entry }) => entry === target2) ?? occurrences[0];
6360
+ navigationLabels.set(target2.url, primary.node.label);
6361
+ for (const { node, entry } of occurrences) {
6362
+ if (node.label === primary.node.label) continue;
6363
+ const existing = navigationAliases.get(entry.url);
6364
+ if (existing?.page.navTitle === node.label && existing.target === target2) {
6365
+ occurrencePages.set(node, existing.page);
6366
+ continue;
6367
+ }
6368
+ const sourceRoute = new URL(entry.url);
6369
+ const id = inScope(sourceRoute, source, scopePath) ? idForUrl(sourceRoute, scopePath) : null;
6370
+ const locale = projection.canonicalLocale(target2.page);
6371
+ let navigationId = id && (locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id);
6372
+ const legacyPrefix = locale && `${locale.toLowerCase()}-`;
6373
+ if (navigationId && legacyPrefix && projection.navigationId(target2.page) !== target2.page.navigationId && navigationId.toLowerCase().startsWith(legacyPrefix)) navigationId = navigationId.slice(legacyPrefix.length);
6374
+ const page = navigationId ? {
6375
+ ...target2.page,
6376
+ id: locale ? `${locale}/${navigationId}` : navigationId,
6377
+ navigationId,
6378
+ ...locale ? { locale } : {},
6379
+ navTitle: node.label,
6380
+ source: entry.url,
6381
+ noindex: true
6382
+ } : void 0;
6383
+ if (!page || existing || occupiedPageIds.has(projection.pageId(page))) {
6384
+ navigationIssues.push({ code: "unsupported-component", source: target2.url, message: `This page has multiple source navigation labels (${primary.node.label}, ${node.label}); their source routes cannot retain distinct sidebar titles safely.` });
6385
+ continue;
6386
+ }
6387
+ occupiedPageIds.add(projection.pageId(page));
6388
+ navigationAliases.set(entry.url, { page, target: target2 });
6389
+ occurrencePages.set(node, page);
6390
+ navigationRedirects.push({ source: projection.route(page), destination: projection.route(target2.page), permanent: true });
6391
+ }
6392
+ }
6282
6393
  const pages = checkpoint.records.flatMap((record) => record.page ? [{ ...record.page, id: projection.pageId(record.page), navigationId: projection.navigationId(record.page), ...projection.canonicalLocale(record.page) ? { locale: projection.canonicalLocale(record.page) } : {}, ...navigationLabels.has(record.url) ? { navTitle: navigationLabels.get(record.url) } : {}, body: rewriteMarkdownDestinations(record.page.body, (value) => {
6283
6394
  let url;
6284
6395
  try {
@@ -6299,6 +6410,10 @@ ${difference}`;
6299
6410
  const hasBrokenSourceAnchor = Boolean(url.hash && destination?.outcome === "imported" && destination.evidence && !destination.evidence.anchors.includes(anchor));
6300
6411
  return `${route}${hasBrokenSourceAnchor ? "" : effectiveHash}`;
6301
6412
  }) }] : []);
6413
+ const projectedPages = new Map(pages.map((page) => [page.id, page]));
6414
+ for (const { page, target: target2 } of navigationAliases.values()) {
6415
+ pages.push({ ...page, id: projection.pageId(page), body: projectedPages.get(projection.pageId(target2.page)).body });
6416
+ }
6302
6417
  const validation = validateUrlMigration(checkpoint, Boolean(options.signal?.aborted));
6303
6418
  validation.issues.push(...navigationIssues);
6304
6419
  if (navigationIssues.length && validation.status === "passed") validation.status = "partial";
@@ -6319,12 +6434,12 @@ ${difference}`;
6319
6434
  platform: options.platform ?? "generic",
6320
6435
  pages,
6321
6436
  assets: [...assets.values()],
6322
- docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
6437
+ docsConfig: navigationConfig(checkpoint, records, occurrencePages) ?? buildNavigationFromPages(pages),
6323
6438
  warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
6324
6439
  urlMigrationValidation: validation,
6325
6440
  stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
6326
6441
  };
6327
- if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
6442
+ if (projection.redirects.length || navigationRedirects.length) bundle.docsConfig.redirects = [...projection.redirects, ...navigationRedirects];
6328
6443
  if (projection.locales.length) {
6329
6444
  const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
6330
6445
  bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };
@@ -6342,9 +6457,11 @@ function rewriteMarkdownDestinations(body, rewrite) {
6342
6457
  const original = body.slice(start, end);
6343
6458
  const replacement = rewrite(node.url);
6344
6459
  if (replacement === node.url) return null;
6345
- const index = original.lastIndexOf(node.url);
6346
- if (index < 0) return null;
6347
- return { start: start + index, end: start + index + node.url.length, replacement };
6460
+ const destinations = postprocess(parse2().document().write(preprocess()(original, void 0, true))).filter(([event, token]) => event === "enter" && ["resourceDestinationString", "definitionDestinationString", "autolinkProtocol", "autolinkEmail"].includes(token.type));
6461
+ const destination = destinations.at(-1)?.[1];
6462
+ if (!destination) return null;
6463
+ const escaped = replacement.replace(/[\\&()<>]/g, "\\$&").replace(/[\t\n\r ]/g, (character) => encodeURIComponent(character));
6464
+ return { start: start + destination.start.offset, end: start + destination.end.offset, replacement: escaped };
6348
6465
  }).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
6349
6466
  for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
6350
6467
  if (!attribute.name || !["src", "poster", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
@@ -6477,7 +6594,7 @@ function renderPage(bundle, page) {
6477
6594
  page.icon ? `icon: ${yamlString(page.icon)}` : null,
6478
6595
  page.iconType ? `iconType: ${yamlString(page.iconType)}` : null,
6479
6596
  `description: ${yamlString(page.description)}`,
6480
- page.descriptionPlacement === "body" ? "descriptionPlacement: body" : null,
6597
+ page.descriptionPlacement === "body" || page.descriptionPlacement === "operation" ? `descriptionPlacement: ${page.descriptionPlacement}` : null,
6481
6598
  page.badge ? `badge: ${yamlString(page.badge)}` : null,
6482
6599
  page.keywords.length > 0 ? `keywords: [${page.keywords.map(yamlString).join(", ")}]` : null,
6483
6600
  page.mode ? `mode: ${yamlString(page.mode)}` : null,