@thallylabs/migrate 0.2.37 → 0.2.39

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
@@ -4462,9 +4462,9 @@ function jsonPointer(root, pointer) {
4462
4462
  const index = Number(token);
4463
4463
  return Number.isSafeInteger(index) && index >= 0 ? current[index] : void 0;
4464
4464
  }
4465
- const object2 = objectValue2(current);
4466
- if (!object2) return void 0;
4467
- return object2[token.replace(/~1/g, "/").replace(/~0/g, "~")];
4465
+ const object3 = objectValue2(current);
4466
+ if (!object3) return void 0;
4467
+ return object3[token.replace(/~1/g, "/").replace(/~0/g, "~")];
4468
4468
  }, root);
4469
4469
  }
4470
4470
  function readMintlifyJson(path, repositoryRoot) {
@@ -4483,22 +4483,22 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
4483
4483
  if (Array.isArray(value)) {
4484
4484
  return value.map((entry) => resolveJsonReferences(entry, currentFile, repositoryRoot, stack));
4485
4485
  }
4486
- const object2 = objectValue2(value);
4487
- if (!object2) return value;
4488
- if (typeof object2.$ref === "string") {
4489
- const [filePart, pointer = ""] = object2.$ref.split("#", 2);
4486
+ const object3 = objectValue2(value);
4487
+ if (!object3) return value;
4488
+ if (typeof object3.$ref === "string") {
4489
+ const [filePart, pointer = ""] = object3.$ref.split("#", 2);
4490
4490
  const referencedFile = filePart ? resolveWithin(dirname2(currentFile), filePart) : currentFile;
4491
4491
  const relativePath = relative3(repositoryRoot, referencedFile);
4492
4492
  resolveWithin(repositoryRoot, relativePath);
4493
4493
  const stackKey = `${referencedFile}#${pointer}`;
4494
- if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object2.$ref}`);
4494
+ if (stack.has(stackKey)) throw new Error(`Circular Mintlify $ref: ${object3.$ref}`);
4495
4495
  stack.add(stackKey);
4496
4496
  const referencedRoot = readMintlifyJson(referencedFile, repositoryRoot);
4497
4497
  const referenced = jsonPointer(referencedRoot, pointer ? `#${pointer}` : "");
4498
4498
  const resolved = resolveJsonReferences(referenced, referencedFile, repositoryRoot, stack);
4499
4499
  stack.delete(stackKey);
4500
4500
  const siblings = Object.fromEntries(
4501
- Object.entries(object2).filter(([key]) => key !== "$ref").map(([key, entry]) => [
4501
+ Object.entries(object3).filter(([key]) => key !== "$ref").map(([key, entry]) => [
4502
4502
  key,
4503
4503
  resolveJsonReferences(entry, currentFile, repositoryRoot, stack)
4504
4504
  ])
@@ -4506,7 +4506,7 @@ function resolveJsonReferences(value, currentFile, repositoryRoot, stack) {
4506
4506
  return objectValue2(resolved) ? { ...resolved, ...siblings } : resolved;
4507
4507
  }
4508
4508
  return Object.fromEntries(
4509
- Object.entries(object2).map(([key, entry]) => [
4509
+ Object.entries(object3).map(([key, entry]) => [
4510
4510
  key,
4511
4511
  resolveJsonReferences(entry, currentFile, repositoryRoot, stack)
4512
4512
  ])
@@ -4529,10 +4529,10 @@ function listedOperations(container) {
4529
4529
  if (OPENAPI_OPERATION_REF.test(entry)) operations.push(entry.trim());
4530
4530
  return;
4531
4531
  }
4532
- const object2 = objectValue2(entry);
4533
- if (!object2 || depth > SWEEP_DEPTH_LIMIT || depth > 0 && (specSource(object2.openapi) || specSource(object2.asyncapi))) return;
4532
+ const object3 = objectValue2(entry);
4533
+ if (!object3 || depth > SWEEP_DEPTH_LIMIT || depth > 0 && (specSource(object3.openapi) || specSource(object3.asyncapi))) return;
4534
4534
  for (const key of ["groups", "pages"]) {
4535
- if (Array.isArray(object2[key])) object2[key].forEach((child) => visit(child, depth + 1));
4535
+ if (Array.isArray(object3[key])) object3[key].forEach((child) => visit(child, depth + 1));
4536
4536
  }
4537
4537
  };
4538
4538
  visit(container, 0);
@@ -4545,8 +4545,8 @@ var operationKey = (entry) => entry.trim().replace(/^(\S+)\s+/, "$1 ");
4545
4545
  function operationGroups(container, maxLevel = API_GROUPS_MAX_DEPTH) {
4546
4546
  const entries = (node, level) => ["groups", "pages"].flatMap((key) => (Array.isArray(node[key]) ? node[key] : []).flatMap((entry) => {
4547
4547
  if (typeof entry === "string") return OPENAPI_OPERATION_REF.test(entry) ? [operationKey(entry)] : [];
4548
- const object2 = objectValue2(entry);
4549
- const group = object2 && !bindsSpec(object2) ? groupOf(object2, level + 1) : void 0;
4548
+ const object3 = objectValue2(entry);
4549
+ const group = object3 && !bindsSpec(object3) ? groupOf(object3, level + 1) : void 0;
4550
4550
  return group ? [group] : [];
4551
4551
  }));
4552
4552
  const groupOf = (node, level) => {
@@ -4641,9 +4641,9 @@ function mintlifyNavigationApiReferences(config) {
4641
4641
  const entries = node[key];
4642
4642
  if (!Array.isArray(entries)) continue;
4643
4643
  for (const entry of entries) {
4644
- const object2 = objectValue2(entry);
4645
- if (!object2) continue;
4646
- visit(object2, labelFor(object2, tabLabel ?? "Documentation"), gated, otherLanguage || key === "languages" && rawNode === navigation && object2 !== defaultLanguage, hidden);
4644
+ const object3 = objectValue2(entry);
4645
+ if (!object3) continue;
4646
+ visit(object3, labelFor(object3, tabLabel ?? "Documentation"), gated, otherLanguage || key === "languages" && rawNode === navigation && object3 !== defaultLanguage, hidden);
4647
4647
  }
4648
4648
  }
4649
4649
  for (const key of nestedKeys) {
@@ -4651,15 +4651,15 @@ function mintlifyNavigationApiReferences(config) {
4651
4651
  if (!Array.isArray(entries)) continue;
4652
4652
  for (const entry of entries) {
4653
4653
  if (typeof entry === "string") continue;
4654
- const object2 = objectValue2(entry);
4655
- if (object2) visit(object2, tabLabel, gated, otherLanguage, hidden || object2.hidden === true);
4654
+ const object3 = objectValue2(entry);
4655
+ if (object3) visit(object3, tabLabel, gated, otherLanguage, hidden || object3.hidden === true);
4656
4656
  }
4657
4657
  }
4658
4658
  if (Array.isArray(node.menu)) {
4659
4659
  const tab = tabLabel ?? "Documentation";
4660
4660
  const items = node.menu.flatMap((entry) => {
4661
- const object2 = objectValue2(entry);
4662
- return object2 ? [object2] : [];
4661
+ const object3 = objectValue2(entry);
4662
+ return object3 ? [object3] : [];
4663
4663
  });
4664
4664
  const apiItems = items.filter((item) => specSource(item.openapi) || specSource(item.asyncapi));
4665
4665
  const soleContent = apiItems.length === 1 && !hasNavigationChildren(node) && !items.some(hasNavigationChildren);
@@ -4727,11 +4727,11 @@ function collectGatedSourceRefs(node, gate, out, limits, depth = 0) {
4727
4727
  for (const item of node) collectGatedSourceRefs(item, gate, out, limits, depth + 1);
4728
4728
  return;
4729
4729
  }
4730
- const object2 = objectValue2(node);
4731
- if (!object2) return;
4732
- const reason = gate ?? navigationGateReason(object2);
4733
- if (reason && typeof object2.sourceRef === "string") out.set(object2, reason);
4734
- for (const value of Object.values(object2)) collectGatedSourceRefs(value, reason, out, limits, depth + 1);
4730
+ const object3 = objectValue2(node);
4731
+ if (!object3) return;
4732
+ const reason = gate ?? navigationGateReason(object3);
4733
+ if (reason && typeof object3.sourceRef === "string") out.set(object3, reason);
4734
+ for (const value of Object.values(object3)) collectGatedSourceRefs(value, reason, out, limits, depth + 1);
4735
4735
  }
4736
4736
  function collectReferenceAccess(node, access, pathPrefix, out, depth = 0) {
4737
4737
  if (depth > SWEEP_DEPTH_LIMIT) return;
@@ -4739,29 +4739,29 @@ function collectReferenceAccess(node, access, pathPrefix, out, depth = 0) {
4739
4739
  for (const item of node) collectReferenceAccess(item, access, pathPrefix, out, depth + 1);
4740
4740
  return;
4741
4741
  }
4742
- const object2 = objectValue2(node);
4743
- if (!object2) return;
4744
- const nested = descendNavigationAccess(access, navigationAccess(object2));
4742
+ const object3 = objectValue2(node);
4743
+ if (!object3) return;
4744
+ const nested = descendNavigationAccess(access, navigationAccess(object3));
4745
4745
  if (hasNavigationRules(nested)) {
4746
- const refs = [object2.page, object2.root, object2.href, ...Array.isArray(object2.pages) ? object2.pages : []];
4746
+ const refs = [object3.page, object3.root, object3.href, ...Array.isArray(object3.pages) ? object3.pages : []];
4747
4747
  for (const ref of refs) {
4748
4748
  if (typeof ref === "string" && normalizePageRef(ref, pathPrefix)) out.push({ ref, access: nested });
4749
4749
  }
4750
4750
  }
4751
- for (const value of Object.values(object2)) collectReferenceAccess(value, nested, pathPrefix, out, depth + 1);
4751
+ for (const value of Object.values(object3)) collectReferenceAccess(value, nested, pathPrefix, out, depth + 1);
4752
4752
  }
4753
- function withContainerAccess(object2, context, walk2, nested = false) {
4753
+ function withContainerAccess(object3, context, walk2, nested = false) {
4754
4754
  const outer = context.access;
4755
- context.access = descendNavigationAccess(outer, navigationAccess(object2));
4755
+ context.access = descendNavigationAccess(outer, navigationAccess(object3));
4756
4756
  try {
4757
- return withOperationScope(object2, context, nested, walk2);
4757
+ return withOperationScope(object3, context, nested, walk2);
4758
4758
  } finally {
4759
4759
  context.access = outer;
4760
4760
  }
4761
4761
  }
4762
- function withOperationScope(object2, context, nested, walk2) {
4762
+ function withOperationScope(object3, context, nested, walk2) {
4763
4763
  const outer = context.operations;
4764
- context.operations = descendOperationScope(outer, object2, nested);
4764
+ context.operations = descendOperationScope(outer, object3, nested);
4765
4765
  try {
4766
4766
  return walk2();
4767
4767
  } finally {
@@ -4835,9 +4835,9 @@ function convertPage(value, context) {
4835
4835
  }
4836
4836
  return registerReference(value, context);
4837
4837
  }
4838
- const object2 = objectValue2(value);
4839
- if (!object2) return null;
4840
- return withContainerAccess(object2, context, () => convertPageObject(object2, context));
4838
+ const object3 = objectValue2(value);
4839
+ if (!object3) return null;
4840
+ return withContainerAccess(object3, context, () => convertPageObject(object3, context));
4841
4841
  }
4842
4842
  function gatedSourceRef(repo, context) {
4843
4843
  if (!context.gatedSourceRefs.includes(repo)) context.gatedSourceRefs.push(repo);
@@ -4848,10 +4848,10 @@ function gatedSourceRef(repo, context) {
4848
4848
  );
4849
4849
  return null;
4850
4850
  }
4851
- function convertPageObject(object2, context) {
4852
- if (typeof object2.page === "string") return registerReference(object2.page, context);
4853
- const href = projectedHref(object2.href);
4854
- if (href && !object2.pages && !object2.groups) {
4851
+ function convertPageObject(object3, context) {
4852
+ if (typeof object3.page === "string") return registerReference(object3.page, context);
4853
+ const href = projectedHref(object3.href);
4854
+ if (href && !object3.pages && !object3.groups) {
4855
4855
  const page = registerReference(href, context);
4856
4856
  if (page) return page;
4857
4857
  warnOnce(
@@ -4861,25 +4861,25 @@ function convertPageObject(object2, context) {
4861
4861
  );
4862
4862
  return null;
4863
4863
  }
4864
- if (typeof object2.sourceRef === "string") {
4865
- if (isNavigationRestricted(context.access) || context.gatedNodes.has(object2) || context.limits.tooDeep) return gatedSourceRef(object2.sourceRef, context);
4866
- const imported = context.resolveSourceRef?.(object2.sourceRef);
4867
- if (imported) return { group: object2.sourceRef.split("/").pop() ?? object2.sourceRef, pages: imported };
4864
+ if (typeof object3.sourceRef === "string") {
4865
+ if (isNavigationRestricted(context.access) || context.gatedNodes.has(object3) || context.limits.tooDeep) return gatedSourceRef(object3.sourceRef, context);
4866
+ const imported = context.resolveSourceRef?.(object3.sourceRef);
4867
+ if (imported) return { group: object3.sourceRef.split("/").pop() ?? object3.sourceRef, pages: imported };
4868
4868
  const where = context.groupLabel ? `the "${context.groupLabel}" navigation group` : "the navigation";
4869
4869
  warnOnce(
4870
4870
  context,
4871
- `source-ref:${context.groupLabel ?? ""}:${object2.sourceRef}`,
4872
- `Navigation in ${where} mounts the docs of the repository ${object2.sourceRef} (sourceRef). Those pages were not migrated; pass --source-ref ${object2.sourceRef}=<path> to import them.`
4871
+ `source-ref:${context.groupLabel ?? ""}:${object3.sourceRef}`,
4872
+ `Navigation in ${where} mounts the docs of the repository ${object3.sourceRef} (sourceRef). Those pages were not migrated; pass --source-ref ${object3.sourceRef}=<path> to import them.`
4873
4873
  );
4874
4874
  return null;
4875
4875
  }
4876
- const pages = Array.isArray(object2.pages) ? object2.pages : [];
4877
- if ("group" in object2 || pages.length > 0) {
4876
+ const pages = Array.isArray(object3.pages) ? object3.pages : [];
4877
+ if ("group" in object3 || pages.length > 0) {
4878
4878
  const children = [];
4879
4879
  const outerGroupLabel = context.groupLabel;
4880
- context.groupLabel = labelFor(object2, "Documentation");
4881
- if (typeof object2.root === "string") {
4882
- const root = registerReference(object2.root, context);
4880
+ context.groupLabel = labelFor(object3, "Documentation");
4881
+ if (typeof object3.root === "string") {
4882
+ const root = registerReference(object3.root, context);
4883
4883
  if (root) children.push(root);
4884
4884
  }
4885
4885
  try {
@@ -4899,9 +4899,9 @@ function convertPageObject(object2, context) {
4899
4899
  }
4900
4900
  if (children.length === 0) return null;
4901
4901
  return {
4902
- group: labelFor(object2, "Documentation"),
4903
- ...iconName(object2.icon) ? { icon: iconName(object2.icon) } : {},
4904
- ...object2.hidden === true ? { hidden: true } : {},
4902
+ group: labelFor(object3, "Documentation"),
4903
+ ...iconName(object3.icon) ? { icon: iconName(object3.icon) } : {},
4904
+ ...object3.hidden === true ? { hidden: true } : {},
4905
4905
  pages: children
4906
4906
  };
4907
4907
  }
@@ -4991,37 +4991,37 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
4991
4991
  entries.sort((left, right) => Number(objectValue2(right)?.default === true) - Number(objectValue2(left)?.default === true));
4992
4992
  }
4993
4993
  const tabs = entries.flatMap((value, index) => {
4994
- const object2 = objectValue2(value);
4995
- if (!object2) return [];
4996
- const tab = labelFor(object2, `${fallbackTab} ${index + 1}`);
4997
- const href = projectedHref(object2.href);
4998
- const hasNestedContainers = [...containerKeys, "menu"].some((containerKey) => Array.isArray(object2[containerKey]));
4999
- if (href && !object2.pages && !object2.groups && !hasNestedContainers) {
4994
+ const object3 = objectValue2(value);
4995
+ if (!object3) return [];
4996
+ const tab = labelFor(object3, `${fallbackTab} ${index + 1}`);
4997
+ const href = projectedHref(object3.href);
4998
+ const hasNestedContainers = [...containerKeys, "menu"].some((containerKey) => Array.isArray(object3[containerKey]));
4999
+ if (href && !object3.pages && !object3.groups && !hasNestedContainers) {
5000
5000
  return [{
5001
5001
  tab,
5002
5002
  href,
5003
- ...containerPresentation(object2),
5004
- ...object2.hidden === true ? { hidden: true } : {}
5003
+ ...containerPresentation(object3),
5004
+ ...object3.hidden === true ? { hidden: true } : {}
5005
5005
  }];
5006
5006
  }
5007
- const nested = withContainerAccess(object2, context, () => convertContainerToTabs(object2, context, tab, trace, depth + 1), true);
5007
+ const nested = withContainerAccess(object3, context, () => convertContainerToTabs(object3, context, tab, trace, depth + 1), true);
5008
5008
  if (nested.length > 0) {
5009
5009
  if (nested.length === 1) {
5010
5010
  return [{
5011
5011
  ...nested[0],
5012
5012
  tab,
5013
- ...containerPresentation(object2),
5013
+ ...containerPresentation(object3),
5014
5014
  ...href ? { href } : {},
5015
- ...object2.hidden === true ? { hidden: true } : {}
5015
+ ...object3.hidden === true ? { hidden: true } : {}
5016
5016
  }];
5017
5017
  }
5018
- const presentation = containerPresentation(object2);
5018
+ const presentation = containerPresentation(object3);
5019
5019
  return nested.map((item, childIndex) => ({
5020
5020
  ...presentation,
5021
5021
  ...item,
5022
5022
  tab: `${tab}: ${item.tab}`,
5023
5023
  ...childIndex === 0 && href && !item.href ? { href } : {},
5024
- ...object2.hidden === true ? { hidden: true } : {}
5024
+ ...object3.hidden === true ? { hidden: true } : {}
5025
5025
  }));
5026
5026
  }
5027
5027
  return [];
@@ -5820,8 +5820,8 @@ function registerPageAt(rawPath, base, context, navTitle, hidden = false) {
5820
5820
  context.sourcePathToNavigationId.set(sourcePath, navigationId);
5821
5821
  return navigationId;
5822
5822
  }
5823
- function registerFolder(object2, parentSegments, context) {
5824
- const rawPath = String(object2.folder).trim();
5823
+ function registerFolder(object3, parentSegments, context) {
5824
+ const rawPath = String(object3.folder).trim();
5825
5825
  let folder;
5826
5826
  try {
5827
5827
  folder = withinRealRoot(resolveWithinRoot(
@@ -5837,8 +5837,8 @@ function registerFolder(object2, parentSegments, context) {
5837
5837
  warnOnce2(context, `fern-missing-folder-${rawPath}`, `Fern folder "${rawPath}" does not exist and was skipped.`);
5838
5838
  return null;
5839
5839
  }
5840
- const label = typeof object2.title === "string" && object2.title.trim() ? object2.title.trim() : titleCase2(basename2(folder));
5841
- const segment = typeof object2.slug === "string" || object2["skip-slug"] === true ? segmentFor(object2, label, context) : fernBasicSlug(basename2(folder));
5840
+ const label = typeof object3.title === "string" && object3.title.trim() ? object3.title.trim() : titleCase2(basename2(folder));
5841
+ const segment = typeof object3.slug === "string" || object3["skip-slug"] === true ? segmentFor(object3, label, context) : fernBasicSlug(basename2(folder));
5842
5842
  const segments = segment ? [...parentSegments, segment] : parentSegments;
5843
5843
  const visit = (directory, routeSegments, depth) => {
5844
5844
  if (depth > 12 || context.descriptors.length >= 5e3) {
@@ -5871,17 +5871,17 @@ function registerFolder(object2, parentSegments, context) {
5871
5871
  addBareRouteRedirect(segments, pages, context);
5872
5872
  return { group: label, pages };
5873
5873
  }
5874
- function registerPage(object2, parentSegments, context) {
5875
- if (typeof object2.path !== "string" || !object2.path.trim()) return null;
5876
- const label = typeof object2.page === "string" && object2.page.trim() ? object2.page.trim() : "Untitled";
5877
- const segment = segmentFor(object2, label, context);
5874
+ function registerPage(object3, parentSegments, context) {
5875
+ if (typeof object3.path !== "string" || !object3.path.trim()) return null;
5876
+ const label = typeof object3.page === "string" && object3.page.trim() ? object3.page.trim() : "Untitled";
5877
+ const segment = segmentFor(object3, label, context);
5878
5878
  const base = [...parentSegments, segment].filter(Boolean).join("/");
5879
- return registerPageAt(object2.path, base, context, label, object2.hidden === true) || null;
5879
+ return registerPageAt(object3.path, base, context, label, object3.hidden === true) || null;
5880
5880
  }
5881
- function isRestrictedNode(object2) {
5882
- const viewers = object2.viewers;
5881
+ function isRestrictedNode(object3) {
5882
+ const viewers = object3.viewers;
5883
5883
  if (Array.isArray(viewers) ? viewers.length > 0 : typeof viewers === "string" ? viewers.trim() !== "" : viewers !== void 0 && viewers !== null && viewers !== false) return true;
5884
- const authed = object2.authed;
5884
+ const authed = object3.authed;
5885
5885
  return authed !== void 0 && authed !== null && authed !== false && !(typeof authed === "string" && ["", "false"].includes(authed.trim().toLowerCase()));
5886
5886
  }
5887
5887
  function restrictionMark(context) {
@@ -5892,67 +5892,67 @@ function markRestricted(context, mark) {
5892
5892
  context.restrictedApiSections.push(...context.apiSections.splice(mark.apis));
5893
5893
  }
5894
5894
  function convertNode(node, parentSegments, context) {
5895
- const object2 = objectValue3(node);
5896
- if (!object2) return null;
5895
+ const object3 = objectValue3(node);
5896
+ if (!object3) return null;
5897
5897
  const walk2 = context.navigationWalk;
5898
- const reason = walk2.active.has(object2) ? "cyclic section" : walk2.active.size >= 64 ? "section depth exceeds 64" : walk2.visits >= 5e3 ? "node budget exceeds 5000" : void 0;
5898
+ const reason = walk2.active.has(object3) ? "cyclic section" : walk2.active.size >= 64 ? "section depth exceeds 64" : walk2.visits >= 5e3 ? "node budget exceeds 5000" : void 0;
5899
5899
  if (reason) {
5900
5900
  walk2.incomplete = true;
5901
5901
  warnOnce2(context, `navigation-${reason}`, `Fern navigation was skipped: ${reason}.`);
5902
5902
  return null;
5903
5903
  }
5904
5904
  walk2.visits++;
5905
- walk2.active.add(object2);
5905
+ walk2.active.add(object3);
5906
5906
  try {
5907
- if (isRestrictedNode(object2)) {
5907
+ if (isRestrictedNode(object3)) {
5908
5908
  const before = restrictionMark(context);
5909
- convertNodeContents({ ...object2, viewers: void 0, authed: void 0 }, parentSegments, context);
5909
+ convertNodeContents({ ...object3, viewers: void 0, authed: void 0 }, parentSegments, context);
5910
5910
  markRestricted(context, before);
5911
5911
  return null;
5912
5912
  }
5913
- return convertNodeContents(object2, parentSegments, context);
5913
+ return convertNodeContents(object3, parentSegments, context);
5914
5914
  } finally {
5915
- walk2.active.delete(object2);
5915
+ walk2.active.delete(object3);
5916
5916
  }
5917
5917
  }
5918
- function convertNodeContents(object2, parentSegments, context) {
5919
- if (typeof object2.page === "string") {
5920
- const page = registerPage(object2, parentSegments, context);
5921
- return object2.hidden === true || context.hiddenInherited ? null : page;
5918
+ function convertNodeContents(object3, parentSegments, context) {
5919
+ if (typeof object3.page === "string") {
5920
+ const page = registerPage(object3, parentSegments, context);
5921
+ return object3.hidden === true || context.hiddenInherited ? null : page;
5922
5922
  }
5923
- if (typeof object2.folder === "string") {
5924
- const folder = registerFolder(object2, parentSegments, object2.hidden === true ? { ...context, hiddenInherited: true } : context);
5925
- return object2.hidden === true || context.hiddenInherited ? null : folder;
5923
+ if (typeof object3.folder === "string") {
5924
+ const folder = registerFolder(object3, parentSegments, object3.hidden === true ? { ...context, hiddenInherited: true } : context);
5925
+ return object3.hidden === true || context.hiddenInherited ? null : folder;
5926
5926
  }
5927
- if (typeof object2.section === "string") {
5928
- const label = object2.section;
5929
- const segment = segmentFor(object2, label, context);
5927
+ if (typeof object3.section === "string") {
5928
+ const label = object3.section;
5929
+ const segment = segmentFor(object3, label, context);
5930
5930
  const segments = segment ? [...parentSegments, segment] : parentSegments;
5931
- const contents = Array.isArray(object2.contents) ? object2.contents : [];
5931
+ const contents = Array.isArray(object3.contents) ? object3.contents : [];
5932
5932
  const pages = [];
5933
- const hasOwnPath = typeof object2.path === "string" && object2.path.trim().length > 0;
5933
+ const hasOwnPath = typeof object3.path === "string" && object3.path.trim().length > 0;
5934
5934
  if (hasOwnPath) {
5935
- const registered = registerPageAt(object2.path, segments.join("/"), context, label, object2.hidden === true);
5935
+ const registered = registerPageAt(object3.path, segments.join("/"), context, label, object3.hidden === true);
5936
5936
  if (registered) pages.push(registered);
5937
5937
  }
5938
5938
  for (const child of contents) {
5939
- const converted = convertNode(child, segments, object2.hidden === true ? { ...context, hiddenInherited: true } : context);
5939
+ const converted = convertNode(child, segments, object3.hidden === true ? { ...context, hiddenInherited: true } : context);
5940
5940
  if (converted) pages.push(converted);
5941
5941
  }
5942
5942
  if (pages.length === 0) return null;
5943
5943
  if (!hasOwnPath && segment) addBareRouteRedirect(segments, pages, context);
5944
- if (object2.hidden === true || context.hiddenInherited) return null;
5944
+ if (object3.hidden === true || context.hiddenInherited) return null;
5945
5945
  return {
5946
5946
  group: label,
5947
- ...["open-by-default", "closed-by-default", true, false].includes(object2.collapsed) ? { defaultOpen: object2.collapsed === "open-by-default" || object2.collapsed === false } : {},
5948
- ...typeof object2.icon === "string" ? { icon: object2.icon } : {},
5949
- ...object2.hidden === true ? { hidden: true } : {},
5947
+ ...["open-by-default", "closed-by-default", true, false].includes(object3.collapsed) ? { defaultOpen: object3.collapsed === "open-by-default" || object3.collapsed === false } : {},
5948
+ ...typeof object3.icon === "string" ? { icon: object3.icon } : {},
5949
+ ...object3.hidden === true ? { hidden: true } : {},
5950
5950
  pages
5951
5951
  };
5952
5952
  }
5953
- if (typeof object2.link === "string") {
5954
- const label = object2.link.trim();
5955
- const rawHref = typeof object2.href === "string" ? object2.href.trim() : "";
5953
+ if (typeof object3.link === "string") {
5954
+ const label = object3.link.trim();
5955
+ const rawHref = typeof object3.href === "string" ? object3.href.trim() : "";
5956
5956
  let href;
5957
5957
  if (/^https?:\/\//i.test(rawHref) && !/[\\\u0000-\u0020\u007f]/.test(rawHref)) {
5958
5958
  try {
@@ -5961,26 +5961,26 @@ function convertNodeContents(object2, parentSegments, context) {
5961
5961
  } catch {
5962
5962
  }
5963
5963
  }
5964
- if (label && href && object2.hidden !== true && !context.hiddenInherited) {
5964
+ if (label && href && object3.hidden !== true && !context.hiddenInherited) {
5965
5965
  if (!context.externalLinks.some((link) => link.label === label && link.href === href)) {
5966
5966
  context.externalLinks.push({ label, href });
5967
5967
  }
5968
- } else if (!href && object2.hidden !== true && !context.hiddenInherited) {
5968
+ } else if (!href && object3.hidden !== true && !context.hiddenInherited) {
5969
5969
  warnOnce2(context, `fern-link-${rawHref}`, `Fern navigation link "${label}" has an unsafe or invalid external URL and was skipped.`);
5970
5970
  }
5971
5971
  return null;
5972
5972
  }
5973
- if (typeof object2.api === "string") {
5974
- const ownSegment = segmentFor(object2, object2.api, context);
5973
+ if (typeof object3.api === "string") {
5974
+ const ownSegment = segmentFor(object3, object3.api, context);
5975
5975
  context.apiSections.push({
5976
- name: typeof object2["api-name"] === "string" ? object2["api-name"] : object2.api,
5977
- nameExplicit: typeof object2["api-name"] === "string",
5976
+ name: typeof object3["api-name"] === "string" ? object3["api-name"] : object3.api,
5977
+ nameExplicit: typeof object3["api-name"] === "string",
5978
5978
  routeSegments: ownSegment ? [...parentSegments, ownSegment] : parentSegments
5979
5979
  });
5980
5980
  return null;
5981
5981
  }
5982
- if (typeof object2.changelog === "string") {
5983
- return registerFolder({ folder: object2.changelog, title: "Changelog", "skip-slug": true }, parentSegments, context);
5982
+ if (typeof object3.changelog === "string") {
5983
+ return registerFolder({ folder: object3.changelog, title: "Changelog", "skip-slug": true }, parentSegments, context);
5984
5984
  }
5985
5985
  return null;
5986
5986
  }
@@ -6643,14 +6643,14 @@ function parseStaticSidebarModule(source) {
6643
6643
  literal
6644
6644
  );
6645
6645
  const parsed = JSON5.parse(substituted);
6646
- const object2 = objectValue4(parsed);
6647
- if (!object2) continue;
6646
+ const object3 = objectValue4(parsed);
6647
+ if (!object3) continue;
6648
6648
  const bindingName = candidate[0].match(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/)?.[1];
6649
6649
  if (bindingName) {
6650
- bindings.set(bindingName, object2);
6650
+ bindings.set(bindingName, object3);
6651
6651
  continue;
6652
6652
  }
6653
- return object2;
6653
+ return object3;
6654
6654
  } catch (error) {
6655
6655
  const message = error instanceof Error ? error.message : String(error);
6656
6656
  const line = Number(message.match(/at (\d+):/)?.[1] ?? 0);
@@ -6739,8 +6739,8 @@ function configuredSidebarPath(repositoryRoot) {
6739
6739
  if (!existsSync4(path)) continue;
6740
6740
  const source = readBoundedText(path);
6741
6741
  for (const docs of source.matchAll(/\bdocs\s*:\s*/g)) {
6742
- const object2 = matchingObjectLiteral(source, (docs.index ?? 0) + docs[0].length);
6743
- const candidate2 = object2 && staticStringField(object2, "sidebarPath")?.replace(/^\.\//, "");
6742
+ const object3 = matchingObjectLiteral(source, (docs.index ?? 0) + docs[0].length);
6743
+ const candidate2 = object3 && staticStringField(object3, "sidebarPath")?.replace(/^\.\//, "");
6744
6744
  if (candidate2) {
6745
6745
  const resolved2 = withinRealRoot(resolveWithin(repositoryRoot, candidate2), repositoryRoot);
6746
6746
  if (existsSync4(resolved2) && lstatSync4(resolved2).isFile()) return candidate2;
@@ -6851,12 +6851,12 @@ function readCategoryMetadata(contentRoot, directory) {
6851
6851
  try {
6852
6852
  const raw = readBoundedText(path);
6853
6853
  const parsed = filename.endsWith(".json") ? JSON5.parse(raw) : parseYaml3(raw);
6854
- const object2 = objectValue4(parsed);
6855
- if (!object2) return {};
6854
+ const object3 = objectValue4(parsed);
6855
+ if (!object3) return {};
6856
6856
  return {
6857
- ...typeof object2.label === "string" ? { label: object2.label } : {},
6858
- ...typeof object2.position === "number" ? { position: object2.position } : {},
6859
- ..."link" in object2 ? { link: object2.link } : {}
6857
+ ...typeof object3.label === "string" ? { label: object3.label } : {},
6858
+ ...typeof object3.position === "number" ? { position: object3.position } : {},
6859
+ ..."link" in object3 ? { link: object3.link } : {}
6860
6860
  };
6861
6861
  } catch {
6862
6862
  return {};
@@ -7040,7 +7040,7 @@ function readDocusaurusSiteSettings(repositoryRoot) {
7040
7040
  const source = readBoundedText(configPath);
7041
7041
  const bindings = staticStringBindings(source);
7042
7042
  const presets = staticArrayField(source, "presets");
7043
- const classicDocs = presets && [...presets.matchAll(/["']?docs["']?\s*:\s*/g)].map((match) => matchingObjectLiteral(presets, (match.index ?? 0) + match[0].length)).find((object2) => Boolean(object2));
7043
+ const classicDocs = presets && [...presets.matchAll(/["']?docs["']?\s*:\s*/g)].map((match) => matchingObjectLiteral(presets, (match.index ?? 0) + match[0].length)).find((object3) => Boolean(object3));
7044
7044
  const docsRouteBasePath = classicDocs && staticStringField(classicDocs, "routeBasePath") || "docs";
7045
7045
  const themeStart = propertyStart(source, "themeConfig");
7046
7046
  const theme = themeStart < 0 ? null : matchingObjectLiteral(source, themeStart);
@@ -7163,24 +7163,24 @@ function convertItems(value, context) {
7163
7163
  return page ? [page] : [];
7164
7164
  }
7165
7165
  if (Array.isArray(value)) return value.flatMap((entry) => convertItems(entry, context));
7166
- const object2 = objectValue4(value);
7167
- if (!object2) return [];
7168
- if (object2.type === "doc" || object2.type === "ref") {
7169
- const page = typeof object2.id === "string" ? registerDoc(object2.id, context) : null;
7166
+ const object3 = objectValue4(value);
7167
+ if (!object3) return [];
7168
+ if (object3.type === "doc" || object3.type === "ref") {
7169
+ const page = typeof object3.id === "string" ? registerDoc(object3.id, context) : null;
7170
7170
  return page ? [page] : [];
7171
7171
  }
7172
- if (object2.type === "autogenerated") {
7173
- return autogeneratedItems(typeof object2.dirName === "string" ? object2.dirName : ".", context);
7172
+ if (object3.type === "autogenerated") {
7173
+ return autogeneratedItems(typeof object3.dirName === "string" ? object3.dirName : ".", context);
7174
7174
  }
7175
- if (object2.type === "category") {
7176
- const label = typeof object2.label === "string" ? object2.label : "Documentation";
7177
- const pages = convertItems(object2.items, context);
7178
- const landing = generatedIndexPage(label, object2.link, context);
7175
+ if (object3.type === "category") {
7176
+ const label = typeof object3.label === "string" ? object3.label : "Documentation";
7177
+ const pages = convertItems(object3.items, context);
7178
+ const landing = generatedIndexPage(label, object3.link, context);
7179
7179
  if (landing && !pages.includes(landing)) pages.unshift(landing);
7180
7180
  return pages.length > 0 ? [{ group: label, pages }] : [];
7181
7181
  }
7182
- if (object2.type === "link" || object2.type === "html") return [];
7183
- return Object.entries(object2).flatMap(([label, items]) => {
7182
+ if (object3.type === "link" || object3.type === "html") return [];
7183
+ return Object.entries(object3).flatMap(([label, items]) => {
7184
7184
  const pages = convertItems(items, context);
7185
7185
  return pages.length > 0 ? [{ group: label, pages }] : [];
7186
7186
  });
@@ -7552,7 +7552,7 @@ import { lstatSync as lstatSync5, readdirSync as readdirSync3, readFileSync as r
7552
7552
  import { isAbsolute as isAbsolute2, join, relative as relative5, resolve as resolve3, sep as sep2 } from "path";
7553
7553
 
7554
7554
  // src/url.ts
7555
- import { load as load2 } from "cheerio";
7555
+ import { load as load3 } from "cheerio";
7556
7556
  import remarkMdx3 from "remark-mdx";
7557
7557
  import remarkParse3 from "remark-parse";
7558
7558
  import TurndownService from "turndown";
@@ -7738,7 +7738,7 @@ function migrationImageUrl(value, base) {
7738
7738
  if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error("Responsive image URL is not a credential-free HTTP(S) URL.");
7739
7739
  return url.toString();
7740
7740
  }
7741
- function migrationImageCandidates(value, base, resolve4 = true) {
7741
+ function migrationImageCandidates(value, base, resolve5 = true) {
7742
7742
  if (value.length > 64e3) throw new Error("Responsive image candidate list exceeds its safety bound.");
7743
7743
  const result = [];
7744
7744
  let index = 0;
@@ -7756,7 +7756,7 @@ function migrationImageCandidates(value, base, resolve4 = true) {
7756
7756
  if (descriptor && !/^(?:[1-9]\d*w|(?:\d+(?:\.\d+)?|\.\d+)x)$/.test(descriptor)) throw new Error("Responsive image has an unsupported descriptor.");
7757
7757
  if (descriptor.endsWith("x") && Number(descriptor.slice(0, -1)) <= 0) throw new Error("Responsive image density must be positive.");
7758
7758
  const resolved = migrationImageUrl(url, base);
7759
- result.push({ url: resolve4 ? resolved : url, descriptor });
7759
+ result.push({ url: resolve5 ? resolved : url, descriptor });
7760
7760
  if (result.length > 128) throw new Error("Responsive image has too many candidates.");
7761
7761
  if (value[index] === ",") index++;
7762
7762
  }
@@ -7770,6 +7770,86 @@ function migrationImageAttribute(value) {
7770
7770
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\{/g, "&#123;").replace(/\}/g, "&#125;").replace(/\r/g, "&#13;").replace(/\n/g, "&#10;");
7771
7771
  }
7772
7772
 
7773
+ // src/url-media.ts
7774
+ import { load as load2 } from "cheerio";
7775
+ var VIDEO_ATTRIBUTES = /* @__PURE__ */ new Set([
7776
+ "src",
7777
+ "title",
7778
+ "poster",
7779
+ "controls",
7780
+ "muted",
7781
+ "autoplay",
7782
+ "loop",
7783
+ "playsinline",
7784
+ "class",
7785
+ "id",
7786
+ "width",
7787
+ "height",
7788
+ "preload",
7789
+ "aria-label"
7790
+ ]);
7791
+ var YOUTUBE_ATTRIBUTES = /* @__PURE__ */ new Set([
7792
+ "src",
7793
+ "title",
7794
+ "width",
7795
+ "height",
7796
+ "allow",
7797
+ "allowfullscreen",
7798
+ "referrerpolicy",
7799
+ "frameborder",
7800
+ "loading",
7801
+ "class",
7802
+ "id",
7803
+ "aria-label"
7804
+ ]);
7805
+ var YOUTUBE_HOSTS = /* @__PURE__ */ new Set([
7806
+ "youtube.com",
7807
+ "www.youtube.com",
7808
+ "youtube-nocookie.com",
7809
+ "www.youtube-nocookie.com"
7810
+ ]);
7811
+ function projectMigrationMedia(html, base) {
7812
+ const $ = load2(html, void 0, false);
7813
+ const nodes = $.root().contents().filter((_index, node2) => node2.type !== "text" || Boolean(node2.data.trim()));
7814
+ if (nodes.length !== 1) return null;
7815
+ const element = nodes.first();
7816
+ const node = element[0];
7817
+ if (node.type !== "tag" || !["video", "iframe"].includes(node.name)) return null;
7818
+ if (element.children().length || element.text().trim()) return null;
7819
+ const allowed = node.name === "video" ? VIDEO_ATTRIBUTES : YOUTUBE_ATTRIBUTES;
7820
+ if (Object.keys(node.attribs).some((name) => !allowed.has(name))) return null;
7821
+ const source = element.attr("src");
7822
+ if (!source) return null;
7823
+ try {
7824
+ let src = migrationImageUrl(source, base);
7825
+ let component = "Video";
7826
+ const props = [];
7827
+ if (node.name === "video") {
7828
+ if (!/\.(?:mp4|webm|ogg|mov|m4v)(?:[?#]|$)/i.test(src)) component = "video";
7829
+ const isMuted = element.attr("muted") !== void 0;
7830
+ const hasAutoplay = element.attr("autoplay") !== void 0;
7831
+ if (hasAutoplay && !isMuted) return null;
7832
+ props.push("controls={true}");
7833
+ for (const [attribute, prop] of [["muted", "muted"], ["loop", "loop"], ["playsinline", "playsInline"], ["autoplay", "autoPlay"]]) {
7834
+ if (element.attr(attribute) !== void 0) props.push(`${prop}={true}`);
7835
+ }
7836
+ const poster = element.attr("poster");
7837
+ if (poster) props.push(`poster="${migrationImageAttribute(migrationImageUrl(poster, base))}"`);
7838
+ } else {
7839
+ const url = new URL(src);
7840
+ if (url.protocol !== "https:" || url.port || !YOUTUBE_HOSTS.has(url.hostname)) return null;
7841
+ const id = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]{11})$/)?.[1];
7842
+ if (!id || url.hash || [...url.searchParams.keys()].some((name) => name !== "si")) return null;
7843
+ src = `https://www.youtube-nocookie.com/embed/${id}`;
7844
+ }
7845
+ const title = element.attr("title") ?? element.attr("aria-label");
7846
+ if (title !== void 0) props.push(`title="${migrationImageAttribute(title)}"`);
7847
+ return `<${component} src="${migrationImageAttribute(src)}"${props.length ? ` ${props.join(" ")}` : ""} />`;
7848
+ } catch {
7849
+ return null;
7850
+ }
7851
+ }
7852
+
7773
7853
  // src/redirects.ts
7774
7854
  var LISTED = 10;
7775
7855
  function exactRoute(route) {
@@ -7987,7 +8067,8 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
7987
8067
  "Warning",
7988
8068
  "YouTube",
7989
8069
  "picture",
7990
- "source"
8070
+ "source",
8071
+ "video"
7991
8072
  ]);
7992
8073
  var LOCALE_CODES = /* @__PURE__ */ new Set([
7993
8074
  "ar",
@@ -8023,7 +8104,7 @@ var LOCALE_CODES = /* @__PURE__ */ new Set([
8023
8104
  "zh-hans",
8024
8105
  "zh-hant"
8025
8106
  ]);
8026
- var PORTABLE_URL_PROPS = "href|src|img|primaryHref|secondaryHref";
8107
+ var PORTABLE_URL_PROPS = "href|src|poster|img|primaryHref|secondaryHref";
8027
8108
  var PORTABLE_URL_PROP_NAMES = new Set(PORTABLE_URL_PROPS.split("|"));
8028
8109
  var MINTLIFY_ARTICLE_LABELS = [
8029
8110
  { baseUrl: "Select base URL", permalink: "Navigate to header" },
@@ -8099,7 +8180,7 @@ function machineIndexScopePath(source, documents) {
8099
8180
  if (!document) continue;
8100
8181
  const references = [...headerLinks(document)];
8101
8182
  if (/html/i.test(document.contentType)) {
8102
- const $ = load2(document.body);
8183
+ const $ = load3(document.body);
8103
8184
  references.push(...$("link[href], a[href]").map((_index, element) => $(element).attr("href") ?? "").get().filter((href) => /\/(?:llms(?:-full)?\.txt|sitemap\.xml)(?:[?#]|$)/i.test(href)));
8104
8185
  }
8105
8186
  for (const reference of references) {
@@ -8210,7 +8291,7 @@ function jsonObjectAfterKey(value, key) {
8210
8291
  return null;
8211
8292
  }
8212
8293
  function embeddedMintlifyConfig(body) {
8213
- const $ = load2(body);
8294
+ const $ = load3(body);
8214
8295
  const flightPayloads = [];
8215
8296
  $("script").each((_index, element) => {
8216
8297
  const script = $(element).text();
@@ -8308,7 +8389,7 @@ function isSafePortableUrl(value) {
8308
8389
  }
8309
8390
  }
8310
8391
  function decodeHtmlAttribute(value) {
8311
- const document = load2(`<i data-thally-value="${value.replace(/"/g, "&quot;")}"></i>`);
8392
+ const document = load3(`<i data-thally-value="${value.replace(/"/g, "&quot;")}"></i>`);
8312
8393
  return document("i").attr("data-thally-value") ?? "";
8313
8394
  }
8314
8395
  function escapeMarkdownText(value, escapeTableSeparator = false) {
@@ -8394,7 +8475,7 @@ function replacePortableHtmlImages(value) {
8394
8475
  if (start < cursor) continue;
8395
8476
  const end = htmlOpeningTagEnd(value, start + match[0].length);
8396
8477
  if (end === null) continue;
8397
- const fragment = load2(value.slice(start, end));
8478
+ const fragment = load3(value.slice(start, end));
8398
8479
  const image = fragment("img").first();
8399
8480
  const source = image.attr("src");
8400
8481
  const destination = source && isSafePortableUrl(source) ? markdownLinkDestination(source) : null;
@@ -8419,7 +8500,7 @@ function replacePortableHtmlAnchors(value) {
8419
8500
  const closing = closingMatch.exec(value);
8420
8501
  if (!closing) continue;
8421
8502
  const end = closing.index + closing[0].length;
8422
- const fragment = load2(value.slice(start, end));
8503
+ const fragment = load3(value.slice(start, end));
8423
8504
  const anchor = fragment("a").first();
8424
8505
  const href = anchor.attr("href");
8425
8506
  const label = escapeMarkdownText(anchor.text().replace(/\s+/g, " ").trim());
@@ -8454,6 +8535,10 @@ function sanitizeMarkdownUrls(value) {
8454
8535
  hasUnsafeMdxSyntax = true;
8455
8536
  return;
8456
8537
  }
8538
+ if (node.name === "video" && node.children?.length) {
8539
+ hasUnsafeMdxSyntax = true;
8540
+ return;
8541
+ }
8457
8542
  for (const attribute of node.attributes ?? []) {
8458
8543
  if (attribute.type !== "mdxJsxAttribute" || !attribute.name || /^on[A-Z]/.test(attribute.name)) {
8459
8544
  hasUnsafeMdxSyntax = true;
@@ -8490,6 +8575,22 @@ function sanitizeMarkdownUrls(value) {
8490
8575
  return;
8491
8576
  }
8492
8577
  }
8578
+ if (node.name === "video") {
8579
+ const isBoolean = ["controls", "muted", "autoPlay", "loop", "playsInline"].includes(attribute.name);
8580
+ const isText = ["src", "poster", "title"].includes(attribute.name);
8581
+ if (isBoolean && typeof staticValue !== "boolean" || isText && typeof attribute.value !== "string" || !isBoolean && !isText) {
8582
+ hasUnsafeMdxSyntax = true;
8583
+ return;
8584
+ }
8585
+ if (attribute.name === "src" || attribute.name === "poster") {
8586
+ try {
8587
+ migrationImageUrl(decodeHtmlAttribute(attribute.value), new URL("https://migration.invalid"));
8588
+ } catch {
8589
+ hasUnsafeMdxSyntax = true;
8590
+ return;
8591
+ }
8592
+ }
8593
+ }
8493
8594
  }
8494
8595
  }
8495
8596
  for (const child of node.children ?? []) validateMdxSyntax(child);
@@ -8602,7 +8703,7 @@ function sanitizeRemoteMarkdown(body) {
8602
8703
  const urlSafeMarkdown = sanitizeMarkdownUrls(sanitized);
8603
8704
  if (urlSafeMarkdown === null) return null;
8604
8705
  const executable = withoutFencedCode2(urlSafeMarkdown).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<(?:https?:\/\/|mailto:)[^>]+>/gi, "").replace(/(`+)[\s\S]*?\1/g, "");
8605
- if (/^\s*(?:import|export)\s/m.test(executable)) return null;
8706
+ if (/^[\t ]*(?:import|export)[\t ]/m.test(executable)) return null;
8606
8707
  let hasUnsafeExpression = false;
8607
8708
  const withoutStaticProps = executable.replace(/=\{([^{}\n]*)\}/g, (original, value) => {
8608
8709
  try {
@@ -8699,7 +8800,7 @@ function extractEmbeddedOpenApi(content) {
8699
8800
  const root = embeddedOpenApiParser.parse(content);
8700
8801
  const candidates = (root.children ?? []).flatMap((node2) => {
8701
8802
  if (node2.type !== "code" || !/^(?:yaml|yml|json)$/i.test(node2.lang ?? "")) return [];
8702
- const metadata = node2.meta?.match(/^(?:(\S+\.(?:yaml|yml|json))\s+)?(delete|get|head|options|patch|post|put|trace)\s+(\/\S+)\s*$/i);
8803
+ const metadata = node2.meta?.match(/^(?:((?:\S+\.(?:yaml|yml|json)|[A-Za-z][A-Za-z0-9_-]{0,127}))\s+)?(delete|get|head|options|patch|post|put|trace)\s+(\/\S+)\s*$/i);
8703
8804
  return metadata ? [{ node: node2, method: metadata[2].toUpperCase(), path: metadata[3] }] : [];
8704
8805
  });
8705
8806
  if (candidates.length !== 1) return { content };
@@ -8838,7 +8939,7 @@ function mergeEmbeddedOpenApi(fragments) {
8838
8939
  return new TextEncoder().encode(stringifyYaml2(combined));
8839
8940
  }
8840
8941
  function migrationHtmlArticle(body) {
8841
- const $ = load2(body);
8942
+ const $ = load3(body);
8842
8943
  const root = ["main article", "article", "main", '[role="main"]', "body"].map((selector) => $(selector).first()).find((candidate) => candidate.length);
8843
8944
  root.find([
8844
8945
  "script",
@@ -8856,7 +8957,10 @@ function migrationHtmlArticle(body) {
8856
8957
  "#chat-assistant-sheet",
8857
8958
  "#chat-assistant-textarea"
8858
8959
  ].join(",")).remove();
8859
- const permalinkSelectors = MINTLIFY_ARTICLE_LABELS.map((labels) => `a[aria-label="${labels.permalink}"]`);
8960
+ root.find('div[data-component-part="code-group-tab-content"]').each((_index, element) => {
8961
+ if ($(element).parents("pre").length === 0 && $(element).find("pre").length === 0) element.name = "pre";
8962
+ });
8963
+ const permalinkSelectors = MINTLIFY_ARTICLE_LABELS.map((labels2) => `a[aria-label="${labels2.permalink}"]`);
8860
8964
  root.find([...permalinkSelectors, "h1 .hash-link", "h2 .hash-link", "h3 .hash-link", "h4 .hash-link", "h5 .hash-link", "h6 .hash-link"].join(",")).each((_index, link) => {
8861
8965
  const parent = $(link).parent();
8862
8966
  $(link).remove();
@@ -8867,7 +8971,7 @@ function migrationHtmlArticle(body) {
8867
8971
  const isChecked = $(element).attr("checked") !== void 0;
8868
8972
  $(element).replaceWith($("<span></span>").attr("data-thally-static-task-checkbox", isChecked ? "checked" : "unchecked").text(isChecked ? "[x] " : "[ ] "));
8869
8973
  });
8870
- root.find(MINTLIFY_ARTICLE_LABELS.map((labels) => `select[aria-label="${labels.baseUrl}"]`).join(",")).each((_index, element) => {
8974
+ root.find(MINTLIFY_ARTICLE_LABELS.map((labels2) => `select[aria-label="${labels2.baseUrl}"]`).join(",")).each((_index, element) => {
8871
8975
  const select = $(element);
8872
8976
  const options = select.children("option").toArray();
8873
8977
  if (!options.length || options.length !== select.children().length) return;
@@ -8906,7 +9010,7 @@ function htmlPage(document, id, options = {}) {
8906
9010
  const title = scopedTitle || $("main h1, article h1, h1").first().text().trim() || $('meta[property="og:title"]').attr("content")?.trim() || $("title").text().split("|")[0].trim();
8907
9011
  const description = $('meta[name="description"]').attr("content")?.trim() || $('meta[property="og:description"]').attr("content")?.trim();
8908
9012
  let documentHeadingAnchor = "";
8909
- const excludedElements = options.omitDocumentHeading ? "script,style,noscript,nav,footer,aside,form,button,svg,iframe,object,embed,link,meta" : "script,style,noscript,nav,header,footer,aside,form,button,svg,iframe,object,embed,link,meta";
9013
+ const excludedElements = options.omitDocumentHeading ? "script,style,noscript,nav,footer,aside,form,button,svg,object,embed,link,meta" : "script,style,noscript,nav,header,footer,aside,form,button,svg,object,embed,link,meta";
8910
9014
  root.find(excludedElements).remove();
8911
9015
  root.find(".theme-doc-version-badge,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
8912
9016
  if (options.omitDocumentHeading) {
@@ -8964,6 +9068,18 @@ function htmlPage(document, id, options = {}) {
8964
9068
  // Emit literal task-list syntax rather than escaping its brackets as copy.
8965
9069
  replacement: (_content, node) => node.getAttribute("data-thally-static-task-checkbox") === "checked" ? "[x] " : "[ ] "
8966
9070
  });
9071
+ turndown.addRule("migration-authored-paragraph", {
9072
+ filter: (node) => node.nodeName === "SPAN" && node.getAttribute("data-as") === "p",
9073
+ // Some publishers render authored paragraphs as spans. Their explicit
9074
+ // semantic hint must retain a block boundary: otherwise following copy
9075
+ // joins a picture's closing tag or an accordion's source anchor and can
9076
+ // change the resulting MDX grammar. Ordinary spans remain inline.
9077
+ replacement: (content) => `
9078
+
9079
+ ${content}
9080
+
9081
+ `
9082
+ });
8967
9083
  const anchor = (element) => {
8968
9084
  const value = element.getAttribute("id");
8969
9085
  return options.preserveAnchors && value ? `<a id="${value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/\{/g, "&#123;")}" />
@@ -8982,6 +9098,18 @@ ${anchor(element)}${heading ? `${"#".repeat(Number(heading[1]))} ${content.trim(
8982
9098
  `;
8983
9099
  }
8984
9100
  });
9101
+ turndown.addRule("migration-static-media", {
9102
+ filter: (node) => node.nodeName === "VIDEO" || node.nodeName === "IFRAME",
9103
+ replacement: (_content, node) => {
9104
+ const element = node;
9105
+ const projected = projectMigrationMedia(element.outerHTML, document.finalUrl);
9106
+ return projected ? `
9107
+
9108
+ ${anchor(element)}${projected}
9109
+
9110
+ ` : "";
9111
+ }
9112
+ });
8985
9113
  if (options.preserveResponsiveImages) {
8986
9114
  const image = (element) => {
8987
9115
  const tag = element.nodeName.toLowerCase();
@@ -9017,7 +9145,7 @@ ${content.trim()}
9017
9145
  const element = node;
9018
9146
  const className = element.className;
9019
9147
  const tag = /admonition-(?:danger)/.test(className) ? "Error" : /admonition-(?:caution|warning)/.test(className) ? "Warning" : /admonition-info/.test(className) ? "Info" : "Note";
9020
- const fragment = load2(element.outerHTML);
9148
+ const fragment = load3(element.outerHTML);
9021
9149
  const clone = fragment(".theme-admonition").first();
9022
9150
  const title2 = clone.find('[class*="admonitionHeading"],.admonition-heading').first();
9023
9151
  const titleText = title2.text().trim();
@@ -9038,15 +9166,15 @@ ${titleText ? `**${escapeMarkdownText(titleText)}**
9038
9166
  filter: (node) => node.nodeName === "DIV" && node.classList.contains("tabs-container"),
9039
9167
  replacement: (_content, node) => {
9040
9168
  const element = node;
9041
- const fragment = load2(element.outerHTML);
9169
+ const fragment = load3(element.outerHTML);
9042
9170
  const container = fragment(".tabs-container").first();
9043
- const labels = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
9171
+ const labels2 = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
9044
9172
  const panels = container.find('[role="tabpanel"]').toArray().filter((panel) => fragment(panel).closest(".tabs-container")[0] === container[0]);
9045
9173
  if (panels.length === 0) return _content;
9046
9174
  const tabs = panels.map((panel, index) => {
9047
9175
  const panelId = fragment(panel).attr("id");
9048
9176
  const tab = panelId ? container.find('[role="tab"]').toArray().find((item) => fragment(item).attr("aria-controls") === panelId) : void 0;
9049
- const title2 = migrationImageAttribute((tab ? fragment(tab).text().trim() : labels[index]) || `Tab ${index + 1}`);
9177
+ const title2 = migrationImageAttribute((tab ? fragment(tab).text().trim() : labels2[index]) || `Tab ${index + 1}`);
9050
9178
  const panelAnchor = options.preserveAnchors && panelId ? `<a id="${migrationImageAttribute(panelId)}" />
9051
9179
 
9052
9180
  ` : "";
@@ -9066,7 +9194,7 @@ ${tabs.join("\n\n")}
9066
9194
  turndown.addRule("gfm-table", {
9067
9195
  filter: "table",
9068
9196
  replacement: (_content, node) => {
9069
- const fragment = load2(node.outerHTML);
9197
+ const fragment = load3(node.outerHTML);
9070
9198
  const rows = fragment("tr").toArray().map((row) => fragment(row).children("th,td").toArray().map((cell) => {
9071
9199
  const wasConvertingTableCell = isConvertingTableCell;
9072
9200
  isConvertingTableCell = true;
@@ -9092,10 +9220,17 @@ ${normalized.slice(1).map((row) => `| ${row.join(" | ")} |`).join("\n")}
9092
9220
  filter: (node) => node.nodeName === "PRE",
9093
9221
  replacement: (_content, node) => {
9094
9222
  const element = node;
9095
- const fragment = load2(element.outerHTML);
9223
+ const fragment = load3(element.outerHTML);
9096
9224
  const tokenLines = fragment(".token-line").toArray();
9097
9225
  const code = (tokenLines.length > 0 ? tokenLines.map((line) => fragment(line).text()).join("\n") : fragment("pre").text()).replace(/\n$/, "");
9098
- const language = fragment("code").attr("class")?.match(/(?:language-|lang-)([A-Za-z0-9_+-]+)/)?.[1] ?? "";
9226
+ const language = ["code", "pre"].flatMap((selector) => {
9227
+ const block = fragment(selector).first();
9228
+ return [
9229
+ block.attr("language"),
9230
+ block.attr("data-language"),
9231
+ block.attr("class")?.match(/(?:^|\s)(?:language-|lang-)([A-Za-z0-9_+-]+)(?=\s|$)/)?.[1]
9232
+ ];
9233
+ }).find((value) => value !== void 0 && /^[A-Za-z0-9_+-]{1,64}$/.test(value)) ?? "";
9099
9234
  const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
9100
9235
  const fence = "`".repeat(Math.max(3, longestFence + 1));
9101
9236
  return `
@@ -9125,7 +9260,7 @@ ${fence}
9125
9260
  };
9126
9261
  }
9127
9262
  function htmlNavigationLinks(document) {
9128
- const $ = load2(document.body);
9263
+ const $ = load3(document.body);
9129
9264
  return $('.nav-tabs a[href], nav a[href], aside a[href], [role="navigation"] a[href]').map((_index, element) => $(element).attr("href") ?? "").get().filter(Boolean);
9130
9265
  }
9131
9266
  function normalizedLinkLabel(value) {
@@ -9133,7 +9268,7 @@ function normalizedLinkLabel(value) {
9133
9268
  }
9134
9269
  function readDocusaurusSidebar(document) {
9135
9270
  if (!/html/i.test(document.contentType)) return null;
9136
- const $ = load2(document.body);
9271
+ const $ = load3(document.body);
9137
9272
  const sidebar = $("ul.theme-doc-sidebar-menu").first();
9138
9273
  if (sidebar.length === 0) return null;
9139
9274
  function readList(list2) {
@@ -9169,7 +9304,7 @@ function docusaurusSidebarHrefs(snapshot) {
9169
9304
  return hrefs;
9170
9305
  }
9171
9306
  function deriveDocusaurusScope(source, document, submittedScopePath) {
9172
- const $ = load2(document.body);
9307
+ const $ = load3(document.body);
9173
9308
  const sidebar = readDocusaurusSidebar(document);
9174
9309
  const internalPaths = docusaurusSidebarHrefs(sidebar).flatMap((href) => {
9175
9310
  try {
@@ -9299,7 +9434,7 @@ function docusaurusNavigationFromSnapshots(snapshots, pages, scopePath) {
9299
9434
  return { tabs };
9300
9435
  }
9301
9436
  function htmlTopLevelNavigation(document, source, scopePath) {
9302
- const $ = load2(document.body);
9437
+ const $ = load3(document.body);
9303
9438
  const collectCandidates = (selector) => {
9304
9439
  const candidates2 = [];
9305
9440
  $(selector).each((_containerIndex, container) => {
@@ -9360,7 +9495,7 @@ function markdownLinks(body) {
9360
9495
  return [...body.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s+['"][^'"]*['"])?\)/g)].map((match) => match[1]);
9361
9496
  }
9362
9497
  function sitemapLinks(body) {
9363
- const $ = load2(body, { xmlMode: true });
9498
+ const $ = load3(body, { xmlMode: true });
9364
9499
  return $("url > loc, sitemap > loc").map((_index, element) => $(element).text().trim()).get().filter(Boolean);
9365
9500
  }
9366
9501
  function robotsSitemaps(body) {
@@ -9399,7 +9534,7 @@ async function migrateUrl(options) {
9399
9534
  const platform = options.platform ?? detectUrlPlatform(scopedHtmlProbe ?? initial);
9400
9535
  let docusaurusHtmlProbe = platform === "docusaurus" ? scopedHtmlProbe : null;
9401
9536
  if (docusaurusHtmlProbe && !readDocusaurusSidebar(docusaurusHtmlProbe)) {
9402
- const $ = load2(docusaurusHtmlProbe.body);
9537
+ const $ = load3(docusaurusHtmlProbe.body);
9403
9538
  const docsHref = $("nav a[href]").toArray().map((element) => ({
9404
9539
  href: $(element).attr("href"),
9405
9540
  label: normalizedLinkLabel($(element).text())
@@ -9849,10 +9984,10 @@ function mainSiteLinkTarget(target2, mountPath, index, unresolved) {
9849
9984
  unresolved.push(target2);
9850
9985
  return void 0;
9851
9986
  }
9852
- function prefixRootLinks(body, mountPath, resolve4, markdown = true) {
9987
+ function prefixRootLinks(body, mountPath, resolve5, markdown = true) {
9853
9988
  const mounted = `/${mountPath}`;
9854
9989
  const alreadyMounted = (target2) => target2 === mounted || ["/", "?", "#"].some((next) => target2.startsWith(mounted + next));
9855
- const fix = (target2) => alreadyMounted(target2) ? target2 : resolve4?.(target2) ?? `${mounted}${target2}`;
9990
+ const fix = (target2) => alreadyMounted(target2) ? target2 : resolve5?.(target2) ?? `${mounted}${target2}`;
9856
9991
  return replaceOutsideCodeAndComments(body, (text3) => text3.replace(markdown ? /(\]\(<?)(\/(?!\/)[^\s)>]*)/g : /(?!)/g, (_match, before, target2) => `${before}${fix(target2)}`).replace(/(\b(?:href|src|to|poster)=(['"]))(\/(?!\/)[^'"\n]*)\2/g, (_match, before, quote, target2) => `${before}${fix(target2)}${quote}`).replace(/(\b(?:href|src|to|poster)=\{\s*(['"]))(\/(?!\/)[^'"\n]*)\2(\s*\})/g, (_match, before, quote, target2, after) => `${before}${fix(target2)}${quote}${after}`).replace(markdown ? /^(\[[^\]\n]+\]:[ \t]*<?)(\/(?!\/)\S*)/gm : /(?!)/g, (_match, before, target2) => `${before}${fix(target2)}`));
9857
9992
  }
9858
9993
  function prefixNavigation(entries, mountPath) {
@@ -10376,8 +10511,8 @@ var HARD_TIMEOUT_REASON = `still running after ${MAX_GIT_PROCESS_MS / 6e4} minut
10376
10511
  var GIT_EXIT_DRAIN_MS = 2e3;
10377
10512
  var RETRYABLE_CLONE_ERROR = /RPC failed|Recv failure|early EOF|curl \d+|Could not resolve host|Connection (?:reset|refused|timed out)|The remote end hung up|SSL[_ ]?(?:read|connect|write) error|timed out|network is unreachable/i;
10378
10513
  function delay(ms) {
10379
- return new Promise((resolve4) => {
10380
- setTimeout(resolve4, ms);
10514
+ return new Promise((resolve5) => {
10515
+ setTimeout(resolve5, ms);
10381
10516
  });
10382
10517
  }
10383
10518
  function gitIdleTimeoutMs() {
@@ -10389,7 +10524,7 @@ function gitErrorTail(stderr) {
10389
10524
  return stderr.split(/[\r\n]+/).map((line) => line.trim()).filter((line) => line && !/^(?:remote: )?[A-Za-z][A-Za-z ]*:\s+\d+%/.test(line)).slice(-5).join("\n");
10390
10525
  }
10391
10526
  function runGit(args, options) {
10392
- return new Promise((resolve4, reject) => {
10527
+ return new Promise((resolve5, reject) => {
10393
10528
  const child = spawn("git", args, {
10394
10529
  stdio: ["ignore", "ignore", "pipe"],
10395
10530
  ...options.cwd ? { cwd: options.cwd } : {},
@@ -10412,7 +10547,7 @@ function runGit(args, options) {
10412
10547
  settled = true;
10413
10548
  clearTimers();
10414
10549
  child.stderr.destroy();
10415
- if (code === 0) resolve4();
10550
+ if (code === 0) resolve5();
10416
10551
  else if (timeoutReason) {
10417
10552
  const hint = timeoutReason === HARD_TIMEOUT_REASON ? "" : "; on a very slow link, raise THALLY_MIGRATE_CLONE_TIMEOUT_MS (milliseconds of allowed silence)";
10418
10553
  reject(new Error(`${options.label}: timed out (${timeoutReason}) and was killed. Check your network connection and try again${hint}.`));
@@ -14343,7 +14478,7 @@ function renderMigrationFiles(bundle, options = {}) {
14343
14478
 
14344
14479
  // src/url-run.ts
14345
14480
  import { createHash as createHash3 } from "crypto";
14346
- import { load as load4 } from "cheerio";
14481
+ import { load as load6 } from "cheerio";
14347
14482
  import remarkParse4 from "remark-parse";
14348
14483
  import remarkMdx4 from "remark-mdx";
14349
14484
  import { unified as unified4 } from "unified";
@@ -14721,7 +14856,53 @@ function cloneUrlMigrationCheckpoint(checkpoint) {
14721
14856
  }
14722
14857
 
14723
14858
  // src/url-navigation.ts
14724
- import { load as load3 } from "cheerio";
14859
+ import { load as load5 } from "cheerio";
14860
+
14861
+ // src/url-scope.ts
14862
+ import { load as load4 } from "cheerio";
14863
+ function inferUrlMigrationScope(capture) {
14864
+ const source = capture.finalUrl;
14865
+ let end = source.pathname.length;
14866
+ while (end > 0 && source.pathname.charCodeAt(end - 1) === 47) end--;
14867
+ const parent = end < source.pathname.length ? source.pathname.slice(0, end) || "/" : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/";
14868
+ if (!/html/i.test(capture.contentType) || Buffer.byteLength(capture.body) > 8e6) return { scopePath: parent, evidence: "source-parent" };
14869
+ const $ = load4(capture.body);
14870
+ const ancestor = (path) => path === "/" || source.pathname === path || source.pathname.startsWith(`${path}/`);
14871
+ const observed = (reference) => {
14872
+ if (!reference || reference.length > 8192) return void 0;
14873
+ try {
14874
+ const url = new URL(reference, source);
14875
+ if (url.origin !== source.origin || url.username || url.password || url.search || url.hash || /%2f|%5c/i.test(url.pathname)) return void 0;
14876
+ return url;
14877
+ } catch {
14878
+ return void 0;
14879
+ }
14880
+ };
14881
+ const indexes = [];
14882
+ $("link[href],a[href]").slice(0, 1e4).each((_, node) => {
14883
+ const url = observed($(node).attr("href"));
14884
+ if (!url || !/\/(?:llms(?:-full)?\.txt|sitemap(?:[-_][^/]*)?\.xml)$/i.test(url.pathname)) return;
14885
+ const path = url.pathname.slice(0, url.pathname.lastIndexOf("/")) || "/";
14886
+ if (ancestor(path)) indexes.push(path);
14887
+ });
14888
+ if (indexes.length) return { scopePath: indexes.sort((a, b) => b.length - a.length)[0], evidence: "discovery-index" };
14889
+ const paths = [];
14890
+ $('nav a[href],aside a[href],[role="navigation"] a[href]').slice(0, 1e4).each((_, node) => {
14891
+ if ($(node).closest("header,footer").length) return;
14892
+ const url = observed($(node).attr("href"));
14893
+ if (url && !/\.[a-z0-9]+$/i.test(url.pathname)) paths.push(url.pathname);
14894
+ });
14895
+ if (new Set(paths).size >= 2 && paths.some((path) => path === source.pathname)) {
14896
+ const segments = paths.map((path) => path.split("/").filter(Boolean));
14897
+ let count = 0;
14898
+ while (segments.every((parts) => parts[count] !== void 0 && parts[count] === segments[0][count])) count++;
14899
+ const common = "/" + segments[0].slice(0, count).join("/");
14900
+ if (ancestor(common)) return { scopePath: common, evidence: "navigation" };
14901
+ }
14902
+ return { scopePath: parent, evidence: "source-parent" };
14903
+ }
14904
+
14905
+ // src/url-navigation.ts
14725
14906
  var MAX_CAPTURE_BYTES = 8e6;
14726
14907
  var MAX_JSON_BYTES = 2e6;
14727
14908
  var MAX_NODES = 1e4;
@@ -14788,8 +14969,18 @@ function flightRecords(stream) {
14788
14969
  }
14789
14970
  return records;
14790
14971
  }
14791
- function mintlifyNavigation(body, base, locale) {
14792
- const $ = load3(body);
14972
+ function mintlifyNavigation(body, base, locale, selectedScope) {
14973
+ const $ = load5(body);
14974
+ const scope = inferUrlMigrationScope({ body, contentType: "text/html", finalUrl: base });
14975
+ const mount = scope.evidence === "source-parent" ? selectedScope ?? "/" : scope.scopePath;
14976
+ const configTarget = (value) => {
14977
+ const reference = text2(value);
14978
+ if (!reference) return void 0;
14979
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(reference)) return target(reference, base);
14980
+ const path = reference.replace(/^\/+/, "");
14981
+ const prefix = mount.replace(/^\/|\/$/g, "");
14982
+ return target(prefix && path !== prefix && !path.startsWith(`${prefix}/`) ? `${prefix}/${path}` : path, new URL("/", base));
14983
+ };
14793
14984
  let stream = "";
14794
14985
  const records = [];
14795
14986
  $("script").each((_index, script) => {
@@ -14826,7 +15017,7 @@ function mintlifyNavigation(body, base, locale) {
14826
15017
  const walk2 = (value, depth = 0, activeLocale) => {
14827
15018
  if (depth > MAX_DEPTH || ++count > MAX_NODES) throw new Error("Source navigation exceeds its safety bound.");
14828
15019
  if (typeof value === "string") {
14829
- const url2 = target(value, new URL("/", base));
15020
+ const url2 = configTarget(value);
14830
15021
  return url2 ? [{ kind: "page", label: value, url: url2 }] : [];
14831
15022
  }
14832
15023
  if (Array.isArray(value)) return value.flatMap((child) => walk2(child, depth + 1, activeLocale));
@@ -14846,7 +15037,7 @@ function mintlifyNavigation(body, base, locale) {
14846
15037
  return;
14847
15038
  }
14848
15039
  if (!object(node)) return;
14849
- const href = target(node.href, base);
15040
+ const href = configTarget(node.href);
14850
15041
  if (href) {
14851
15042
  populated++;
14852
15043
  const url2 = new URL(href);
@@ -14864,10 +15055,10 @@ function mintlifyNavigation(body, base, locale) {
14864
15055
  const selected = matching[0]?.item ?? languages.find((item) => text2(item.language)?.toLowerCase() === language?.toLowerCase()) ?? languages.find((item) => text2(item.language)?.toLowerCase() === "en") ?? languages[0];
14865
15056
  return selected ? walk2({ ...selected, languages: void 0 }, depth + 1, text2(selected.language)) : [];
14866
15057
  }
14867
- const label = text2(value.tab) ?? text2(value.group) ?? text2(value.title) ?? text2(value.label);
14868
- const kind = text2(value.tab) ? "tab" : text2(value.group) ? "group" : "page";
15058
+ const label = text2(value.tab) ?? text2(value.dropdown) ?? text2(value.group) ?? text2(value.sidebarTitle) ?? text2(value.title) ?? text2(value.label);
15059
+ const kind = text2(value.tab) || text2(value.dropdown) ? "tab" : text2(value.group) ? "group" : "page";
14869
15060
  const children = ["tabs", "groups", "pages", "dropdowns", "anchors", "versions"].flatMap((key) => Array.isArray(value[key]) ? walk2(value[key], depth + 1, activeLocale) : []);
14870
- const url = target(value.href, base);
15061
+ const url = configTarget(value.href);
14871
15062
  if (!label) return children;
14872
15063
  if (!url && !children.length) return [];
14873
15064
  return [{
@@ -14875,6 +15066,7 @@ function mintlifyNavigation(body, base, locale) {
14875
15066
  label,
14876
15067
  ...url ? { url } : {},
14877
15068
  ...children.length ? { children } : {},
15069
+ ...text2(value.dropdown) ? { presentation: "dropdown" } : {},
14878
15070
  ...kind === "tab" && activeLocale ? { locale: activeLocale } : {}
14879
15071
  }];
14880
15072
  };
@@ -14901,18 +15093,19 @@ function resolveNavigation(nodes, base) {
14901
15093
  ...url ? { url } : {},
14902
15094
  ...children.length ? { children } : {},
14903
15095
  ...["tab", "group", "page"].includes(node.kind ?? "") ? { kind: node.kind } : {},
15096
+ ...node.presentation === "dropdown" ? { presentation: "dropdown" } : {},
14904
15097
  ...text2(node.locale) ? { locale: text2(node.locale) } : {}
14905
15098
  }];
14906
15099
  });
14907
15100
  };
14908
15101
  return walk2(nodes);
14909
15102
  }
14910
- function extractSourceNavigation(capture) {
15103
+ function extractSourceNavigation(capture, selectedScope) {
14911
15104
  if (capture.navigation) return resolveNavigation(capture.navigation, capture.finalUrl);
14912
15105
  if (!/html/i.test(capture.contentType) || Buffer.byteLength(capture.body) > MAX_CAPTURE_BYTES) return [];
14913
- const mintlify = mintlifyNavigation(capture.body, capture.finalUrl, capture.locale);
15106
+ const mintlify = mintlifyNavigation(capture.body, capture.finalUrl, capture.locale, selectedScope);
14914
15107
  if (mintlify.length) return mintlify;
14915
- const $ = load3(capture.body);
15108
+ const $ = load5(capture.body);
14916
15109
  const candidates = ['.theme-doc-sidebar-menu,[aria-label*="sidebar" i]', "aside", '[role="navigation"],nav'];
14917
15110
  const root = candidates.map((selector) => $(selector).filter((_index, node) => $(node).find("a[href]").length > 0 && $(node).closest("header").length === 0).first()).find((candidate) => candidate.length);
14918
15111
  if (!root) return [];
@@ -14988,8 +15181,127 @@ function mergeSourceNavigation(existing, incoming) {
14988
15181
  return merge(existing, incoming);
14989
15182
  }
14990
15183
 
15184
+ // src/url-api-schema-controls.ts
15185
+ var MAX_CAPTURE_BYTES2 = 8e6;
15186
+ var MAX_TAB_GROUPS = 256;
15187
+ var MAX_TABS = 1024;
15188
+ function object2(value) {
15189
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
15190
+ }
15191
+ function localReference2(document, reference) {
15192
+ if (typeof reference !== "string" || !reference.startsWith("#/")) return void 0;
15193
+ let target2 = document;
15194
+ try {
15195
+ for (const part of reference.slice(2).split("/")) {
15196
+ const key = decodeURIComponent(part).replace(/~1/g, "/").replace(/~0/g, "~");
15197
+ if (!target2 || typeof target2 !== "object" || !Object.hasOwn(target2, key)) return void 0;
15198
+ target2 = target2[key];
15199
+ }
15200
+ } catch {
15201
+ return void 0;
15202
+ }
15203
+ return target2;
15204
+ }
15205
+ function resolve4(document, value) {
15206
+ const seen = /* @__PURE__ */ new Set();
15207
+ for (let depth = 0; depth < 32; depth += 1) {
15208
+ const schema = object2(value);
15209
+ if (!schema || seen.has(schema)) return void 0;
15210
+ seen.add(schema);
15211
+ if (!Object.hasOwn(schema, "$ref")) return schema;
15212
+ value = localReference2(document, schema.$ref);
15213
+ }
15214
+ return void 0;
15215
+ }
15216
+ function complete(document) {
15217
+ let visited = 0;
15218
+ const active = /* @__PURE__ */ new Set();
15219
+ const visit = (value, depth) => {
15220
+ if (++visited > 1e5 || depth > 96) return false;
15221
+ if (typeof value === "number" && !Number.isFinite(value)) return false;
15222
+ if (!value || typeof value !== "object") return true;
15223
+ if (active.has(value)) return false;
15224
+ active.add(value);
15225
+ const schema = object2(value);
15226
+ const valid = (!schema || !Object.hasOwn(schema, "$ref") || Boolean(object2(localReference2(document, schema.$ref)))) && Object.values(value).every((child) => visit(child, depth + 1));
15227
+ active.delete(value);
15228
+ return valid;
15229
+ };
15230
+ return visit(document, 0);
15231
+ }
15232
+ function labels(document, value) {
15233
+ const schema = resolve4(document, value);
15234
+ if (!Array.isArray(schema?.oneOf) || schema.oneOf.length < 2 || schema.oneOf.length > 64) return void 0;
15235
+ const result = [];
15236
+ for (const [index, branch] of schema.oneOf.entries()) {
15237
+ const target2 = resolve4(document, branch);
15238
+ if (!target2) return void 0;
15239
+ const title = object2(branch)?.title ?? target2.title;
15240
+ result.push(typeof title === "string" && title.trim() ? title.trim().replace(/\s+/g, " ") : `Option ${index + 1}`);
15241
+ }
15242
+ return new Set(result).size === result.length ? result : void 0;
15243
+ }
15244
+ function apiSchemaTabGroups(body, document, operation) {
15245
+ if (Buffer.byteLength(body) > MAX_CAPTURE_BYTES2 || !complete(document)) return [];
15246
+ const path = object2(document.paths)?.[operation.path];
15247
+ const method = operation.method.toLowerCase();
15248
+ if (!/^(?:get|post|put|patch|delete|head|options|trace)$/.test(method)) return [];
15249
+ const selected = object2(object2(path)?.[method]);
15250
+ if (!selected) return [];
15251
+ const { $, root } = migrationHtmlArticle(body);
15252
+ const groups = root.find('[role="tablist"],[data-component-part="tabs-list"]');
15253
+ if (groups.length > MAX_TAB_GROUPS || root.find('[role="tab"]').length > MAX_TABS) return [];
15254
+ const idCounts = /* @__PURE__ */ new Map();
15255
+ root.find("[id]").each((_index, node) => {
15256
+ const id = $(node).attr("id");
15257
+ idCounts.set(id, (idCounts.get(id) ?? 0) + 1);
15258
+ });
15259
+ const proofs = [];
15260
+ groups.each((index, node) => {
15261
+ const group = $(node);
15262
+ const container = group.parent(".tabs.tab-container");
15263
+ const field = group.closest(".object-param-field");
15264
+ const section = field.parent(".api-section");
15265
+ if (!group.is('[role="tablist"][data-component-part="tabs-list"]') || !container.length || !section.length || !section.closest('[id^="api-playground-"][id$="-operation-page"]').length) return;
15266
+ if (field.parents(".object-param-field").length) return;
15267
+ const heading = section.children(".api-section-heading");
15268
+ const title = heading.find(".api-section-heading-title").text().trim().toLowerCase();
15269
+ const media = heading.find("*").toArray().filter((element) => $(element).children().length === 0).map((element) => $(element).text().trim()).filter((text3) => /^[a-z][a-z\d.+-]*\/[a-z\d.+-]+$/i.test(text3));
15270
+ if (heading.length !== 1 || !media || media.length !== 1) return;
15271
+ let schema;
15272
+ let scope;
15273
+ if (title === "body") {
15274
+ const request2 = resolve4(document, selected.requestBody);
15275
+ schema = object2(object2(request2?.content)?.[media[0]])?.schema;
15276
+ scope = `request:${media[0]}`;
15277
+ } else if (title === "response") {
15278
+ const statuses = heading.find('button[aria-haspopup="menu"]').toArray().map((button) => $(button).text().trim()).filter((text3) => /^(?:[1-5]\d{2}|default)$/.test(text3));
15279
+ if (statuses.length !== 1) return;
15280
+ const response = resolve4(document, object2(selected.responses)?.[statuses[0]]);
15281
+ schema = object2(object2(response?.content)?.[media[0]])?.schema;
15282
+ scope = `response:${statuses[0]}:${media[0]}`;
15283
+ } else return;
15284
+ const expected = labels(document, schema);
15285
+ const tabs = group.children('[role="tab"][data-component-part="tab"]');
15286
+ if (!expected || tabs.length !== expected.length || group.children().length !== tabs.length) return;
15287
+ const panels = container.children('[data-component-part="tabs-panels"]').children('[role="tabpanel"][data-component-part="tab-content"]');
15288
+ if (panels.length !== tabs.length) return;
15289
+ const ids = /* @__PURE__ */ new Set();
15290
+ for (const [position, tab] of tabs.toArray().entries()) {
15291
+ const control = $(tab).attr("aria-controls");
15292
+ const panel = panels.eq(position);
15293
+ if ($(tab).text().trim().replace(/\s+/g, " ") !== expected[position] || !control || ids.has(control) || panel.attr("id") !== control || !$(tab).attr("id") || panel.attr("aria-labelledby") !== $(tab).attr("id") || idCounts.get(control) !== 1 || panel.text().trim() || panel.children().length) return;
15294
+ ids.add(control);
15295
+ }
15296
+ proofs.push({ index, scope });
15297
+ });
15298
+ const scopeCounts = /* @__PURE__ */ new Map();
15299
+ for (const proof of proofs) scopeCounts.set(proof.scope, (scopeCounts.get(proof.scope) ?? 0) + 1);
15300
+ return proofs.filter((proof) => scopeCounts.get(proof.scope) === 1).map((proof) => proof.index);
15301
+ }
15302
+
14991
15303
  // src/url-run.ts
14992
- var CONVERTER_VERSION = "url-evidence-v4";
15304
+ var CONVERTER_VERSION = "url-evidence-v5";
14993
15305
  var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
14994
15306
  var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
14995
15307
  var MAX_RESPONSE_BYTES = 8e6;
@@ -15052,7 +15364,7 @@ function linksOf(document) {
15052
15364
  return [...root.find("a[href]").toArray(), ...$('link[rel="sitemap"][href],link[rel="alternate"][href]').toArray()].map((node) => $(node).attr("href")).filter(Boolean);
15053
15365
  }
15054
15366
  if (/xml/i.test(document.contentType) || /<urlset|<sitemapindex/i.test(document.body)) {
15055
- const $ = load4(document.body, { xmlMode: true });
15367
+ const $ = load6(document.body, { xmlMode: true });
15056
15368
  return $("loc").toArray().map((node) => $(node).text().trim());
15057
15369
  }
15058
15370
  return [
@@ -15095,8 +15407,8 @@ function markdownHeadingAnchors(body) {
15095
15407
  return occurrence ? `${base}-${occurrence}` : base;
15096
15408
  });
15097
15409
  }
15098
- function sourceNavigation(capture) {
15099
- return extractSourceNavigation(capture);
15410
+ function sourceNavigation(capture, selectedScope) {
15411
+ return extractSourceNavigation(capture, selectedScope);
15100
15412
  }
15101
15413
  function idForUrl(url, scope) {
15102
15414
  const relative7 = scope === "/" ? url.pathname.slice(1) : url.pathname.slice(scope.length).replace(/^\//, "");
@@ -15105,7 +15417,7 @@ function idForUrl(url, scope) {
15105
15417
  }
15106
15418
  function articleIdentity(document) {
15107
15419
  if (!/html/i.test(document.contentType)) return document.finalUrl;
15108
- const $ = load4(document.body);
15420
+ const $ = load6(document.body);
15109
15421
  const reference = $('link[rel="canonical"][href]').first().attr("href");
15110
15422
  if (!reference) return document.finalUrl;
15111
15423
  try {
@@ -15161,16 +15473,24 @@ function assetReferences(capture) {
15161
15473
  }
15162
15474
  const { $, root } = migrationHtmlArticle(capture.body);
15163
15475
  return [
15164
- ...root.find("img[src],video[poster],a[download][href]").toArray().map((node) => $(node).attr("src") ?? $(node).attr("poster") ?? $(node).attr("href")),
15476
+ ...root.find("img[src],video[poster],a[download][href]").toArray().map((node) => $(node).is("video") ? $(node).attr("poster") : $(node).is("a") ? $(node).attr("href") : $(node).attr("src")),
15165
15477
  ...root.find("[srcset]").toArray().flatMap((node) => migrationImageCandidates($(node).attr("srcset") ?? "", capture.finalUrl).map((candidate) => candidate.url))
15166
15478
  ].filter(Boolean);
15167
15479
  }
15168
- function unresolvedStaticControls(body, hasApiSchema = false) {
15480
+ function unresolvedStaticControls(body, hasApiSchema = false, coveredSchemaGroups = []) {
15169
15481
  const { $, root } = migrationHtmlArticle(body);
15482
+ root.find('[role="tablist"],[data-component-part="tabs-list"]').each((index, node) => {
15483
+ if (coveredSchemaGroups.includes(index)) $(node).remove();
15484
+ });
15170
15485
  const tabs = root.find('[role="tab"]').toArray();
15171
15486
  for (const tab of tabs) {
15172
15487
  const controls = $(tab).attr("aria-controls");
15173
- const panels = controls ? root.find("[id]").filter((_index, node) => $(node).attr("id") === controls) : root.find('[role="tabpanel"]').eq(tabs.indexOf(tab));
15488
+ const group = $(tab).closest('[role="tablist"]');
15489
+ const container = group.closest(".code-group,.tabs-container,.tabs.tab-container");
15490
+ const peers = group.find('[role="tab"]').toArray().filter((node) => $(node).closest('[role="tablist"]')[0] === group[0]);
15491
+ const localPanels = container.find('[role="tabpanel"]').toArray().filter((node) => $(node).closest(".code-group,.tabs-container,.tabs.tab-container")[0] === container[0]);
15492
+ const owners = container.find('[role="tablist"]').toArray().filter((node) => $(node).closest(".code-group,.tabs-container,.tabs.tab-container")[0] === container[0]);
15493
+ const panels = controls ? root.find("[id]").filter((_index, node) => $(node).attr("id") === controls) : owners.length === 1 && peers.length === localPanels.length ? $(localPanels).eq(peers.indexOf(tab)) : $([]);
15174
15494
  if (panels.length !== 1 || !panels.text().trim() && !panels.find("pre,img,table,svg").length || panels.find('[aria-busy="true"],[role="progressbar"]').length) return true;
15175
15495
  }
15176
15496
  return root.find("details:not([open])").toArray().some((node) => {
@@ -15221,12 +15541,12 @@ function hasCompleteLocalApiSchema(document) {
15221
15541
  if (typeof value === "number" && !Number.isFinite(value)) return false;
15222
15542
  if (!value || typeof value !== "object") return true;
15223
15543
  if (Array.isArray(value)) return value.every((child) => visit(child, depth + 1));
15224
- const object2 = value;
15225
- if (Object.prototype.hasOwnProperty.call(object2, "$ref")) {
15226
- if (typeof object2.$ref !== "string" || !object2.$ref.startsWith("#/")) return false;
15544
+ const object3 = value;
15545
+ if (Object.prototype.hasOwnProperty.call(object3, "$ref")) {
15546
+ if (typeof object3.$ref !== "string" || !object3.$ref.startsWith("#/")) return false;
15227
15547
  let target2 = document;
15228
15548
  try {
15229
- for (const encoded of object2.$ref.slice(2).split("/")) {
15549
+ for (const encoded of object3.$ref.slice(2).split("/")) {
15230
15550
  const key = decodeURIComponent(encoded).replace(/~1/g, "/").replace(/~0/g, "~");
15231
15551
  if (!target2 || typeof target2 !== "object" || !Object.prototype.hasOwnProperty.call(target2, key)) return false;
15232
15552
  target2 = target2[key];
@@ -15235,7 +15555,7 @@ function hasCompleteLocalApiSchema(document) {
15235
15555
  return false;
15236
15556
  }
15237
15557
  }
15238
- return Object.values(object2).every((child) => visit(child, depth + 1));
15558
+ return Object.values(object3).every((child) => visit(child, depth + 1));
15239
15559
  };
15240
15560
  return visit(document);
15241
15561
  }
@@ -15250,6 +15570,25 @@ function resolveMigrationRecord(records, record) {
15250
15570
  }
15251
15571
  function navigationConfig(checkpoint, records) {
15252
15572
  const navigation = checkpoint.navigation;
15573
+ const shortcuts = [];
15574
+ const collectShortcuts = (nodes) => {
15575
+ for (const node of nodes) {
15576
+ if (node.url) {
15577
+ const record = records.get(canonical(node.url, checkpoint.sourceUrl) ?? "");
15578
+ const href = record?.redirectTo ?? node.url;
15579
+ const identity = canonical(href, checkpoint.sourceUrl);
15580
+ const target2 = identity ? new URL(href, checkpoint.sourceUrl).href : null;
15581
+ if (target2 && !inScope(new URL(target2), new URL(checkpoint.sourceUrl), checkpoint.scopePath) && !shortcuts.some((link) => link.label === node.label && link.href === target2)) shortcuts.push({ label: node.label, href: target2 });
15582
+ }
15583
+ collectShortcuts(node.children ?? []);
15584
+ }
15585
+ };
15586
+ collectShortcuts(navigation);
15587
+ const hasDropdown = navigation.some((node) => node.presentation === "dropdown");
15588
+ const externalNavigation = shortcuts.length || hasDropdown ? { navigation: {
15589
+ ...shortcuts.length ? { shortcuts } : {},
15590
+ ...hasDropdown ? { display: "dropdown" } : {}
15591
+ } } : {};
15253
15592
  const projection = localeProjection(checkpoint);
15254
15593
  const project2 = (nodes) => nodes.flatMap((node) => {
15255
15594
  const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
@@ -15270,7 +15609,7 @@ function navigationConfig(checkpoint, records) {
15270
15609
  return pages2.length || tab.url ? [{ tab: tab.label, ...pages2.length ? { pages: pages2 } : {}, ...tab.url ? { href: tab.url } : {} }] : [];
15271
15610
  });
15272
15611
  const primary = localizedTabs(projection.defaultLocale);
15273
- return { tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
15612
+ return { ...externalNavigation, tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
15274
15613
  i18n: {
15275
15614
  defaultLocale: projection.defaultLocale,
15276
15615
  locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })),
@@ -15279,7 +15618,7 @@ function navigationConfig(checkpoint, records) {
15279
15618
  } : {} };
15280
15619
  }
15281
15620
  const pages = project2(navigation);
15282
- return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
15621
+ return pages.length || shortcuts.length ? { ...externalNavigation, tabs: [{ tab: "Documentation", pages }] } : null;
15283
15622
  }
15284
15623
  function canonicalLanguage(value) {
15285
15624
  if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
@@ -15299,7 +15638,7 @@ function observedLocale(document, id, scopePath) {
15299
15638
  }
15300
15639
  const urlLocale = [id.split("/", 1)[0], ...scopePath.split("/"), document.finalUrl.pathname.split("/").filter(Boolean)[0]].find((segment) => segment && LOCALES.has(segment.toLowerCase()));
15301
15640
  if (urlLocale) candidates.push({ source: "URL", value: canonicalLanguage(urlLocale) });
15302
- const declared = /html/i.test(document.contentType) ? load4(document.body)("html").attr("lang")?.trim() : void 0;
15641
+ const declared = /html/i.test(document.contentType) ? load6(document.body)("html").attr("lang")?.trim() : void 0;
15303
15642
  if (declared) {
15304
15643
  const locale = canonicalLanguage(declared);
15305
15644
  if (locale) candidates.push({ source: "html lang", value: locale });
@@ -15376,13 +15715,15 @@ function validateUrlMigration(checkpoint, cancelled = false) {
15376
15715
  const projection = localeProjection(checkpoint);
15377
15716
  const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!projection.canonicalLocale(record.page) || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [projection.navigationId(record.page)] : []));
15378
15717
  const outputIds = /* @__PURE__ */ new Set();
15379
- const resolve4 = (url) => {
15718
+ const resolve5 = (url) => {
15380
15719
  const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
15381
15720
  return resolveMigrationRecord(records, record);
15382
15721
  };
15383
15722
  for (const record of checkpoint.records) {
15384
15723
  counts[record.outcome]++;
15385
15724
  issues.push(...record.issues ?? []);
15725
+ warnings.push(...record.warnings ?? []);
15726
+ if (record.outcome === "alias" && !resolveMigrationRecord(records, record)?.page) issues.push({ code: "missing-page", source: record.url, message: "An alias does not resolve to an imported source page." });
15386
15727
  if (record.outcome === "pending" || record.outcome === "failed") issues.push({ code: "missing-page", source: record.url, message: record.reason ?? `Inventory entry is ${record.outcome}.` });
15387
15728
  if (!record.page || !record.evidence) continue;
15388
15729
  const outputId = projection.pageId(record.page);
@@ -15401,8 +15742,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
15401
15742
  continue;
15402
15743
  }
15403
15744
  if (!inScope(link, source, checkpoint.scopePath)) continue;
15745
+ if (isDiscoveryIndex(link, source)) continue;
15404
15746
  if (/\.(?:png|jpe?g|gif|svg|webp|avif|pdf|zip|mp4|webm|css|js|json|ya?ml)$/i.test(link.pathname)) continue;
15405
- const destination = resolve4(link.toString());
15747
+ const destination = resolve5(link.toString());
15406
15748
  if (!destination || !["imported", "excluded"].includes(destination.outcome)) {
15407
15749
  issues.push({ code: "missing-page", source: record.url, message: `Unresolved internal destination: ${link}` });
15408
15750
  } else if (link.hash && destination.outcome === "imported") {
@@ -15429,7 +15771,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
15429
15771
  if (node.url) {
15430
15772
  const url = new URL(node.url, source);
15431
15773
  if (inScope(url, source, checkpoint.scopePath)) {
15432
- const page = resolve4(url.toString())?.page;
15774
+ const destination = resolve5(url.toString());
15775
+ if (destination?.outcome === "excluded" && destination.redirectTo) continue;
15776
+ const page = destination?.page;
15433
15777
  if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
15434
15778
  else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
15435
15779
  issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
@@ -15446,16 +15790,32 @@ function validateUrlMigration(checkpoint, cancelled = false) {
15446
15790
  async function runUrlMigration(options) {
15447
15791
  const source = validateMigrationUrl(options.sourceUrl);
15448
15792
  const sourceUrl = canonical(source.toString(), source.toString());
15449
- const requestedScope = options.scopePath ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
15450
- let scopeEnd = requestedScope.length;
15451
- while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
15452
- const scopePath = requestedScope.slice(0, scopeEnd) || "/";
15453
- if (!scopePath.startsWith("/") || scopePath.includes("?") || scopePath.includes("#") || !inScope(source, source, scopePath)) throw new Error("The selected documentation scope must contain the source URL.");
15454
15793
  const maxPages = options.maxPages ?? 1e3;
15455
15794
  if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 1e4) throw new Error("maxPages must be between 1 and 10000.");
15456
15795
  const concurrency = Math.max(1, Math.min(32, Math.floor(options.concurrency ?? 5)));
15457
15796
  const batchSize = Math.max(1, Math.min(500, Math.floor(options.batchSize ?? 50)));
15458
15797
  if (!Number.isFinite(concurrency) || !Number.isFinite(batchSize) || options.maxCaptures !== void 0 && (!Number.isInteger(options.maxCaptures) || options.maxCaptures < 1) || options.maxTotalBytes !== void 0 && (!Number.isFinite(options.maxTotalBytes) || options.maxTotalBytes < 1)) throw new Error("Migration batch, concurrency and byte limits must be positive finite numbers.");
15798
+ const fetcher = options.fetcher ?? defaultMigrationFetcher;
15799
+ const acquire = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
15800
+ let initial;
15801
+ let inferredScope;
15802
+ if (options.scopePath === void 0 && !options.checkpoint && !options.signal?.aborted) {
15803
+ initial = Promise.resolve().then(() => acquire({ url: source, signal: options.signal }));
15804
+ try {
15805
+ const document = await initial;
15806
+ if (document.finalUrl.origin === source.origin) {
15807
+ const inferred = inferUrlMigrationScope(document).scopePath;
15808
+ if (inScope(source, source, inferred)) inferredScope = inferred;
15809
+ }
15810
+ } catch {
15811
+ }
15812
+ }
15813
+ const capture = (request2) => initial && canonical(request2.url.href, sourceUrl) === sourceUrl ? initial : acquire(request2);
15814
+ const requestedScope = options.scopePath ?? options.checkpoint?.scopePath ?? inferredScope ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
15815
+ let scopeEnd = requestedScope.length;
15816
+ while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
15817
+ const scopePath = requestedScope.slice(0, scopeEnd) || "/";
15818
+ if (!scopePath.startsWith("/") || scopePath.includes("?") || scopePath.includes("#") || !inScope(source, source, scopePath)) throw new Error("The selected documentation scope must contain the source URL.");
15459
15819
  const captureVersion = options.captureVersion ?? "fetch-v1";
15460
15820
  if (options.checkpoint && options.checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
15461
15821
  const checkpoint = options.checkpoint ? cloneUrlMigrationCheckpoint(options.checkpoint) : { schemaVersion: 1, converterVersion: CONVERTER_VERSION, sourceUrl, scopePath, captureVersion, records: [], navigation: [] };
@@ -15474,6 +15834,8 @@ async function runUrlMigration(options) {
15474
15834
  delete record.apiSpecification;
15475
15835
  delete record.version;
15476
15836
  delete record.issues;
15837
+ delete record.warnings;
15838
+ delete record.redirectTo;
15477
15839
  delete record.reason;
15478
15840
  delete record.aliasOf;
15479
15841
  }
@@ -15486,6 +15848,7 @@ async function runUrlMigration(options) {
15486
15848
  const enqueue = (value, base, discoveredBy) => {
15487
15849
  const url = canonical(value, base);
15488
15850
  if (!url || !inScope(new URL(url), source, scopePath) && !isDiscoveryIndex(new URL(url), source)) return;
15851
+ const isAttachment = discoveredBy !== "submitted" && /\.(?:json|ya?ml)$/i.test(new URL(url).pathname);
15489
15852
  const existing = records.get(url);
15490
15853
  if (existing) {
15491
15854
  if (!existing.discoveredBy.includes(discoveredBy)) {
@@ -15495,10 +15858,15 @@ async function runUrlMigration(options) {
15495
15858
  return;
15496
15859
  }
15497
15860
  if (records.size >= MAX_RECORDS) throw new Error("Discovered URL inventory exceeded the 100000 URL safety bound.");
15498
- const record = { url, discoveredBy: [discoveredBy], outcome: "pending" };
15861
+ const record = {
15862
+ url,
15863
+ discoveredBy: [discoveredBy],
15864
+ outcome: isAttachment ? "excluded" : "pending",
15865
+ ...isAttachment ? { reason: "Machine-readable attachment, not an authored article." } : {}
15866
+ };
15499
15867
  records.set(url, record);
15500
15868
  checkpoint.records.push(record);
15501
- pending.push(record);
15869
+ if (!isAttachment) pending.push(record);
15502
15870
  changed.push(record);
15503
15871
  };
15504
15872
  enqueue(sourceUrl, sourceUrl, "submitted");
@@ -15531,8 +15899,6 @@ async function runUrlMigration(options) {
15531
15899
  }
15532
15900
  }
15533
15901
  enqueueNavigation(checkpoint.navigation, sourceUrl);
15534
- const fetcher = options.fetcher ?? defaultMigrationFetcher;
15535
- const capture = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
15536
15902
  const assetFetcher = options.assetFetcher ?? (!options.fetcher && !options.captureProvider ? defaultAssetFetcher : void 0);
15537
15903
  const usedIds = new Map([...records.values()].filter((record) => record.page).map((record) => [record.page.id, record.url]));
15538
15904
  const finalUrls = new Map([...records.values()].filter((record) => record.evidence && record.page).map((record) => [canonical(record.evidence.finalUrl, sourceUrl), record.url]));
@@ -15540,6 +15906,10 @@ async function runUrlMigration(options) {
15540
15906
  const assetPayloads = new Map(checkpoint.records.flatMap((record) => (record.assets ?? []).map((asset) => [asset.sha256, asset])));
15541
15907
  const assetBytes = /* @__PURE__ */ new Map();
15542
15908
  const retainAsset = (asset) => {
15909
+ if (asset.unavailable) {
15910
+ if ([404, 410].includes(asset.unavailable.status) && asset.content.byteLength === 0) return asset;
15911
+ throw new Error("Invalid source asset availability evidence.");
15912
+ }
15543
15913
  if (!acceptedAsset(asset)) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
15544
15914
  const sha256 = hash2(asset.content);
15545
15915
  const content = assetBytes.get(sha256) ?? Buffer.from(asset.content);
@@ -15572,6 +15942,11 @@ async function runUrlMigration(options) {
15572
15942
  let markdownRepresentation;
15573
15943
  try {
15574
15944
  delete record.reason;
15945
+ if (/\/llms-full\.txt$/i.test(new URL(record.url).pathname)) {
15946
+ record.outcome = "excluded";
15947
+ record.reason = "Aggregate documentation corpus; individual articles are inventoried separately.";
15948
+ return;
15949
+ }
15575
15950
  delete record.apiSpecification;
15576
15951
  const represented = resolveMigrationRecord(records, record.representationOf ? records.get(record.representationOf) : void 0);
15577
15952
  if (represented?.outcome === "imported") {
@@ -15581,6 +15956,16 @@ async function runUrlMigration(options) {
15581
15956
  }
15582
15957
  const document = await capture({ url: new URL(record.url), signal: options.signal });
15583
15958
  if (options.signal?.aborted) return;
15959
+ if (document.status && document.status >= 300 && document.status < 400 && document.headers?.location) {
15960
+ const destination = canonical(document.headers.location, document.finalUrl.href);
15961
+ if (destination && new URL(destination).origin !== source.origin) {
15962
+ record.outcome = "excluded";
15963
+ record.redirectTo = new URL(document.headers.location, document.finalUrl).href;
15964
+ record.reason = "The source entry is a redirect to an external public resource, retained as a link.";
15965
+ return;
15966
+ }
15967
+ throw new Error("Capture returned an unresolved redirect.");
15968
+ }
15584
15969
  if (Buffer.byteLength(document.body) > MAX_RESPONSE_BYTES) throw new Error("Captured response exceeds the 8 MB safety bound.");
15585
15970
  const identity = articleIdentity(document);
15586
15971
  const final = canonical(identity.toString(), sourceUrl);
@@ -15588,6 +15973,7 @@ async function runUrlMigration(options) {
15588
15973
  if (!inScope(document.finalUrl, source, scopePath) && !(role === "index" && isDiscoveryIndex(document.finalUrl, source))) {
15589
15974
  record.outcome = "excluded";
15590
15975
  record.reason = "Redirected outside the selected origin/path.";
15976
+ if (document.finalUrl.origin === source.origin && (!document.status || document.status >= 200 && document.status < 300)) record.redirectTo = document.finalUrl.href;
15591
15977
  return;
15592
15978
  }
15593
15979
  if (document.status && document.status >= 400) throw new Error(`Source page returned HTTP ${document.status}.`);
@@ -15605,7 +15991,7 @@ async function runUrlMigration(options) {
15605
15991
  return;
15606
15992
  }
15607
15993
  if (/html/i.test(document.contentType)) {
15608
- const $ = load4(document.body);
15994
+ const $ = load6(document.body);
15609
15995
  $('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
15610
15996
  const alternate = canonical($(node).attr("href"), final);
15611
15997
  if (!alternate) return;
@@ -15619,7 +16005,7 @@ async function runUrlMigration(options) {
15619
16005
  changed.push(sibling);
15620
16006
  });
15621
16007
  }
15622
- const observedNavigation = sourceNavigation(document);
16008
+ const observedNavigation = sourceNavigation(document, scopePath);
15623
16009
  if (observedNavigation.length && !(checkpoint.navigation.some((node) => node.kind === "tab") && !observedNavigation.some((node) => node.kind === "tab"))) {
15624
16010
  const merged = mergeSourceNavigation(checkpoint.navigation, observedNavigation);
15625
16011
  if (JSON.stringify(merged) !== JSON.stringify(checkpoint.navigation)) {
@@ -15661,6 +16047,7 @@ async function runUrlMigration(options) {
15661
16047
  record.reason = "Content could not be represented as safe inert MDX.";
15662
16048
  return;
15663
16049
  }
16050
+ let coveredSchemaGroups = [];
15664
16051
  const hasSchemaChoice = /html/i.test(document.contentType) && migrationHtmlArticle(document.body).root.find('select[aria-label="Select schema type"],select[aria-label="\u9009\u62E9\u67B6\u6784\u7C7B\u578B"]').length > 0;
15665
16052
  if (/html/i.test(document.contentType) && markdownRepresentation && (unresolvedStaticControls(document.body) || hasSchemaChoice)) {
15666
16053
  const schemaCapture = await capture({ url: new URL(markdownRepresentation), signal: options.signal });
@@ -15676,6 +16063,7 @@ async function runUrlMigration(options) {
15676
16063
  const path = `openapi/migration-${sha256}.json`;
15677
16064
  record.apiSpecification = { path, sha256, document: fragment.document };
15678
16065
  page.openapi = `${path} ${fragment.method} ${fragment.path}`;
16066
+ coveredSchemaGroups = apiSchemaTabGroups(document.body, fragment.document, fragment);
15679
16067
  }
15680
16068
  }
15681
16069
  const states = document.states ?? [];
@@ -15705,17 +16093,24 @@ ${difference}`;
15705
16093
  }
15706
16094
  record.assets = [];
15707
16095
  record.issues = [];
16096
+ record.warnings = [];
15708
16097
  if (/html/i.test(document.contentType)) {
15709
16098
  const { $, root } = migrationHtmlArticle(document.body);
15710
16099
  root.find("nav,footer,aside,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
15711
16100
  root.find('button svg,a.hash-link svg,[aria-hidden="true"] svg,svg[aria-hidden="true"],svg[data-lucide],svg[data-icon]').remove();
16101
+ root.find("video,iframe").each((_index, node) => {
16102
+ if (projectMigrationMedia($.html(node), document.finalUrl) !== null) {
16103
+ record.warnings.push({ code: "source-asset-warning", source: record.url, message: "Embedded video is preserved at its original public media URL; playback depends on the original host." });
16104
+ $(node).remove();
16105
+ }
16106
+ });
15712
16107
  if (record.apiSpecification) {
15713
16108
  root.find('select[aria-label="Select schema type"],select[aria-label="\u9009\u62E9\u67B6\u6784\u7C7B\u578B"]').remove();
15714
16109
  root.find('.option-dropdown svg[data-component-part="icon-svg"]').remove();
15715
16110
  }
15716
16111
  const unsupported = [...new Set(root.find("iframe,object,embed,canvas,svg,form,input,textarea,select,math,video,audio").toArray().map((node) => node.tagName))];
15717
16112
  if (unsupported.length) record.issues.push({ code: "unsupported-component", source: record.url, message: `Rendered content requires explicit review for: ${unsupported.join(", ")}.` });
15718
- if (!states.length && unresolvedStaticControls(document.body, Boolean(record.apiSpecification))) record.issues.push({ code: "unsupported-component", source: record.url, message: "An interactive alternative has no complete captured panel or disclosure content." });
16113
+ if (!states.length && unresolvedStaticControls(document.body, Boolean(record.apiSpecification), coveredSchemaGroups)) record.issues.push({ code: "unsupported-component", source: record.url, message: "An interactive alternative has no complete captured panel or disclosure content." });
15719
16114
  }
15720
16115
  const suppliedAssets = new Map((document.assets ?? []).map((asset) => [canonical(asset.url, final), asset]));
15721
16116
  const assetUrls = [...new Set([...assetReferences(document), ...states.flatMap((state) => assetReferences(state.response))].map((url) => canonical(url, final)).filter((url) => Boolean(url)))];
@@ -15729,6 +16124,11 @@ ${difference}`;
15729
16124
  const supplied = suppliedAssets.get(url);
15730
16125
  const asset = supplied ? retainAsset(supplied) : assetRequest ? await assetRequest : null;
15731
16126
  if (!asset) throw new Error("Capture host did not supply asset bytes or a binary fetch boundary.");
16127
+ if (asset.unavailable && [404, 410].includes(asset.unavailable.status) && asset.content.byteLength === 0) {
16128
+ record.warnings.push({ code: "asset-failed", source: record.url, message: `The original source asset is unavailable (HTTP ${asset.unavailable.status}); its original URL was retained: ${url}` });
16129
+ page.body = rewriteMarkdownDestinations(page.body, (destination) => canonical(destination, final) === url ? url : destination);
16130
+ continue;
16131
+ }
15732
16132
  const extension = acceptedAsset(asset);
15733
16133
  if (!extension) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
15734
16134
  const sha256 = hash2(asset.content);
@@ -15770,7 +16170,7 @@ ${difference}`;
15770
16170
  } catch (error) {
15771
16171
  if (options.signal?.aborted) return;
15772
16172
  const wasImported = record.outcome === "imported";
15773
- record.outcome = record.discoveredBy.every((origin) => origin === "discovery-index") ? "excluded" : "failed";
16173
+ record.outcome = isDiscoveryIndex(new URL(record.url), source) || record.discoveredBy.every((origin) => origin === "discovery-index") ? "excluded" : "failed";
15774
16174
  record.reason = error instanceof Error ? error.message : String(error);
15775
16175
  if (record.page) {
15776
16176
  if (wasImported) imported--;
@@ -15795,7 +16195,7 @@ ${difference}`;
15795
16195
  for (const record of records.values()) {
15796
16196
  if (!record.representationOf) continue;
15797
16197
  const primary = resolveMigrationRecord(records, records.get(record.representationOf));
15798
- if (!primary) continue;
16198
+ if (!primary || primary === record) continue;
15799
16199
  const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
15800
16200
  if (!winner) continue;
15801
16201
  const alternate = winner === primary ? record : primary;
@@ -15921,7 +16321,7 @@ function rewriteMarkdownDestinations(body, rewrite) {
15921
16321
  return { start: start + index, end: start + index + node.url.length, replacement };
15922
16322
  }).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
15923
16323
  for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
15924
- if (!attribute.name || !["src", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
16324
+ if (!attribute.name || !["src", "poster", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
15925
16325
  let value = attribute.value;
15926
16326
  if (typeof value !== "string") {
15927
16327
  try {
@@ -15982,7 +16382,7 @@ async function getPinnedAddress(url) {
15982
16382
  }
15983
16383
  async function requestPinnedSpec(url) {
15984
16384
  const pinned = await getPinnedAddress(url);
15985
- return new Promise((resolve4, reject) => {
16385
+ return new Promise((resolve5, reject) => {
15986
16386
  const req = request(url, {
15987
16387
  headers: { Accept: "application/json, application/yaml, text/yaml, */*", "User-Agent": "Thally-Migrate/1.0 (+https://thally.io)" },
15988
16388
  signal: AbortSignal.timeout(3e4),
@@ -15996,7 +16396,7 @@ async function requestPinnedSpec(url) {
15996
16396
  const status = response.statusCode ?? 0;
15997
16397
  if (status >= 300 && status < 400) {
15998
16398
  response.resume();
15999
- resolve4({ status, location: response.headers.location, body: Buffer.alloc(0) });
16399
+ resolve5({ status, location: response.headers.location, body: Buffer.alloc(0) });
16000
16400
  return;
16001
16401
  }
16002
16402
  if (status < 200 || status >= 300) {
@@ -16020,7 +16420,7 @@ async function requestPinnedSpec(url) {
16020
16420
  }
16021
16421
  chunks.push(chunk);
16022
16422
  });
16023
- response.on("end", () => resolve4({ status, body: Buffer.concat(chunks) }));
16423
+ response.on("end", () => resolve5({ status, body: Buffer.concat(chunks) }));
16024
16424
  response.on("error", reject);
16025
16425
  });
16026
16426
  req.on("error", reject);