@thallylabs/migrate 0.2.42 → 0.2.43

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,10 +1,10 @@
1
1
  // src/url-run.ts
2
- import { createHash as createHash4 } from "crypto";
2
+ import { createHash as createHash5 } from "crypto";
3
3
  import { load as load7 } from "cheerio";
4
4
  import { parse as parse2, postprocess, preprocess } from "micromark";
5
- import remarkParse4 from "remark-parse";
5
+ import remarkParse5 from "remark-parse";
6
6
  import remarkMdx4 from "remark-mdx";
7
- import { unified as unified4 } from "unified";
7
+ import { unified as unified5 } from "unified";
8
8
 
9
9
  // src/navigation.ts
10
10
  import { existsSync, lstatSync, readFileSync, realpathSync as realpathSync2 } from "fs";
@@ -497,15 +497,15 @@ function createMigrationAssetCache() {
497
497
  load(url, acquire) {
498
498
  const existing = get(url);
499
499
  if (existing) return existing;
500
- const request = Promise.resolve().then(acquire).then((asset) => {
500
+ const request2 = Promise.resolve().then(acquire).then((asset) => {
501
501
  pending.delete(url);
502
502
  return remember(url, asset);
503
503
  }, (error) => {
504
504
  pending.delete(url);
505
505
  throw error;
506
506
  });
507
- pending.set(url, request);
508
- return request;
507
+ pending.set(url, request2);
508
+ return request2;
509
509
  },
510
510
  delete(url) {
511
511
  settled.delete(url);
@@ -1884,16 +1884,27 @@ function projectMigrationMedia(html, base) {
1884
1884
  }
1885
1885
 
1886
1886
  // src/url-identity.ts
1887
- import { createHash as createHash3 } from "crypto";
1887
+ import { createHash as createHash4 } from "crypto";
1888
1888
  import { load as load6 } from "cheerio";
1889
1889
 
1890
1890
  // src/url.ts
1891
1891
  import { load as load5 } from "cheerio";
1892
1892
  import remarkMdx3 from "remark-mdx";
1893
- import remarkParse3 from "remark-parse";
1893
+ import remarkParse4 from "remark-parse";
1894
1894
  import TurndownService from "turndown";
1895
- import { unified as unified3 } from "unified";
1896
- import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1895
+ import { unified as unified4 } from "unified";
1896
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
1897
+
1898
+ // src/url-openapi.ts
1899
+ import { createHash as createHash2 } from "crypto";
1900
+
1901
+ // src/remote-spec.ts
1902
+ import { lookup } from "dns/promises";
1903
+ import { request } from "https";
1904
+ import { isIP } from "net";
1905
+
1906
+ // src/spec-exclude.ts
1907
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1897
1908
 
1898
1909
  // src/analytics.ts
1899
1910
  var HOSTNAME_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
@@ -1905,8 +1916,8 @@ import { compileSync } from "@mdx-js/mdx";
1905
1916
  import { nameToEmoji } from "gemoji";
1906
1917
  import remarkGfm2 from "remark-gfm";
1907
1918
  import remarkMdx2 from "remark-mdx";
1908
- import remarkParse2 from "remark-parse";
1909
- import { unified as unified2 } from "unified";
1919
+ import remarkParse3 from "remark-parse";
1920
+ import { unified as unified3 } from "unified";
1910
1921
 
1911
1922
  // src/builtin-components.ts
1912
1923
  var THALLY_BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
@@ -2343,13 +2354,13 @@ function unresolvedExpressionNames(expression, pageNames) {
2343
2354
  }
2344
2355
 
2345
2356
  // src/frontmatter.ts
2346
- import { parse as parseYaml } from "yaml";
2357
+ import { parse as parseYaml2 } from "yaml";
2347
2358
  function salvageFrontmatterLines(matter) {
2348
2359
  const data = {};
2349
2360
  for (const line of matter.split(/\r?\n/)) {
2350
2361
  if (!/^[A-Za-z_][\w-]*:\s?/.test(line)) continue;
2351
2362
  try {
2352
- const parsedLine = parseYaml(line);
2363
+ const parsedLine = parseYaml2(line);
2353
2364
  if (parsedLine && typeof parsedLine === "object" && !Array.isArray(parsedLine)) {
2354
2365
  Object.assign(data, parsedLine);
2355
2366
  }
@@ -2373,7 +2384,7 @@ function parseFrontmatter(raw) {
2373
2384
  return { content, data: {} };
2374
2385
  }
2375
2386
  try {
2376
- const parsed = parseYaml(matter);
2387
+ const parsed = parseYaml2(matter);
2377
2388
  return {
2378
2389
  content,
2379
2390
  data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
@@ -2387,13 +2398,103 @@ function parseFrontmatter(raw) {
2387
2398
  }
2388
2399
  }
2389
2400
 
2401
+ // src/fence-meta.ts
2402
+ import remarkParse2 from "remark-parse";
2403
+ import { unified as unified2 } from "unified";
2404
+ function delimitedEnd(meta, start) {
2405
+ const opening = meta[start];
2406
+ if (opening === '"' || opening === "'") {
2407
+ let close = start + 1;
2408
+ while (close < meta.length && meta[close] !== opening) close += meta[close] === "\\" && meta[close + 1] === opening ? 2 : 1;
2409
+ return close < meta.length ? close + 1 : close;
2410
+ }
2411
+ if (opening !== "{") return void 0;
2412
+ let depth = 0;
2413
+ let quote = "";
2414
+ for (let index = start; index < meta.length; index++) {
2415
+ const char = meta[index];
2416
+ if (quote) {
2417
+ if (char === quote) quote = "";
2418
+ } else if (char === '"' || char === "'") quote = char;
2419
+ else if (char === "{") depth++;
2420
+ else if (char === "}" && --depth === 0) return index + 1;
2421
+ }
2422
+ return meta.length;
2423
+ }
2424
+ function tokens(meta) {
2425
+ const result = [];
2426
+ let index = 0;
2427
+ while (index < meta.length) {
2428
+ if (/\s/.test(meta[index])) {
2429
+ index++;
2430
+ continue;
2431
+ }
2432
+ const start = index;
2433
+ const delimited = delimitedEnd(meta, index);
2434
+ if (delimited !== void 0) {
2435
+ result.push({ start, end: delimited, value: meta.slice(start, delimited), quoted: meta[start] !== "{", group: meta[start] === "{" });
2436
+ index = delimited;
2437
+ continue;
2438
+ }
2439
+ const key = /^([A-Za-z][\w-]*)=/.exec(meta.slice(index));
2440
+ if (key) {
2441
+ index += key[0].length;
2442
+ const groupEnd = delimitedEnd(meta, index);
2443
+ if (groupEnd !== void 0) index = groupEnd;
2444
+ else while (index < meta.length && !/\s/.test(meta[index])) index++;
2445
+ result.push({ start, end: index, key: key[1], value: meta.slice(start, index) });
2446
+ continue;
2447
+ }
2448
+ while (index < meta.length && !/\s/.test(meta[index])) {
2449
+ if (index > start && meta[index] === "{" && /^\{[\d,\s-]*\}/.test(meta.slice(index))) break;
2450
+ index++;
2451
+ }
2452
+ result.push({ start, end: index, value: meta.slice(start, index) });
2453
+ }
2454
+ return result;
2455
+ }
2456
+ function normalizeMintlifyCodeFences(body, warn) {
2457
+ const tree = unified2().use(remarkParse2).parse(body);
2458
+ const edits = [];
2459
+ const removed = /* @__PURE__ */ new Set();
2460
+ const unknown = /* @__PURE__ */ new Set();
2461
+ const supported = /* @__PURE__ */ new Set(["title", "filename", "framework", "tag", "icon", "highlight", "focus", "wrap", "lines", "expandable", "nocopy"]);
2462
+ function visit(node) {
2463
+ if (node.type === "code" && node.position?.start.offset !== void 0) {
2464
+ const start = node.position.start.offset;
2465
+ const lineEnd = body.indexOf("\n", start);
2466
+ const end = lineEnd < 0 ? body.length : lineEnd;
2467
+ const line = body.slice(start, end);
2468
+ const opening = /^([`~]{3,}[^\s]*)([ \t]+)(.*?)(\r?)$/.exec(line);
2469
+ if (opening) {
2470
+ const meta = opening[3];
2471
+ const kept = tokens(meta).filter((token) => {
2472
+ if (token.key === "theme" || !token.key && !token.quoted && !token.group && token.value === "twoslash") {
2473
+ removed.add(token.key ?? token.value);
2474
+ return false;
2475
+ }
2476
+ if (token.key && !supported.has(token.key)) unknown.add(token.key);
2477
+ return true;
2478
+ });
2479
+ const normalized = kept.map((token) => meta.slice(token.start, token.end)).join(" ");
2480
+ if (normalized !== meta && kept.length !== tokens(meta).length) edits.push({ start, end, value: `${opening[1]}${normalized ? ` ${normalized}` : ""}${opening[4]}` });
2481
+ }
2482
+ }
2483
+ for (const child of node.children ?? []) visit(child);
2484
+ }
2485
+ visit(tree);
2486
+ if (removed.size) warn?.(`Removed unsupported Mintlify code-fence options: ${[...removed].join(", ")}. Code examples and supported display options were preserved.`);
2487
+ if (unknown.size) warn?.(`Unsupported Mintlify code-fence options are ignored: ${[...unknown].join(", ")}. Review these renderer-specific options.`);
2488
+ return edits.sort((a, b) => b.start - a.start).reduce((text2, edit) => text2.slice(0, edit.start) + edit.value + text2.slice(edit.end), body);
2489
+ }
2490
+
2390
2491
  // src/mdx.ts
2391
2492
  function titleFromId(id) {
2392
2493
  const value = id.split("/").at(-1) ?? id;
2393
2494
  if (value === "introduction") return "Introduction";
2394
2495
  return value.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2395
2496
  }
2396
- var descriptionParser = unified2().use(remarkParse2).use(remarkGfm2).use(remarkMdx2);
2497
+ var descriptionParser = unified3().use(remarkParse3).use(remarkGfm2).use(remarkMdx2);
2397
2498
  function plainText(node) {
2398
2499
  if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
2399
2500
  if (node.type === "image") return node.alt ?? "";
@@ -3408,13 +3509,41 @@ function externalPageUrl(value) {
3408
3509
  }
3409
3510
  function parseMarkdownPage(input) {
3410
3511
  const parsed = parseFrontmatter(input.raw);
3512
+ if (input.platform === "mintlify") {
3513
+ const supported = /* @__PURE__ */ new Set([
3514
+ "title",
3515
+ "description",
3516
+ "sidebarTitle",
3517
+ "navTitle",
3518
+ "icon",
3519
+ "iconType",
3520
+ "tag",
3521
+ "badge",
3522
+ "keywords",
3523
+ "mode",
3524
+ "hidden",
3525
+ "noindex",
3526
+ "url",
3527
+ "openapi",
3528
+ "api",
3529
+ "authMethod",
3530
+ "playground",
3531
+ // Access settings are enforced by the repository classifier, never lost here.
3532
+ "groups",
3533
+ "public",
3534
+ ...PAGE_META_FIELDS.map(([field]) => field)
3535
+ ]);
3536
+ const dropped = Object.keys(parsed.data).filter((key) => !supported.has(key));
3537
+ if (dropped.length) input.warn?.(`Unsupported Mintlify frontmatter fields were omitted: ${dropped.join(", ")}. Review their behavior before publishing.`);
3538
+ }
3411
3539
  const fallbackIdentity = {
3412
3540
  id: input.id,
3413
3541
  navigationId: input.navigationId ?? input.id,
3414
3542
  ...input.locale ? { locale: input.locale } : {}
3415
3543
  };
3416
3544
  const identity = input.resolveIdentity?.(parsed.data, fallbackIdentity) ?? fallbackIdentity;
3417
- const body = removeUndefinedExpressions(injectReactHookImports(normalizeMdx(parsed.content, input.platform)), input.warn).trim();
3545
+ const normalized = normalizeMdx(parsed.content, input.platform);
3546
+ const body = removeUndefinedExpressions(injectReactHookImports(input.platform === "mintlify" ? normalizeMintlifyCodeFences(normalized, input.warn) : normalized), input.warn).trim();
3418
3547
  const keywords = Array.isArray(parsed.data.keywords) ? parsed.data.keywords.filter((value) => typeof value === "string") : [];
3419
3548
  const heading = leadingPageHeading(body);
3420
3549
  const title = typeof parsed.data.title === "string" && parsed.data.title.trim() ? parsed.data.title.trim() : heading?.title ?? titleFromId(identity.navigationId);
@@ -3487,7 +3616,7 @@ function apiFrontmatter(data, warn) {
3487
3616
  }
3488
3617
 
3489
3618
  // src/url-static-content.ts
3490
- import { createHash as createHash2 } from "crypto";
3619
+ import { createHash as createHash3 } from "crypto";
3491
3620
  import { load as load4 } from "cheerio";
3492
3621
  function projectMigrationSvg(html) {
3493
3622
  const $ = load4(html, { xmlMode: true }, false);
@@ -3496,7 +3625,7 @@ function projectMigrationSvg(html) {
3496
3625
  if (svg.attr("xmlns") === void 0) svg.attr("xmlns", "http://www.w3.org/2000/svg");
3497
3626
  const content = Buffer.from($.xml(svg));
3498
3627
  if (!isPassiveSvg(content)) return null;
3499
- const sha256 = createHash2("sha256").update(content).digest("hex");
3628
+ const sha256 = createHash3("sha256").update(content).digest("hex");
3500
3629
  const labelled = (svg.attr("aria-labelledby") ?? "").split(/\s+/).filter(Boolean).map((id) => svg.find("[id]").filter((_index, node) => $(node).attr("id") === id).text().trim()).filter(Boolean);
3501
3630
  const alternative = svg.attr("aria-label")?.trim() || labelled.join(" ") || svg.children("title").text().trim() || svg.children("desc").text().trim() || svg.find("text").text().trim();
3502
3631
  return { asset: { path: `migration-assets/${sha256}.svg`, content }, alternative };
@@ -3609,6 +3738,32 @@ function projectCapturedTabs($, root) {
3609
3738
  }
3610
3739
  }
3611
3740
 
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
+
3612
3767
  // src/url.ts
3613
3768
  var MAX_LOCAL_RESPONSE_BYTES = 8e6;
3614
3769
  var MAX_EMBEDDED_SCHEMA_BYTES = 2e6;
@@ -3688,8 +3843,8 @@ var MINTLIFY_ARTICLE_LABELS = [
3688
3843
  { baseUrl: "Select base URL", permalink: "Navigate to header" },
3689
3844
  { baseUrl: "\u9009\u62E9\u57FA\u7840URL", permalink: "\u5BFC\u822A\u5230\u6807\u9898" }
3690
3845
  ];
3691
- var remoteMarkdownParser = unified3().use(remarkParse3).use(remarkMdx3);
3692
- var embeddedOpenApiParser = unified3().use(remarkParse3);
3846
+ var remoteMarkdownParser = unified4().use(remarkParse4).use(remarkMdx3);
3847
+ var embeddedOpenApiParser = unified4().use(remarkParse4);
3693
3848
  function validateMigrationUrl(value) {
3694
3849
  let url;
3695
3850
  try {
@@ -3729,10 +3884,10 @@ async function readResponseBody(response) {
3729
3884
  }
3730
3885
  return new TextDecoder().decode(body);
3731
3886
  }
3732
- var defaultMigrationFetcher = async (url, request) => {
3887
+ var defaultMigrationFetcher = async (url, request2) => {
3733
3888
  const response = await fetch(url, {
3734
3889
  headers: {
3735
- Accept: request.accept,
3890
+ Accept: request2.accept,
3736
3891
  "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)"
3737
3892
  },
3738
3893
  redirect: "follow",
@@ -4206,7 +4361,7 @@ function extractEmbeddedOpenApi(content) {
4206
4361
  const opening = lines[0].match(/^ {0,3}(`{3,}|~{3,})/);
4207
4362
  const closing = lines.at(-1)?.match(/^ {0,3}(`{3,}|~{3,})[\t ]*$/);
4208
4363
  if (!opening || !closing || opening[1][0] !== closing[1][0] || closing[1].length < opening[1].length) return { content };
4209
- const parsed = parseYaml2(node.value ?? "", { maxAliasCount: 50 });
4364
+ const parsed = parseYaml3(node.value ?? "", { maxAliasCount: 50 });
4210
4365
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { content };
4211
4366
  const document = parsed;
4212
4367
  if (typeof document.openapi !== "string" && typeof document.swagger !== "string") return { content };
@@ -4736,7 +4891,7 @@ function observedMigrationArticleIdentity(document) {
4736
4891
  canonical2.pathname = canonical2.pathname.replace(/\/+$/, "") || "/";
4737
4892
  const removed = [...source.searchParams].filter(([key, value]) => !canonical2.searchParams.getAll(key).includes(value));
4738
4893
  const sectionAnchor = removed.length === 1 && anchors.includes(removed[0][1]) ? removed[0][1] : void 0;
4739
- return { canonicalUrl: canonical2.href, articleSha256: createHash3("sha256").update(payload).digest("hex"), ...sectionAnchor ? { sectionAnchor } : {} };
4894
+ return { canonicalUrl: canonical2.href, articleSha256: createHash4("sha256").update(payload).digest("hex"), ...sectionAnchor ? { sectionAnchor } : {} };
4740
4895
  }
4741
4896
 
4742
4897
  // src/url-api-schema-controls.ts
@@ -4829,8 +4984,8 @@ function apiSchemaTabGroups(body, document, operation) {
4829
4984
  let schema;
4830
4985
  let scope;
4831
4986
  if (title === "body") {
4832
- const request = resolve2(document, selected.requestBody);
4833
- schema = object2(object2(request?.content)?.[media[0]])?.schema;
4987
+ const request2 = resolve2(document, selected.requestBody);
4988
+ schema = object2(object2(request2?.content)?.[media[0]])?.schema;
4834
4989
  scope = `request:${media[0]}`;
4835
4990
  } else if (title === "response") {
4836
4991
  const statuses = heading.find('button[aria-haspopup="menu"]').toArray().map((button) => $(button).text().trim()).filter((text2) => /^(?:[1-5]\d{2}|default)$/.test(text2));
@@ -4929,8 +5084,8 @@ function apiSchemaDisclosureGroups(body, document, operation) {
4929
5084
  schemas = [object2(object2(response?.content)?.[media[0]])?.schema];
4930
5085
  prefix = "response";
4931
5086
  } else if (media.length === 1 && !statuses.length) {
4932
- const request = resolve2(document, selected.requestBody);
4933
- schemas = [object2(object2(request?.content)?.[media[0]])?.schema];
5087
+ const request2 = resolve2(document, selected.requestBody);
5088
+ schemas = [object2(object2(request2?.content)?.[media[0]])?.schema];
4934
5089
  prefix = "body";
4935
5090
  } else if (!media.length && !statuses.length) {
4936
5091
  const head = $(chain[0]).find(".param-head[id]").filter((_index, element) => $(element).closest(fieldSelector)[0] === chain[0]).first().attr("id") ?? "";
@@ -5063,8 +5218,8 @@ function apiOperationCodeControls(body, specification, operation) {
5063
5218
  const metadata = object2(resolveValue(value));
5064
5219
  const declaration = resolveValue(metadata?.openapi);
5065
5220
  if (typeof declaration !== "string") return false;
5066
- const tokens = declaration.trim().split(/\s+/);
5067
- return tokens.at(-2)?.toLowerCase() === operation.method.toLowerCase() && tokens.at(-1) === operation.path;
5221
+ const tokens2 = declaration.trim().split(/\s+/);
5222
+ return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && tokens2.at(-1) === operation.path;
5068
5223
  };
5069
5224
  const active = /* @__PURE__ */ new Set();
5070
5225
  const visit = (input, provider, group, depth, owners = []) => {
@@ -5110,9 +5265,9 @@ function apiOperationCodeControls(body, specification, operation) {
5110
5265
  const hint = object2(resolveValue(scope?.pageMetadata)), metadata = object2(resolveValue(props.pageMetadata));
5111
5266
  const declaration = resolveValue(hint?.openapi);
5112
5267
  if (typeof declaration !== "string" || typeof hint?.href !== "string" || !hint.href || hint.href !== metadata?.href) return false;
5113
- const tokens = declaration.trim().split(/\s+/);
5268
+ const tokens2 = declaration.trim().split(/\s+/);
5114
5269
  const template = (path) => path.split("/").map((segment) => /^\{[^{}\/]+\}$/.test(segment) ? "{}" : segment).join("/");
5115
- return tokens.at(-2)?.toLowerCase() === operation.method.toLowerCase() && template(tokens.at(-1) ?? "") === template(operation.path);
5270
+ return tokens2.at(-2)?.toLowerCase() === operation.method.toLowerCase() && template(tokens2.at(-1) ?? "") === template(operation.path);
5116
5271
  })()) generatedControllers += 1;
5117
5272
  }
5118
5273
  let local = group;
@@ -5221,14 +5376,14 @@ function apiOperationCodeControls(body, specification, operation) {
5221
5376
 
5222
5377
  // src/url-run.ts
5223
5378
  var CONVERTER_VERSION = "url-evidence-v7";
5224
- var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
5379
+ var parser2 = unified5().use(remarkParse5).use(remarkMdx4);
5225
5380
  var ACCEPT = "text/html,application/xhtml+xml";
5226
5381
  var MAX_RESPONSE_BYTES = 8e6;
5227
5382
  var MAX_ASSET_BYTES = 1e7;
5228
5383
  var MAX_RECORDS = 1e5;
5229
5384
  var LOCALES = /* @__PURE__ */ new Set(["ar", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hi", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "pt", "pt-br", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi", "zh", "zh-cn", "zh-hans", "zh-hant"]);
5230
5385
  function hash(value) {
5231
- return createHash4("sha256").update(value).digest("hex");
5386
+ return createHash5("sha256").update(value).digest("hex");
5232
5387
  }
5233
5388
  function canonicalJson(value) {
5234
5389
  const canonical2 = (value2) => Array.isArray(value2) ? value2.map(canonical2) : value2 !== null && typeof value2 === "object" ? Object.fromEntries(Object.entries(value2).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, field]) => [key, canonical2(field)])) : value2;
@@ -5749,7 +5904,7 @@ async function runUrlMigration(options) {
5749
5904
  } catch {
5750
5905
  }
5751
5906
  }
5752
- const capture = (request) => initial && canonical(request.url.href, sourceUrl) === sourceUrl ? initial : acquire(request);
5907
+ const capture = (request2) => initial && canonical(request2.url.href, sourceUrl) === sourceUrl ? initial : acquire(request2);
5753
5908
  const requestedScope = options.scopePath ?? options.checkpoint?.scopePath ?? inferredScope ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
5754
5909
  let scopeEnd = requestedScope.length;
5755
5910
  while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
@@ -6493,16 +6648,16 @@ function rewriteMarkdownDestinations(body, rewrite) {
6493
6648
  }
6494
6649
 
6495
6650
  // src/components.ts
6496
- import { createHash as createHash5 } from "crypto";
6651
+ import { createHash as createHash6 } from "crypto";
6497
6652
  import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
6498
6653
  import { basename, dirname as dirname2, extname as extname3, posix as posix2, relative as relative3, resolve as resolve3 } from "path";
6499
6654
  import postcss from "postcss";
6500
6655
  import selectorParser from "postcss-selector-parser";
6501
6656
  import remarkMdx5 from "remark-mdx";
6502
- import remarkParse5 from "remark-parse";
6657
+ import remarkParse6 from "remark-parse";
6503
6658
  import ts2 from "typescript";
6504
- import { unified as unified5 } from "unified";
6505
- var parser3 = unified5().use(remarkParse5).use(remarkMdx5);
6659
+ import { unified as unified6 } from "unified";
6660
+ var parser3 = unified6().use(remarkParse6).use(remarkMdx5);
6506
6661
  var DOCUSAURUS_THEME_SHIMS = {
6507
6662
  "@theme/CodeBlock": {
6508
6663
  filename: "docusaurus-code-block.tsx",
@@ -6542,7 +6697,7 @@ function applyReplacements(source, replacements) {
6542
6697
  );
6543
6698
  }
6544
6699
  function hash2(value) {
6545
- return createHash5("sha256").update(value).digest("hex").slice(0, 12);
6700
+ return createHash6("sha256").update(value).digest("hex").slice(0, 12);
6546
6701
  }
6547
6702
  function sourceFile(source, filename) {
6548
6703
  return ts2.createSourceFile(filename, source, ts2.ScriptTarget.Latest, true, scriptKindFor(filename));
@@ -6619,6 +6774,32 @@ function renderPage(bundle, page) {
6619
6774
  function isIntegrationsObject(value) {
6620
6775
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6621
6776
  }
6777
+ function mergeRedirects(existing = [], incoming = []) {
6778
+ const redirects = [...existing];
6779
+ const key = (source) => literalRoute(source) ?? trimTrailingSlashes(source).toLowerCase();
6780
+ const destinationKey = (destination) => literalRoute(destinationRoute(destination) ?? "");
6781
+ const sources = new Set(existing.map((redirect) => key(redirect.source)));
6782
+ for (const redirect of incoming) {
6783
+ const source = key(redirect.source);
6784
+ if (sources.has(source)) continue;
6785
+ const visited = /* @__PURE__ */ new Set([source]);
6786
+ let destination = destinationKey(redirect.destination);
6787
+ let cycle = false;
6788
+ while (destination) {
6789
+ if (visited.has(destination)) {
6790
+ cycle = true;
6791
+ break;
6792
+ }
6793
+ visited.add(destination);
6794
+ const next = redirects.find((candidate) => literalRoute(candidate.source) === destination);
6795
+ destination = next ? destinationKey(next.destination) : null;
6796
+ }
6797
+ if (cycle) continue;
6798
+ redirects.push(redirect);
6799
+ sources.add(source);
6800
+ }
6801
+ return redirects;
6802
+ }
6622
6803
  function mergeMigrationConfig(existing, incoming) {
6623
6804
  const tabs = existing.tabs.filter((tab) => tab.tab.toLowerCase() !== "changelog");
6624
6805
  const names = new Set(tabs.map((tab) => tab.tab.toLowerCase()));
@@ -6686,6 +6867,10 @@ function mergeMigrationConfig(existing, incoming) {
6686
6867
  ...incoming,
6687
6868
  ...existing,
6688
6869
  tabs,
6870
+ ...existing.redirects || incoming.redirects ? { redirects: mergeRedirects(existing.redirects, incoming.redirects) } : {},
6871
+ // A starter's primary action must not discard a newly imported logo.
6872
+ // Owner-authored fields, including an explicit null logo, remain authoritative.
6873
+ ...incoming.navbar || existing.navbar ? { navbar: { ...incoming.navbar, ...existing.navbar } } : {},
6689
6874
  // Collection presentation belongs to the imported information
6690
6875
  // architecture; starter defaults must not coerce source dropdowns to tabs.
6691
6876
  navigation: incoming.navigation ?? existing.navigation,