@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/README.md +17 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +628 -228
- package/dist/index.js.map +1 -1
- package/dist/url-migration.d.ts +11 -1
- package/dist/url-migration.js +468 -67
- package/dist/url-migration.js.map +1 -1
- package/dist/{url-migration-peVzTLjL.d.ts → url-run-BOZvcGs1.d.ts} +11 -1
- package/package.json +1 -1
package/dist/url-migration.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/url-run.ts
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
|
-
import { load as
|
|
3
|
+
import { load as load5 } from "cheerio";
|
|
4
4
|
import remarkParse3 from "remark-parse";
|
|
5
5
|
import remarkMdx3 from "remark-mdx";
|
|
6
6
|
import { unified as unified3 } from "unified";
|
|
@@ -531,7 +531,53 @@ function cloneUrlMigrationCheckpoint(checkpoint) {
|
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
// src/url-navigation.ts
|
|
534
|
+
import { load as load2 } from "cheerio";
|
|
535
|
+
|
|
536
|
+
// src/url-scope.ts
|
|
534
537
|
import { load } from "cheerio";
|
|
538
|
+
function inferUrlMigrationScope(capture) {
|
|
539
|
+
const source = capture.finalUrl;
|
|
540
|
+
let end = source.pathname.length;
|
|
541
|
+
while (end > 0 && source.pathname.charCodeAt(end - 1) === 47) end--;
|
|
542
|
+
const parent = end < source.pathname.length ? source.pathname.slice(0, end) || "/" : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/";
|
|
543
|
+
if (!/html/i.test(capture.contentType) || Buffer.byteLength(capture.body) > 8e6) return { scopePath: parent, evidence: "source-parent" };
|
|
544
|
+
const $ = load(capture.body);
|
|
545
|
+
const ancestor = (path) => path === "/" || source.pathname === path || source.pathname.startsWith(`${path}/`);
|
|
546
|
+
const observed = (reference) => {
|
|
547
|
+
if (!reference || reference.length > 8192) return void 0;
|
|
548
|
+
try {
|
|
549
|
+
const url = new URL(reference, source);
|
|
550
|
+
if (url.origin !== source.origin || url.username || url.password || url.search || url.hash || /%2f|%5c/i.test(url.pathname)) return void 0;
|
|
551
|
+
return url;
|
|
552
|
+
} catch {
|
|
553
|
+
return void 0;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const indexes = [];
|
|
557
|
+
$("link[href],a[href]").slice(0, 1e4).each((_, node) => {
|
|
558
|
+
const url = observed($(node).attr("href"));
|
|
559
|
+
if (!url || !/\/(?:llms(?:-full)?\.txt|sitemap(?:[-_][^/]*)?\.xml)$/i.test(url.pathname)) return;
|
|
560
|
+
const path = url.pathname.slice(0, url.pathname.lastIndexOf("/")) || "/";
|
|
561
|
+
if (ancestor(path)) indexes.push(path);
|
|
562
|
+
});
|
|
563
|
+
if (indexes.length) return { scopePath: indexes.sort((a, b) => b.length - a.length)[0], evidence: "discovery-index" };
|
|
564
|
+
const paths = [];
|
|
565
|
+
$('nav a[href],aside a[href],[role="navigation"] a[href]').slice(0, 1e4).each((_, node) => {
|
|
566
|
+
if ($(node).closest("header,footer").length) return;
|
|
567
|
+
const url = observed($(node).attr("href"));
|
|
568
|
+
if (url && !/\.[a-z0-9]+$/i.test(url.pathname)) paths.push(url.pathname);
|
|
569
|
+
});
|
|
570
|
+
if (new Set(paths).size >= 2 && paths.some((path) => path === source.pathname)) {
|
|
571
|
+
const segments = paths.map((path) => path.split("/").filter(Boolean));
|
|
572
|
+
let count = 0;
|
|
573
|
+
while (segments.every((parts) => parts[count] !== void 0 && parts[count] === segments[0][count])) count++;
|
|
574
|
+
const common = "/" + segments[0].slice(0, count).join("/");
|
|
575
|
+
if (ancestor(common)) return { scopePath: common, evidence: "navigation" };
|
|
576
|
+
}
|
|
577
|
+
return { scopePath: parent, evidence: "source-parent" };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/url-navigation.ts
|
|
535
581
|
var MAX_CAPTURE_BYTES = 8e6;
|
|
536
582
|
var MAX_JSON_BYTES = 2e6;
|
|
537
583
|
var MAX_NODES = 1e4;
|
|
@@ -598,8 +644,18 @@ function flightRecords(stream) {
|
|
|
598
644
|
}
|
|
599
645
|
return records;
|
|
600
646
|
}
|
|
601
|
-
function mintlifyNavigation(body, base, locale) {
|
|
602
|
-
const $ =
|
|
647
|
+
function mintlifyNavigation(body, base, locale, selectedScope) {
|
|
648
|
+
const $ = load2(body);
|
|
649
|
+
const scope = inferUrlMigrationScope({ body, contentType: "text/html", finalUrl: base });
|
|
650
|
+
const mount = scope.evidence === "source-parent" ? selectedScope ?? "/" : scope.scopePath;
|
|
651
|
+
const configTarget = (value) => {
|
|
652
|
+
const reference = text(value);
|
|
653
|
+
if (!reference) return void 0;
|
|
654
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(reference)) return target(reference, base);
|
|
655
|
+
const path = reference.replace(/^\/+/, "");
|
|
656
|
+
const prefix = mount.replace(/^\/|\/$/g, "");
|
|
657
|
+
return target(prefix && path !== prefix && !path.startsWith(`${prefix}/`) ? `${prefix}/${path}` : path, new URL("/", base));
|
|
658
|
+
};
|
|
603
659
|
let stream = "";
|
|
604
660
|
const records = [];
|
|
605
661
|
$("script").each((_index, script) => {
|
|
@@ -636,7 +692,7 @@ function mintlifyNavigation(body, base, locale) {
|
|
|
636
692
|
const walk = (value, depth = 0, activeLocale) => {
|
|
637
693
|
if (depth > MAX_DEPTH || ++count > MAX_NODES) throw new Error("Source navigation exceeds its safety bound.");
|
|
638
694
|
if (typeof value === "string") {
|
|
639
|
-
const url2 =
|
|
695
|
+
const url2 = configTarget(value);
|
|
640
696
|
return url2 ? [{ kind: "page", label: value, url: url2 }] : [];
|
|
641
697
|
}
|
|
642
698
|
if (Array.isArray(value)) return value.flatMap((child) => walk(child, depth + 1, activeLocale));
|
|
@@ -656,7 +712,7 @@ function mintlifyNavigation(body, base, locale) {
|
|
|
656
712
|
return;
|
|
657
713
|
}
|
|
658
714
|
if (!object(node)) return;
|
|
659
|
-
const href =
|
|
715
|
+
const href = configTarget(node.href);
|
|
660
716
|
if (href) {
|
|
661
717
|
populated++;
|
|
662
718
|
const url2 = new URL(href);
|
|
@@ -674,10 +730,10 @@ function mintlifyNavigation(body, base, locale) {
|
|
|
674
730
|
const selected = matching[0]?.item ?? languages.find((item) => text(item.language)?.toLowerCase() === language?.toLowerCase()) ?? languages.find((item) => text(item.language)?.toLowerCase() === "en") ?? languages[0];
|
|
675
731
|
return selected ? walk({ ...selected, languages: void 0 }, depth + 1, text(selected.language)) : [];
|
|
676
732
|
}
|
|
677
|
-
const label = text(value.tab) ?? text(value.group) ?? text(value.title) ?? text(value.label);
|
|
678
|
-
const kind = text(value.tab) ? "tab" : text(value.group) ? "group" : "page";
|
|
733
|
+
const label = text(value.tab) ?? text(value.dropdown) ?? text(value.group) ?? text(value.sidebarTitle) ?? text(value.title) ?? text(value.label);
|
|
734
|
+
const kind = text(value.tab) || text(value.dropdown) ? "tab" : text(value.group) ? "group" : "page";
|
|
679
735
|
const children = ["tabs", "groups", "pages", "dropdowns", "anchors", "versions"].flatMap((key) => Array.isArray(value[key]) ? walk(value[key], depth + 1, activeLocale) : []);
|
|
680
|
-
const url =
|
|
736
|
+
const url = configTarget(value.href);
|
|
681
737
|
if (!label) return children;
|
|
682
738
|
if (!url && !children.length) return [];
|
|
683
739
|
return [{
|
|
@@ -685,6 +741,7 @@ function mintlifyNavigation(body, base, locale) {
|
|
|
685
741
|
label,
|
|
686
742
|
...url ? { url } : {},
|
|
687
743
|
...children.length ? { children } : {},
|
|
744
|
+
...text(value.dropdown) ? { presentation: "dropdown" } : {},
|
|
688
745
|
...kind === "tab" && activeLocale ? { locale: activeLocale } : {}
|
|
689
746
|
}];
|
|
690
747
|
};
|
|
@@ -711,18 +768,19 @@ function resolveNavigation(nodes, base) {
|
|
|
711
768
|
...url ? { url } : {},
|
|
712
769
|
...children.length ? { children } : {},
|
|
713
770
|
...["tab", "group", "page"].includes(node.kind ?? "") ? { kind: node.kind } : {},
|
|
771
|
+
...node.presentation === "dropdown" ? { presentation: "dropdown" } : {},
|
|
714
772
|
...text(node.locale) ? { locale: text(node.locale) } : {}
|
|
715
773
|
}];
|
|
716
774
|
});
|
|
717
775
|
};
|
|
718
776
|
return walk(nodes);
|
|
719
777
|
}
|
|
720
|
-
function extractSourceNavigation(capture) {
|
|
778
|
+
function extractSourceNavigation(capture, selectedScope) {
|
|
721
779
|
if (capture.navigation) return resolveNavigation(capture.navigation, capture.finalUrl);
|
|
722
780
|
if (!/html/i.test(capture.contentType) || Buffer.byteLength(capture.body) > MAX_CAPTURE_BYTES) return [];
|
|
723
|
-
const mintlify = mintlifyNavigation(capture.body, capture.finalUrl, capture.locale);
|
|
781
|
+
const mintlify = mintlifyNavigation(capture.body, capture.finalUrl, capture.locale, selectedScope);
|
|
724
782
|
if (mintlify.length) return mintlify;
|
|
725
|
-
const $ =
|
|
783
|
+
const $ = load2(capture.body);
|
|
726
784
|
const candidates = ['.theme-doc-sidebar-menu,[aria-label*="sidebar" i]', "aside", '[role="navigation"],nav'];
|
|
727
785
|
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);
|
|
728
786
|
if (!root) return [];
|
|
@@ -804,7 +862,7 @@ function migrationImageUrl(value, base) {
|
|
|
804
862
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error("Responsive image URL is not a credential-free HTTP(S) URL.");
|
|
805
863
|
return url.toString();
|
|
806
864
|
}
|
|
807
|
-
function migrationImageCandidates(value, base,
|
|
865
|
+
function migrationImageCandidates(value, base, resolve4 = true) {
|
|
808
866
|
if (value.length > 64e3) throw new Error("Responsive image candidate list exceeds its safety bound.");
|
|
809
867
|
const result = [];
|
|
810
868
|
let index = 0;
|
|
@@ -822,7 +880,7 @@ function migrationImageCandidates(value, base, resolve3 = true) {
|
|
|
822
880
|
if (descriptor && !/^(?:[1-9]\d*w|(?:\d+(?:\.\d+)?|\.\d+)x)$/.test(descriptor)) throw new Error("Responsive image has an unsupported descriptor.");
|
|
823
881
|
if (descriptor.endsWith("x") && Number(descriptor.slice(0, -1)) <= 0) throw new Error("Responsive image density must be positive.");
|
|
824
882
|
const resolved = migrationImageUrl(url, base);
|
|
825
|
-
result.push({ url:
|
|
883
|
+
result.push({ url: resolve4 ? resolved : url, descriptor });
|
|
826
884
|
if (result.length > 128) throw new Error("Responsive image has too many candidates.");
|
|
827
885
|
if (value[index] === ",") index++;
|
|
828
886
|
}
|
|
@@ -836,8 +894,88 @@ function migrationImageAttribute(value) {
|
|
|
836
894
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/\{/g, "{").replace(/\}/g, "}").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
837
895
|
}
|
|
838
896
|
|
|
897
|
+
// src/url-media.ts
|
|
898
|
+
import { load as load3 } from "cheerio";
|
|
899
|
+
var VIDEO_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
900
|
+
"src",
|
|
901
|
+
"title",
|
|
902
|
+
"poster",
|
|
903
|
+
"controls",
|
|
904
|
+
"muted",
|
|
905
|
+
"autoplay",
|
|
906
|
+
"loop",
|
|
907
|
+
"playsinline",
|
|
908
|
+
"class",
|
|
909
|
+
"id",
|
|
910
|
+
"width",
|
|
911
|
+
"height",
|
|
912
|
+
"preload",
|
|
913
|
+
"aria-label"
|
|
914
|
+
]);
|
|
915
|
+
var YOUTUBE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
916
|
+
"src",
|
|
917
|
+
"title",
|
|
918
|
+
"width",
|
|
919
|
+
"height",
|
|
920
|
+
"allow",
|
|
921
|
+
"allowfullscreen",
|
|
922
|
+
"referrerpolicy",
|
|
923
|
+
"frameborder",
|
|
924
|
+
"loading",
|
|
925
|
+
"class",
|
|
926
|
+
"id",
|
|
927
|
+
"aria-label"
|
|
928
|
+
]);
|
|
929
|
+
var YOUTUBE_HOSTS = /* @__PURE__ */ new Set([
|
|
930
|
+
"youtube.com",
|
|
931
|
+
"www.youtube.com",
|
|
932
|
+
"youtube-nocookie.com",
|
|
933
|
+
"www.youtube-nocookie.com"
|
|
934
|
+
]);
|
|
935
|
+
function projectMigrationMedia(html, base) {
|
|
936
|
+
const $ = load3(html, void 0, false);
|
|
937
|
+
const nodes = $.root().contents().filter((_index, node2) => node2.type !== "text" || Boolean(node2.data.trim()));
|
|
938
|
+
if (nodes.length !== 1) return null;
|
|
939
|
+
const element = nodes.first();
|
|
940
|
+
const node = element[0];
|
|
941
|
+
if (node.type !== "tag" || !["video", "iframe"].includes(node.name)) return null;
|
|
942
|
+
if (element.children().length || element.text().trim()) return null;
|
|
943
|
+
const allowed = node.name === "video" ? VIDEO_ATTRIBUTES : YOUTUBE_ATTRIBUTES;
|
|
944
|
+
if (Object.keys(node.attribs).some((name) => !allowed.has(name))) return null;
|
|
945
|
+
const source = element.attr("src");
|
|
946
|
+
if (!source) return null;
|
|
947
|
+
try {
|
|
948
|
+
let src = migrationImageUrl(source, base);
|
|
949
|
+
let component = "Video";
|
|
950
|
+
const props = [];
|
|
951
|
+
if (node.name === "video") {
|
|
952
|
+
if (!/\.(?:mp4|webm|ogg|mov|m4v)(?:[?#]|$)/i.test(src)) component = "video";
|
|
953
|
+
const isMuted = element.attr("muted") !== void 0;
|
|
954
|
+
const hasAutoplay = element.attr("autoplay") !== void 0;
|
|
955
|
+
if (hasAutoplay && !isMuted) return null;
|
|
956
|
+
props.push("controls={true}");
|
|
957
|
+
for (const [attribute, prop] of [["muted", "muted"], ["loop", "loop"], ["playsinline", "playsInline"], ["autoplay", "autoPlay"]]) {
|
|
958
|
+
if (element.attr(attribute) !== void 0) props.push(`${prop}={true}`);
|
|
959
|
+
}
|
|
960
|
+
const poster = element.attr("poster");
|
|
961
|
+
if (poster) props.push(`poster="${migrationImageAttribute(migrationImageUrl(poster, base))}"`);
|
|
962
|
+
} else {
|
|
963
|
+
const url = new URL(src);
|
|
964
|
+
if (url.protocol !== "https:" || url.port || !YOUTUBE_HOSTS.has(url.hostname)) return null;
|
|
965
|
+
const id = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]{11})$/)?.[1];
|
|
966
|
+
if (!id || url.hash || [...url.searchParams.keys()].some((name) => name !== "si")) return null;
|
|
967
|
+
src = `https://www.youtube-nocookie.com/embed/${id}`;
|
|
968
|
+
}
|
|
969
|
+
const title = element.attr("title") ?? element.attr("aria-label");
|
|
970
|
+
if (title !== void 0) props.push(`title="${migrationImageAttribute(title)}"`);
|
|
971
|
+
return `<${component} src="${migrationImageAttribute(src)}"${props.length ? ` ${props.join(" ")}` : ""} />`;
|
|
972
|
+
} catch {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
839
977
|
// src/url.ts
|
|
840
|
-
import { load as
|
|
978
|
+
import { load as load4 } from "cheerio";
|
|
841
979
|
import remarkMdx2 from "remark-mdx";
|
|
842
980
|
import remarkParse2 from "remark-parse";
|
|
843
981
|
import TurndownService from "turndown";
|
|
@@ -2470,9 +2608,10 @@ var PORTABLE_MDX_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
2470
2608
|
"Warning",
|
|
2471
2609
|
"YouTube",
|
|
2472
2610
|
"picture",
|
|
2473
|
-
"source"
|
|
2611
|
+
"source",
|
|
2612
|
+
"video"
|
|
2474
2613
|
]);
|
|
2475
|
-
var PORTABLE_URL_PROPS = "href|src|img|primaryHref|secondaryHref";
|
|
2614
|
+
var PORTABLE_URL_PROPS = "href|src|poster|img|primaryHref|secondaryHref";
|
|
2476
2615
|
var PORTABLE_URL_PROP_NAMES = new Set(PORTABLE_URL_PROPS.split("|"));
|
|
2477
2616
|
var MINTLIFY_ARTICLE_LABELS = [
|
|
2478
2617
|
{ baseUrl: "Select base URL", permalink: "Navigate to header" },
|
|
@@ -2612,7 +2751,7 @@ function isSafePortableUrl(value) {
|
|
|
2612
2751
|
}
|
|
2613
2752
|
}
|
|
2614
2753
|
function decodeHtmlAttribute(value) {
|
|
2615
|
-
const document =
|
|
2754
|
+
const document = load4(`<i data-thally-value="${value.replace(/"/g, """)}"></i>`);
|
|
2616
2755
|
return document("i").attr("data-thally-value") ?? "";
|
|
2617
2756
|
}
|
|
2618
2757
|
function escapeMarkdownText(value, escapeTableSeparator = false) {
|
|
@@ -2698,7 +2837,7 @@ function replacePortableHtmlImages(value) {
|
|
|
2698
2837
|
if (start < cursor) continue;
|
|
2699
2838
|
const end = htmlOpeningTagEnd(value, start + match[0].length);
|
|
2700
2839
|
if (end === null) continue;
|
|
2701
|
-
const fragment =
|
|
2840
|
+
const fragment = load4(value.slice(start, end));
|
|
2702
2841
|
const image = fragment("img").first();
|
|
2703
2842
|
const source = image.attr("src");
|
|
2704
2843
|
const destination = source && isSafePortableUrl(source) ? markdownLinkDestination(source) : null;
|
|
@@ -2723,7 +2862,7 @@ function replacePortableHtmlAnchors(value) {
|
|
|
2723
2862
|
const closing = closingMatch.exec(value);
|
|
2724
2863
|
if (!closing) continue;
|
|
2725
2864
|
const end = closing.index + closing[0].length;
|
|
2726
|
-
const fragment =
|
|
2865
|
+
const fragment = load4(value.slice(start, end));
|
|
2727
2866
|
const anchor = fragment("a").first();
|
|
2728
2867
|
const href = anchor.attr("href");
|
|
2729
2868
|
const label = escapeMarkdownText(anchor.text().replace(/\s+/g, " ").trim());
|
|
@@ -2758,6 +2897,10 @@ function sanitizeMarkdownUrls(value) {
|
|
|
2758
2897
|
hasUnsafeMdxSyntax = true;
|
|
2759
2898
|
return;
|
|
2760
2899
|
}
|
|
2900
|
+
if (node.name === "video" && node.children?.length) {
|
|
2901
|
+
hasUnsafeMdxSyntax = true;
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2761
2904
|
for (const attribute of node.attributes ?? []) {
|
|
2762
2905
|
if (attribute.type !== "mdxJsxAttribute" || !attribute.name || /^on[A-Z]/.test(attribute.name)) {
|
|
2763
2906
|
hasUnsafeMdxSyntax = true;
|
|
@@ -2794,6 +2937,22 @@ function sanitizeMarkdownUrls(value) {
|
|
|
2794
2937
|
return;
|
|
2795
2938
|
}
|
|
2796
2939
|
}
|
|
2940
|
+
if (node.name === "video") {
|
|
2941
|
+
const isBoolean = ["controls", "muted", "autoPlay", "loop", "playsInline"].includes(attribute.name);
|
|
2942
|
+
const isText = ["src", "poster", "title"].includes(attribute.name);
|
|
2943
|
+
if (isBoolean && typeof staticValue !== "boolean" || isText && typeof attribute.value !== "string" || !isBoolean && !isText) {
|
|
2944
|
+
hasUnsafeMdxSyntax = true;
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (attribute.name === "src" || attribute.name === "poster") {
|
|
2948
|
+
try {
|
|
2949
|
+
migrationImageUrl(decodeHtmlAttribute(attribute.value), new URL("https://migration.invalid"));
|
|
2950
|
+
} catch {
|
|
2951
|
+
hasUnsafeMdxSyntax = true;
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2797
2956
|
}
|
|
2798
2957
|
}
|
|
2799
2958
|
for (const child of node.children ?? []) validateMdxSyntax(child);
|
|
@@ -2906,7 +3065,7 @@ function sanitizeRemoteMarkdown(body) {
|
|
|
2906
3065
|
const urlSafeMarkdown = sanitizeMarkdownUrls(sanitized);
|
|
2907
3066
|
if (urlSafeMarkdown === null) return null;
|
|
2908
3067
|
const executable = withoutFencedCode(urlSafeMarkdown).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<(?:https?:\/\/|mailto:)[^>]+>/gi, "").replace(/(`+)[\s\S]*?\1/g, "");
|
|
2909
|
-
if (
|
|
3068
|
+
if (/^[\t ]*(?:import|export)[\t ]/m.test(executable)) return null;
|
|
2910
3069
|
let hasUnsafeExpression = false;
|
|
2911
3070
|
const withoutStaticProps = executable.replace(/=\{([^{}\n]*)\}/g, (original, value) => {
|
|
2912
3071
|
try {
|
|
@@ -2964,7 +3123,7 @@ function extractEmbeddedOpenApi(content) {
|
|
|
2964
3123
|
const root = embeddedOpenApiParser.parse(content);
|
|
2965
3124
|
const candidates = (root.children ?? []).flatMap((node2) => {
|
|
2966
3125
|
if (node2.type !== "code" || !/^(?:yaml|yml|json)$/i.test(node2.lang ?? "")) return [];
|
|
2967
|
-
const metadata = node2.meta?.match(/^(?:(
|
|
3126
|
+
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);
|
|
2968
3127
|
return metadata ? [{ node: node2, method: metadata[2].toUpperCase(), path: metadata[3] }] : [];
|
|
2969
3128
|
});
|
|
2970
3129
|
if (candidates.length !== 1) return { content };
|
|
@@ -3049,7 +3208,7 @@ function markdownPage(document, id, platform, warn) {
|
|
|
3049
3208
|
};
|
|
3050
3209
|
}
|
|
3051
3210
|
function migrationHtmlArticle(body) {
|
|
3052
|
-
const $ =
|
|
3211
|
+
const $ = load4(body);
|
|
3053
3212
|
const root = ["main article", "article", "main", '[role="main"]', "body"].map((selector) => $(selector).first()).find((candidate) => candidate.length);
|
|
3054
3213
|
root.find([
|
|
3055
3214
|
"script",
|
|
@@ -3067,7 +3226,10 @@ function migrationHtmlArticle(body) {
|
|
|
3067
3226
|
"#chat-assistant-sheet",
|
|
3068
3227
|
"#chat-assistant-textarea"
|
|
3069
3228
|
].join(",")).remove();
|
|
3070
|
-
|
|
3229
|
+
root.find('div[data-component-part="code-group-tab-content"]').each((_index, element) => {
|
|
3230
|
+
if ($(element).parents("pre").length === 0 && $(element).find("pre").length === 0) element.name = "pre";
|
|
3231
|
+
});
|
|
3232
|
+
const permalinkSelectors = MINTLIFY_ARTICLE_LABELS.map((labels2) => `a[aria-label="${labels2.permalink}"]`);
|
|
3071
3233
|
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) => {
|
|
3072
3234
|
const parent = $(link).parent();
|
|
3073
3235
|
$(link).remove();
|
|
@@ -3078,7 +3240,7 @@ function migrationHtmlArticle(body) {
|
|
|
3078
3240
|
const isChecked = $(element).attr("checked") !== void 0;
|
|
3079
3241
|
$(element).replaceWith($("<span></span>").attr("data-thally-static-task-checkbox", isChecked ? "checked" : "unchecked").text(isChecked ? "[x] " : "[ ] "));
|
|
3080
3242
|
});
|
|
3081
|
-
root.find(MINTLIFY_ARTICLE_LABELS.map((
|
|
3243
|
+
root.find(MINTLIFY_ARTICLE_LABELS.map((labels2) => `select[aria-label="${labels2.baseUrl}"]`).join(",")).each((_index, element) => {
|
|
3082
3244
|
const select = $(element);
|
|
3083
3245
|
const options = select.children("option").toArray();
|
|
3084
3246
|
if (!options.length || options.length !== select.children().length) return;
|
|
@@ -3117,7 +3279,7 @@ function htmlPage(document, id, options = {}) {
|
|
|
3117
3279
|
const title = scopedTitle || $("main h1, article h1, h1").first().text().trim() || $('meta[property="og:title"]').attr("content")?.trim() || $("title").text().split("|")[0].trim();
|
|
3118
3280
|
const description = $('meta[name="description"]').attr("content")?.trim() || $('meta[property="og:description"]').attr("content")?.trim();
|
|
3119
3281
|
let documentHeadingAnchor = "";
|
|
3120
|
-
const excludedElements = options.omitDocumentHeading ? "script,style,noscript,nav,footer,aside,form,button,svg,
|
|
3282
|
+
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";
|
|
3121
3283
|
root.find(excludedElements).remove();
|
|
3122
3284
|
root.find(".theme-doc-version-badge,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
|
|
3123
3285
|
if (options.omitDocumentHeading) {
|
|
@@ -3175,6 +3337,18 @@ function htmlPage(document, id, options = {}) {
|
|
|
3175
3337
|
// Emit literal task-list syntax rather than escaping its brackets as copy.
|
|
3176
3338
|
replacement: (_content, node) => node.getAttribute("data-thally-static-task-checkbox") === "checked" ? "[x] " : "[ ] "
|
|
3177
3339
|
});
|
|
3340
|
+
turndown.addRule("migration-authored-paragraph", {
|
|
3341
|
+
filter: (node) => node.nodeName === "SPAN" && node.getAttribute("data-as") === "p",
|
|
3342
|
+
// Some publishers render authored paragraphs as spans. Their explicit
|
|
3343
|
+
// semantic hint must retain a block boundary: otherwise following copy
|
|
3344
|
+
// joins a picture's closing tag or an accordion's source anchor and can
|
|
3345
|
+
// change the resulting MDX grammar. Ordinary spans remain inline.
|
|
3346
|
+
replacement: (content) => `
|
|
3347
|
+
|
|
3348
|
+
${content}
|
|
3349
|
+
|
|
3350
|
+
`
|
|
3351
|
+
});
|
|
3178
3352
|
const anchor = (element) => {
|
|
3179
3353
|
const value = element.getAttribute("id");
|
|
3180
3354
|
return options.preserveAnchors && value ? `<a id="${value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/\{/g, "{")}" />
|
|
@@ -3193,6 +3367,18 @@ ${anchor(element)}${heading ? `${"#".repeat(Number(heading[1]))} ${content.trim(
|
|
|
3193
3367
|
`;
|
|
3194
3368
|
}
|
|
3195
3369
|
});
|
|
3370
|
+
turndown.addRule("migration-static-media", {
|
|
3371
|
+
filter: (node) => node.nodeName === "VIDEO" || node.nodeName === "IFRAME",
|
|
3372
|
+
replacement: (_content, node) => {
|
|
3373
|
+
const element = node;
|
|
3374
|
+
const projected = projectMigrationMedia(element.outerHTML, document.finalUrl);
|
|
3375
|
+
return projected ? `
|
|
3376
|
+
|
|
3377
|
+
${anchor(element)}${projected}
|
|
3378
|
+
|
|
3379
|
+
` : "";
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3196
3382
|
if (options.preserveResponsiveImages) {
|
|
3197
3383
|
const image = (element) => {
|
|
3198
3384
|
const tag = element.nodeName.toLowerCase();
|
|
@@ -3228,7 +3414,7 @@ ${content.trim()}
|
|
|
3228
3414
|
const element = node;
|
|
3229
3415
|
const className = element.className;
|
|
3230
3416
|
const tag = /admonition-(?:danger)/.test(className) ? "Error" : /admonition-(?:caution|warning)/.test(className) ? "Warning" : /admonition-info/.test(className) ? "Info" : "Note";
|
|
3231
|
-
const fragment =
|
|
3417
|
+
const fragment = load4(element.outerHTML);
|
|
3232
3418
|
const clone = fragment(".theme-admonition").first();
|
|
3233
3419
|
const title2 = clone.find('[class*="admonitionHeading"],.admonition-heading').first();
|
|
3234
3420
|
const titleText = title2.text().trim();
|
|
@@ -3249,15 +3435,15 @@ ${titleText ? `**${escapeMarkdownText(titleText)}**
|
|
|
3249
3435
|
filter: (node) => node.nodeName === "DIV" && node.classList.contains("tabs-container"),
|
|
3250
3436
|
replacement: (_content, node) => {
|
|
3251
3437
|
const element = node;
|
|
3252
|
-
const fragment =
|
|
3438
|
+
const fragment = load4(element.outerHTML);
|
|
3253
3439
|
const container = fragment(".tabs-container").first();
|
|
3254
|
-
const
|
|
3440
|
+
const labels2 = container.children("ul.tabs").children("li").toArray().map((item) => fragment(item).text().trim() || "Tab");
|
|
3255
3441
|
const panels = container.find('[role="tabpanel"]').toArray().filter((panel) => fragment(panel).closest(".tabs-container")[0] === container[0]);
|
|
3256
3442
|
if (panels.length === 0) return _content;
|
|
3257
3443
|
const tabs = panels.map((panel, index) => {
|
|
3258
3444
|
const panelId = fragment(panel).attr("id");
|
|
3259
3445
|
const tab = panelId ? container.find('[role="tab"]').toArray().find((item) => fragment(item).attr("aria-controls") === panelId) : void 0;
|
|
3260
|
-
const title2 = migrationImageAttribute((tab ? fragment(tab).text().trim() :
|
|
3446
|
+
const title2 = migrationImageAttribute((tab ? fragment(tab).text().trim() : labels2[index]) || `Tab ${index + 1}`);
|
|
3261
3447
|
const panelAnchor = options.preserveAnchors && panelId ? `<a id="${migrationImageAttribute(panelId)}" />
|
|
3262
3448
|
|
|
3263
3449
|
` : "";
|
|
@@ -3277,7 +3463,7 @@ ${tabs.join("\n\n")}
|
|
|
3277
3463
|
turndown.addRule("gfm-table", {
|
|
3278
3464
|
filter: "table",
|
|
3279
3465
|
replacement: (_content, node) => {
|
|
3280
|
-
const fragment =
|
|
3466
|
+
const fragment = load4(node.outerHTML);
|
|
3281
3467
|
const rows = fragment("tr").toArray().map((row) => fragment(row).children("th,td").toArray().map((cell) => {
|
|
3282
3468
|
const wasConvertingTableCell = isConvertingTableCell;
|
|
3283
3469
|
isConvertingTableCell = true;
|
|
@@ -3303,10 +3489,17 @@ ${normalized.slice(1).map((row) => `| ${row.join(" | ")} |`).join("\n")}
|
|
|
3303
3489
|
filter: (node) => node.nodeName === "PRE",
|
|
3304
3490
|
replacement: (_content, node) => {
|
|
3305
3491
|
const element = node;
|
|
3306
|
-
const fragment =
|
|
3492
|
+
const fragment = load4(element.outerHTML);
|
|
3307
3493
|
const tokenLines = fragment(".token-line").toArray();
|
|
3308
3494
|
const code = (tokenLines.length > 0 ? tokenLines.map((line) => fragment(line).text()).join("\n") : fragment("pre").text()).replace(/\n$/, "");
|
|
3309
|
-
const language =
|
|
3495
|
+
const language = ["code", "pre"].flatMap((selector) => {
|
|
3496
|
+
const block = fragment(selector).first();
|
|
3497
|
+
return [
|
|
3498
|
+
block.attr("language"),
|
|
3499
|
+
block.attr("data-language"),
|
|
3500
|
+
block.attr("class")?.match(/(?:^|\s)(?:language-|lang-)([A-Za-z0-9_+-]+)(?=\s|$)/)?.[1]
|
|
3501
|
+
];
|
|
3502
|
+
}).find((value) => value !== void 0 && /^[A-Za-z0-9_+-]{1,64}$/.test(value)) ?? "";
|
|
3310
3503
|
const longestFence = Math.max(0, ...[...code.matchAll(/`+/g)].map((match) => match[0].length));
|
|
3311
3504
|
const fence = "`".repeat(Math.max(3, longestFence + 1));
|
|
3312
3505
|
return `
|
|
@@ -3336,8 +3529,127 @@ ${fence}
|
|
|
3336
3529
|
};
|
|
3337
3530
|
}
|
|
3338
3531
|
|
|
3532
|
+
// src/url-api-schema-controls.ts
|
|
3533
|
+
var MAX_CAPTURE_BYTES2 = 8e6;
|
|
3534
|
+
var MAX_TAB_GROUPS = 256;
|
|
3535
|
+
var MAX_TABS = 1024;
|
|
3536
|
+
function object2(value) {
|
|
3537
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3538
|
+
}
|
|
3539
|
+
function localReference(document, reference) {
|
|
3540
|
+
if (typeof reference !== "string" || !reference.startsWith("#/")) return void 0;
|
|
3541
|
+
let target2 = document;
|
|
3542
|
+
try {
|
|
3543
|
+
for (const part of reference.slice(2).split("/")) {
|
|
3544
|
+
const key = decodeURIComponent(part).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3545
|
+
if (!target2 || typeof target2 !== "object" || !Object.hasOwn(target2, key)) return void 0;
|
|
3546
|
+
target2 = target2[key];
|
|
3547
|
+
}
|
|
3548
|
+
} catch {
|
|
3549
|
+
return void 0;
|
|
3550
|
+
}
|
|
3551
|
+
return target2;
|
|
3552
|
+
}
|
|
3553
|
+
function resolve2(document, value) {
|
|
3554
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3555
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
3556
|
+
const schema = object2(value);
|
|
3557
|
+
if (!schema || seen.has(schema)) return void 0;
|
|
3558
|
+
seen.add(schema);
|
|
3559
|
+
if (!Object.hasOwn(schema, "$ref")) return schema;
|
|
3560
|
+
value = localReference(document, schema.$ref);
|
|
3561
|
+
}
|
|
3562
|
+
return void 0;
|
|
3563
|
+
}
|
|
3564
|
+
function complete(document) {
|
|
3565
|
+
let visited = 0;
|
|
3566
|
+
const active = /* @__PURE__ */ new Set();
|
|
3567
|
+
const visit = (value, depth) => {
|
|
3568
|
+
if (++visited > 1e5 || depth > 96) return false;
|
|
3569
|
+
if (typeof value === "number" && !Number.isFinite(value)) return false;
|
|
3570
|
+
if (!value || typeof value !== "object") return true;
|
|
3571
|
+
if (active.has(value)) return false;
|
|
3572
|
+
active.add(value);
|
|
3573
|
+
const schema = object2(value);
|
|
3574
|
+
const valid = (!schema || !Object.hasOwn(schema, "$ref") || Boolean(object2(localReference(document, schema.$ref)))) && Object.values(value).every((child) => visit(child, depth + 1));
|
|
3575
|
+
active.delete(value);
|
|
3576
|
+
return valid;
|
|
3577
|
+
};
|
|
3578
|
+
return visit(document, 0);
|
|
3579
|
+
}
|
|
3580
|
+
function labels(document, value) {
|
|
3581
|
+
const schema = resolve2(document, value);
|
|
3582
|
+
if (!Array.isArray(schema?.oneOf) || schema.oneOf.length < 2 || schema.oneOf.length > 64) return void 0;
|
|
3583
|
+
const result = [];
|
|
3584
|
+
for (const [index, branch] of schema.oneOf.entries()) {
|
|
3585
|
+
const target2 = resolve2(document, branch);
|
|
3586
|
+
if (!target2) return void 0;
|
|
3587
|
+
const title = object2(branch)?.title ?? target2.title;
|
|
3588
|
+
result.push(typeof title === "string" && title.trim() ? title.trim().replace(/\s+/g, " ") : `Option ${index + 1}`);
|
|
3589
|
+
}
|
|
3590
|
+
return new Set(result).size === result.length ? result : void 0;
|
|
3591
|
+
}
|
|
3592
|
+
function apiSchemaTabGroups(body, document, operation) {
|
|
3593
|
+
if (Buffer.byteLength(body) > MAX_CAPTURE_BYTES2 || !complete(document)) return [];
|
|
3594
|
+
const path = object2(document.paths)?.[operation.path];
|
|
3595
|
+
const method = operation.method.toLowerCase();
|
|
3596
|
+
if (!/^(?:get|post|put|patch|delete|head|options|trace)$/.test(method)) return [];
|
|
3597
|
+
const selected = object2(object2(path)?.[method]);
|
|
3598
|
+
if (!selected) return [];
|
|
3599
|
+
const { $, root } = migrationHtmlArticle(body);
|
|
3600
|
+
const groups = root.find('[role="tablist"],[data-component-part="tabs-list"]');
|
|
3601
|
+
if (groups.length > MAX_TAB_GROUPS || root.find('[role="tab"]').length > MAX_TABS) return [];
|
|
3602
|
+
const idCounts = /* @__PURE__ */ new Map();
|
|
3603
|
+
root.find("[id]").each((_index, node) => {
|
|
3604
|
+
const id = $(node).attr("id");
|
|
3605
|
+
idCounts.set(id, (idCounts.get(id) ?? 0) + 1);
|
|
3606
|
+
});
|
|
3607
|
+
const proofs = [];
|
|
3608
|
+
groups.each((index, node) => {
|
|
3609
|
+
const group = $(node);
|
|
3610
|
+
const container = group.parent(".tabs.tab-container");
|
|
3611
|
+
const field = group.closest(".object-param-field");
|
|
3612
|
+
const section = field.parent(".api-section");
|
|
3613
|
+
if (!group.is('[role="tablist"][data-component-part="tabs-list"]') || !container.length || !section.length || !section.closest('[id^="api-playground-"][id$="-operation-page"]').length) return;
|
|
3614
|
+
if (field.parents(".object-param-field").length) return;
|
|
3615
|
+
const heading = section.children(".api-section-heading");
|
|
3616
|
+
const title = heading.find(".api-section-heading-title").text().trim().toLowerCase();
|
|
3617
|
+
const media = heading.find("*").toArray().filter((element) => $(element).children().length === 0).map((element) => $(element).text().trim()).filter((text2) => /^[a-z][a-z\d.+-]*\/[a-z\d.+-]+$/i.test(text2));
|
|
3618
|
+
if (heading.length !== 1 || !media || media.length !== 1) return;
|
|
3619
|
+
let schema;
|
|
3620
|
+
let scope;
|
|
3621
|
+
if (title === "body") {
|
|
3622
|
+
const request = resolve2(document, selected.requestBody);
|
|
3623
|
+
schema = object2(object2(request?.content)?.[media[0]])?.schema;
|
|
3624
|
+
scope = `request:${media[0]}`;
|
|
3625
|
+
} else if (title === "response") {
|
|
3626
|
+
const statuses = heading.find('button[aria-haspopup="menu"]').toArray().map((button) => $(button).text().trim()).filter((text2) => /^(?:[1-5]\d{2}|default)$/.test(text2));
|
|
3627
|
+
if (statuses.length !== 1) return;
|
|
3628
|
+
const response = resolve2(document, object2(selected.responses)?.[statuses[0]]);
|
|
3629
|
+
schema = object2(object2(response?.content)?.[media[0]])?.schema;
|
|
3630
|
+
scope = `response:${statuses[0]}:${media[0]}`;
|
|
3631
|
+
} else return;
|
|
3632
|
+
const expected = labels(document, schema);
|
|
3633
|
+
const tabs = group.children('[role="tab"][data-component-part="tab"]');
|
|
3634
|
+
if (!expected || tabs.length !== expected.length || group.children().length !== tabs.length) return;
|
|
3635
|
+
const panels = container.children('[data-component-part="tabs-panels"]').children('[role="tabpanel"][data-component-part="tab-content"]');
|
|
3636
|
+
if (panels.length !== tabs.length) return;
|
|
3637
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3638
|
+
for (const [position, tab] of tabs.toArray().entries()) {
|
|
3639
|
+
const control = $(tab).attr("aria-controls");
|
|
3640
|
+
const panel = panels.eq(position);
|
|
3641
|
+
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;
|
|
3642
|
+
ids.add(control);
|
|
3643
|
+
}
|
|
3644
|
+
proofs.push({ index, scope });
|
|
3645
|
+
});
|
|
3646
|
+
const scopeCounts = /* @__PURE__ */ new Map();
|
|
3647
|
+
for (const proof of proofs) scopeCounts.set(proof.scope, (scopeCounts.get(proof.scope) ?? 0) + 1);
|
|
3648
|
+
return proofs.filter((proof) => scopeCounts.get(proof.scope) === 1).map((proof) => proof.index);
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3339
3651
|
// src/url-run.ts
|
|
3340
|
-
var CONVERTER_VERSION = "url-evidence-
|
|
3652
|
+
var CONVERTER_VERSION = "url-evidence-v5";
|
|
3341
3653
|
var parser = unified3().use(remarkParse3).use(remarkMdx3);
|
|
3342
3654
|
var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
|
|
3343
3655
|
var MAX_RESPONSE_BYTES = 8e6;
|
|
@@ -3400,7 +3712,7 @@ function linksOf(document) {
|
|
|
3400
3712
|
return [...root.find("a[href]").toArray(), ...$('link[rel="sitemap"][href],link[rel="alternate"][href]').toArray()].map((node) => $(node).attr("href")).filter(Boolean);
|
|
3401
3713
|
}
|
|
3402
3714
|
if (/xml/i.test(document.contentType) || /<urlset|<sitemapindex/i.test(document.body)) {
|
|
3403
|
-
const $ =
|
|
3715
|
+
const $ = load5(document.body, { xmlMode: true });
|
|
3404
3716
|
return $("loc").toArray().map((node) => $(node).text().trim());
|
|
3405
3717
|
}
|
|
3406
3718
|
return [
|
|
@@ -3443,8 +3755,8 @@ function markdownHeadingAnchors(body) {
|
|
|
3443
3755
|
return occurrence ? `${base}-${occurrence}` : base;
|
|
3444
3756
|
});
|
|
3445
3757
|
}
|
|
3446
|
-
function sourceNavigation(capture) {
|
|
3447
|
-
return extractSourceNavigation(capture);
|
|
3758
|
+
function sourceNavigation(capture, selectedScope) {
|
|
3759
|
+
return extractSourceNavigation(capture, selectedScope);
|
|
3448
3760
|
}
|
|
3449
3761
|
function idForUrl(url, scope) {
|
|
3450
3762
|
const relative4 = scope === "/" ? url.pathname.slice(1) : url.pathname.slice(scope.length).replace(/^\//, "");
|
|
@@ -3453,7 +3765,7 @@ function idForUrl(url, scope) {
|
|
|
3453
3765
|
}
|
|
3454
3766
|
function articleIdentity(document) {
|
|
3455
3767
|
if (!/html/i.test(document.contentType)) return document.finalUrl;
|
|
3456
|
-
const $ =
|
|
3768
|
+
const $ = load5(document.body);
|
|
3457
3769
|
const reference = $('link[rel="canonical"][href]').first().attr("href");
|
|
3458
3770
|
if (!reference) return document.finalUrl;
|
|
3459
3771
|
try {
|
|
@@ -3509,16 +3821,24 @@ function assetReferences(capture) {
|
|
|
3509
3821
|
}
|
|
3510
3822
|
const { $, root } = migrationHtmlArticle(capture.body);
|
|
3511
3823
|
return [
|
|
3512
|
-
...root.find("img[src],video[poster],a[download][href]").toArray().map((node) => $(node).
|
|
3824
|
+
...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")),
|
|
3513
3825
|
...root.find("[srcset]").toArray().flatMap((node) => migrationImageCandidates($(node).attr("srcset") ?? "", capture.finalUrl).map((candidate) => candidate.url))
|
|
3514
3826
|
].filter(Boolean);
|
|
3515
3827
|
}
|
|
3516
|
-
function unresolvedStaticControls(body, hasApiSchema = false) {
|
|
3828
|
+
function unresolvedStaticControls(body, hasApiSchema = false, coveredSchemaGroups = []) {
|
|
3517
3829
|
const { $, root } = migrationHtmlArticle(body);
|
|
3830
|
+
root.find('[role="tablist"],[data-component-part="tabs-list"]').each((index, node) => {
|
|
3831
|
+
if (coveredSchemaGroups.includes(index)) $(node).remove();
|
|
3832
|
+
});
|
|
3518
3833
|
const tabs = root.find('[role="tab"]').toArray();
|
|
3519
3834
|
for (const tab of tabs) {
|
|
3520
3835
|
const controls = $(tab).attr("aria-controls");
|
|
3521
|
-
const
|
|
3836
|
+
const group = $(tab).closest('[role="tablist"]');
|
|
3837
|
+
const container = group.closest(".code-group,.tabs-container,.tabs.tab-container");
|
|
3838
|
+
const peers = group.find('[role="tab"]').toArray().filter((node) => $(node).closest('[role="tablist"]')[0] === group[0]);
|
|
3839
|
+
const localPanels = container.find('[role="tabpanel"]').toArray().filter((node) => $(node).closest(".code-group,.tabs-container,.tabs.tab-container")[0] === container[0]);
|
|
3840
|
+
const owners = container.find('[role="tablist"]').toArray().filter((node) => $(node).closest(".code-group,.tabs-container,.tabs.tab-container")[0] === container[0]);
|
|
3841
|
+
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)) : $([]);
|
|
3522
3842
|
if (panels.length !== 1 || !panels.text().trim() && !panels.find("pre,img,table,svg").length || panels.find('[aria-busy="true"],[role="progressbar"]').length) return true;
|
|
3523
3843
|
}
|
|
3524
3844
|
return root.find("details:not([open])").toArray().some((node) => {
|
|
@@ -3569,12 +3889,12 @@ function hasCompleteLocalApiSchema(document) {
|
|
|
3569
3889
|
if (typeof value === "number" && !Number.isFinite(value)) return false;
|
|
3570
3890
|
if (!value || typeof value !== "object") return true;
|
|
3571
3891
|
if (Array.isArray(value)) return value.every((child) => visit(child, depth + 1));
|
|
3572
|
-
const
|
|
3573
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
3574
|
-
if (typeof
|
|
3892
|
+
const object3 = value;
|
|
3893
|
+
if (Object.prototype.hasOwnProperty.call(object3, "$ref")) {
|
|
3894
|
+
if (typeof object3.$ref !== "string" || !object3.$ref.startsWith("#/")) return false;
|
|
3575
3895
|
let target2 = document;
|
|
3576
3896
|
try {
|
|
3577
|
-
for (const encoded of
|
|
3897
|
+
for (const encoded of object3.$ref.slice(2).split("/")) {
|
|
3578
3898
|
const key = decodeURIComponent(encoded).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3579
3899
|
if (!target2 || typeof target2 !== "object" || !Object.prototype.hasOwnProperty.call(target2, key)) return false;
|
|
3580
3900
|
target2 = target2[key];
|
|
@@ -3583,7 +3903,7 @@ function hasCompleteLocalApiSchema(document) {
|
|
|
3583
3903
|
return false;
|
|
3584
3904
|
}
|
|
3585
3905
|
}
|
|
3586
|
-
return Object.values(
|
|
3906
|
+
return Object.values(object3).every((child) => visit(child, depth + 1));
|
|
3587
3907
|
};
|
|
3588
3908
|
return visit(document);
|
|
3589
3909
|
}
|
|
@@ -3598,6 +3918,25 @@ function resolveMigrationRecord(records, record) {
|
|
|
3598
3918
|
}
|
|
3599
3919
|
function navigationConfig(checkpoint, records) {
|
|
3600
3920
|
const navigation = checkpoint.navigation;
|
|
3921
|
+
const shortcuts = [];
|
|
3922
|
+
const collectShortcuts = (nodes) => {
|
|
3923
|
+
for (const node of nodes) {
|
|
3924
|
+
if (node.url) {
|
|
3925
|
+
const record = records.get(canonical(node.url, checkpoint.sourceUrl) ?? "");
|
|
3926
|
+
const href = record?.redirectTo ?? node.url;
|
|
3927
|
+
const identity = canonical(href, checkpoint.sourceUrl);
|
|
3928
|
+
const target2 = identity ? new URL(href, checkpoint.sourceUrl).href : null;
|
|
3929
|
+
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 });
|
|
3930
|
+
}
|
|
3931
|
+
collectShortcuts(node.children ?? []);
|
|
3932
|
+
}
|
|
3933
|
+
};
|
|
3934
|
+
collectShortcuts(navigation);
|
|
3935
|
+
const hasDropdown = navigation.some((node) => node.presentation === "dropdown");
|
|
3936
|
+
const externalNavigation = shortcuts.length || hasDropdown ? { navigation: {
|
|
3937
|
+
...shortcuts.length ? { shortcuts } : {},
|
|
3938
|
+
...hasDropdown ? { display: "dropdown" } : {}
|
|
3939
|
+
} } : {};
|
|
3601
3940
|
const projection = localeProjection(checkpoint);
|
|
3602
3941
|
const project = (nodes) => nodes.flatMap((node) => {
|
|
3603
3942
|
const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
|
|
@@ -3618,7 +3957,7 @@ function navigationConfig(checkpoint, records) {
|
|
|
3618
3957
|
return pages2.length || tab.url ? [{ tab: tab.label, ...pages2.length ? { pages: pages2 } : {}, ...tab.url ? { href: tab.url } : {} }] : [];
|
|
3619
3958
|
});
|
|
3620
3959
|
const primary = localizedTabs(projection.defaultLocale);
|
|
3621
|
-
return { tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
|
|
3960
|
+
return { ...externalNavigation, tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
|
|
3622
3961
|
i18n: {
|
|
3623
3962
|
defaultLocale: projection.defaultLocale,
|
|
3624
3963
|
locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })),
|
|
@@ -3627,7 +3966,7 @@ function navigationConfig(checkpoint, records) {
|
|
|
3627
3966
|
} : {} };
|
|
3628
3967
|
}
|
|
3629
3968
|
const pages = project(navigation);
|
|
3630
|
-
return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
|
|
3969
|
+
return pages.length || shortcuts.length ? { ...externalNavigation, tabs: [{ tab: "Documentation", pages }] } : null;
|
|
3631
3970
|
}
|
|
3632
3971
|
function canonicalLanguage(value) {
|
|
3633
3972
|
if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
|
|
@@ -3647,7 +3986,7 @@ function observedLocale(document, id, scopePath) {
|
|
|
3647
3986
|
}
|
|
3648
3987
|
const urlLocale = [id.split("/", 1)[0], ...scopePath.split("/"), document.finalUrl.pathname.split("/").filter(Boolean)[0]].find((segment) => segment && LOCALES.has(segment.toLowerCase()));
|
|
3649
3988
|
if (urlLocale) candidates.push({ source: "URL", value: canonicalLanguage(urlLocale) });
|
|
3650
|
-
const declared = /html/i.test(document.contentType) ?
|
|
3989
|
+
const declared = /html/i.test(document.contentType) ? load5(document.body)("html").attr("lang")?.trim() : void 0;
|
|
3651
3990
|
if (declared) {
|
|
3652
3991
|
const locale = canonicalLanguage(declared);
|
|
3653
3992
|
if (locale) candidates.push({ source: "html lang", value: locale });
|
|
@@ -3724,13 +4063,15 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3724
4063
|
const projection = localeProjection(checkpoint);
|
|
3725
4064
|
const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!projection.canonicalLocale(record.page) || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [projection.navigationId(record.page)] : []));
|
|
3726
4065
|
const outputIds = /* @__PURE__ */ new Set();
|
|
3727
|
-
const
|
|
4066
|
+
const resolve4 = (url) => {
|
|
3728
4067
|
const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
|
|
3729
4068
|
return resolveMigrationRecord(records, record);
|
|
3730
4069
|
};
|
|
3731
4070
|
for (const record of checkpoint.records) {
|
|
3732
4071
|
counts[record.outcome]++;
|
|
3733
4072
|
issues.push(...record.issues ?? []);
|
|
4073
|
+
warnings.push(...record.warnings ?? []);
|
|
4074
|
+
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." });
|
|
3734
4075
|
if (record.outcome === "pending" || record.outcome === "failed") issues.push({ code: "missing-page", source: record.url, message: record.reason ?? `Inventory entry is ${record.outcome}.` });
|
|
3735
4076
|
if (!record.page || !record.evidence) continue;
|
|
3736
4077
|
const outputId = projection.pageId(record.page);
|
|
@@ -3749,8 +4090,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3749
4090
|
continue;
|
|
3750
4091
|
}
|
|
3751
4092
|
if (!inScope(link, source, checkpoint.scopePath)) continue;
|
|
4093
|
+
if (isDiscoveryIndex(link, source)) continue;
|
|
3752
4094
|
if (/\.(?:png|jpe?g|gif|svg|webp|avif|pdf|zip|mp4|webm|css|js|json|ya?ml)$/i.test(link.pathname)) continue;
|
|
3753
|
-
const destination =
|
|
4095
|
+
const destination = resolve4(link.toString());
|
|
3754
4096
|
if (!destination || !["imported", "excluded"].includes(destination.outcome)) {
|
|
3755
4097
|
issues.push({ code: "missing-page", source: record.url, message: `Unresolved internal destination: ${link}` });
|
|
3756
4098
|
} else if (link.hash && destination.outcome === "imported") {
|
|
@@ -3777,7 +4119,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3777
4119
|
if (node.url) {
|
|
3778
4120
|
const url = new URL(node.url, source);
|
|
3779
4121
|
if (inScope(url, source, checkpoint.scopePath)) {
|
|
3780
|
-
const
|
|
4122
|
+
const destination = resolve4(url.toString());
|
|
4123
|
+
if (destination?.outcome === "excluded" && destination.redirectTo) continue;
|
|
4124
|
+
const page = destination?.page;
|
|
3781
4125
|
if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
|
|
3782
4126
|
else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
|
|
3783
4127
|
issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
|
|
@@ -3794,16 +4138,32 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3794
4138
|
async function runUrlMigration(options) {
|
|
3795
4139
|
const source = validateMigrationUrl(options.sourceUrl);
|
|
3796
4140
|
const sourceUrl = canonical(source.toString(), source.toString());
|
|
3797
|
-
const requestedScope = options.scopePath ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
|
|
3798
|
-
let scopeEnd = requestedScope.length;
|
|
3799
|
-
while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
|
|
3800
|
-
const scopePath = requestedScope.slice(0, scopeEnd) || "/";
|
|
3801
|
-
if (!scopePath.startsWith("/") || scopePath.includes("?") || scopePath.includes("#") || !inScope(source, source, scopePath)) throw new Error("The selected documentation scope must contain the source URL.");
|
|
3802
4141
|
const maxPages = options.maxPages ?? 1e3;
|
|
3803
4142
|
if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 1e4) throw new Error("maxPages must be between 1 and 10000.");
|
|
3804
4143
|
const concurrency = Math.max(1, Math.min(32, Math.floor(options.concurrency ?? 5)));
|
|
3805
4144
|
const batchSize = Math.max(1, Math.min(500, Math.floor(options.batchSize ?? 50)));
|
|
3806
4145
|
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.");
|
|
4146
|
+
const fetcher = options.fetcher ?? defaultMigrationFetcher;
|
|
4147
|
+
const acquire = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
|
|
4148
|
+
let initial;
|
|
4149
|
+
let inferredScope;
|
|
4150
|
+
if (options.scopePath === void 0 && !options.checkpoint && !options.signal?.aborted) {
|
|
4151
|
+
initial = Promise.resolve().then(() => acquire({ url: source, signal: options.signal }));
|
|
4152
|
+
try {
|
|
4153
|
+
const document = await initial;
|
|
4154
|
+
if (document.finalUrl.origin === source.origin) {
|
|
4155
|
+
const inferred = inferUrlMigrationScope(document).scopePath;
|
|
4156
|
+
if (inScope(source, source, inferred)) inferredScope = inferred;
|
|
4157
|
+
}
|
|
4158
|
+
} catch {
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
const capture = (request) => initial && canonical(request.url.href, sourceUrl) === sourceUrl ? initial : acquire(request);
|
|
4162
|
+
const requestedScope = options.scopePath ?? options.checkpoint?.scopePath ?? inferredScope ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
|
|
4163
|
+
let scopeEnd = requestedScope.length;
|
|
4164
|
+
while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
|
|
4165
|
+
const scopePath = requestedScope.slice(0, scopeEnd) || "/";
|
|
4166
|
+
if (!scopePath.startsWith("/") || scopePath.includes("?") || scopePath.includes("#") || !inScope(source, source, scopePath)) throw new Error("The selected documentation scope must contain the source URL.");
|
|
3807
4167
|
const captureVersion = options.captureVersion ?? "fetch-v1";
|
|
3808
4168
|
if (options.checkpoint && options.checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
3809
4169
|
const checkpoint = options.checkpoint ? cloneUrlMigrationCheckpoint(options.checkpoint) : { schemaVersion: 1, converterVersion: CONVERTER_VERSION, sourceUrl, scopePath, captureVersion, records: [], navigation: [] };
|
|
@@ -3822,6 +4182,8 @@ async function runUrlMigration(options) {
|
|
|
3822
4182
|
delete record.apiSpecification;
|
|
3823
4183
|
delete record.version;
|
|
3824
4184
|
delete record.issues;
|
|
4185
|
+
delete record.warnings;
|
|
4186
|
+
delete record.redirectTo;
|
|
3825
4187
|
delete record.reason;
|
|
3826
4188
|
delete record.aliasOf;
|
|
3827
4189
|
}
|
|
@@ -3834,6 +4196,7 @@ async function runUrlMigration(options) {
|
|
|
3834
4196
|
const enqueue = (value, base, discoveredBy) => {
|
|
3835
4197
|
const url = canonical(value, base);
|
|
3836
4198
|
if (!url || !inScope(new URL(url), source, scopePath) && !isDiscoveryIndex(new URL(url), source)) return;
|
|
4199
|
+
const isAttachment = discoveredBy !== "submitted" && /\.(?:json|ya?ml)$/i.test(new URL(url).pathname);
|
|
3837
4200
|
const existing = records.get(url);
|
|
3838
4201
|
if (existing) {
|
|
3839
4202
|
if (!existing.discoveredBy.includes(discoveredBy)) {
|
|
@@ -3843,10 +4206,15 @@ async function runUrlMigration(options) {
|
|
|
3843
4206
|
return;
|
|
3844
4207
|
}
|
|
3845
4208
|
if (records.size >= MAX_RECORDS) throw new Error("Discovered URL inventory exceeded the 100000 URL safety bound.");
|
|
3846
|
-
const record = {
|
|
4209
|
+
const record = {
|
|
4210
|
+
url,
|
|
4211
|
+
discoveredBy: [discoveredBy],
|
|
4212
|
+
outcome: isAttachment ? "excluded" : "pending",
|
|
4213
|
+
...isAttachment ? { reason: "Machine-readable attachment, not an authored article." } : {}
|
|
4214
|
+
};
|
|
3847
4215
|
records.set(url, record);
|
|
3848
4216
|
checkpoint.records.push(record);
|
|
3849
|
-
pending.push(record);
|
|
4217
|
+
if (!isAttachment) pending.push(record);
|
|
3850
4218
|
changed.push(record);
|
|
3851
4219
|
};
|
|
3852
4220
|
enqueue(sourceUrl, sourceUrl, "submitted");
|
|
@@ -3879,8 +4247,6 @@ async function runUrlMigration(options) {
|
|
|
3879
4247
|
}
|
|
3880
4248
|
}
|
|
3881
4249
|
enqueueNavigation(checkpoint.navigation, sourceUrl);
|
|
3882
|
-
const fetcher = options.fetcher ?? defaultMigrationFetcher;
|
|
3883
|
-
const capture = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
|
|
3884
4250
|
const assetFetcher = options.assetFetcher ?? (!options.fetcher && !options.captureProvider ? defaultAssetFetcher : void 0);
|
|
3885
4251
|
const usedIds = new Map([...records.values()].filter((record) => record.page).map((record) => [record.page.id, record.url]));
|
|
3886
4252
|
const finalUrls = new Map([...records.values()].filter((record) => record.evidence && record.page).map((record) => [canonical(record.evidence.finalUrl, sourceUrl), record.url]));
|
|
@@ -3888,6 +4254,10 @@ async function runUrlMigration(options) {
|
|
|
3888
4254
|
const assetPayloads = new Map(checkpoint.records.flatMap((record) => (record.assets ?? []).map((asset) => [asset.sha256, asset])));
|
|
3889
4255
|
const assetBytes = /* @__PURE__ */ new Map();
|
|
3890
4256
|
const retainAsset = (asset) => {
|
|
4257
|
+
if (asset.unavailable) {
|
|
4258
|
+
if ([404, 410].includes(asset.unavailable.status) && asset.content.byteLength === 0) return asset;
|
|
4259
|
+
throw new Error("Invalid source asset availability evidence.");
|
|
4260
|
+
}
|
|
3891
4261
|
if (!acceptedAsset(asset)) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
3892
4262
|
const sha256 = hash(asset.content);
|
|
3893
4263
|
const content = assetBytes.get(sha256) ?? Buffer.from(asset.content);
|
|
@@ -3920,6 +4290,11 @@ async function runUrlMigration(options) {
|
|
|
3920
4290
|
let markdownRepresentation;
|
|
3921
4291
|
try {
|
|
3922
4292
|
delete record.reason;
|
|
4293
|
+
if (/\/llms-full\.txt$/i.test(new URL(record.url).pathname)) {
|
|
4294
|
+
record.outcome = "excluded";
|
|
4295
|
+
record.reason = "Aggregate documentation corpus; individual articles are inventoried separately.";
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
3923
4298
|
delete record.apiSpecification;
|
|
3924
4299
|
const represented = resolveMigrationRecord(records, record.representationOf ? records.get(record.representationOf) : void 0);
|
|
3925
4300
|
if (represented?.outcome === "imported") {
|
|
@@ -3929,6 +4304,16 @@ async function runUrlMigration(options) {
|
|
|
3929
4304
|
}
|
|
3930
4305
|
const document = await capture({ url: new URL(record.url), signal: options.signal });
|
|
3931
4306
|
if (options.signal?.aborted) return;
|
|
4307
|
+
if (document.status && document.status >= 300 && document.status < 400 && document.headers?.location) {
|
|
4308
|
+
const destination = canonical(document.headers.location, document.finalUrl.href);
|
|
4309
|
+
if (destination && new URL(destination).origin !== source.origin) {
|
|
4310
|
+
record.outcome = "excluded";
|
|
4311
|
+
record.redirectTo = new URL(document.headers.location, document.finalUrl).href;
|
|
4312
|
+
record.reason = "The source entry is a redirect to an external public resource, retained as a link.";
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
throw new Error("Capture returned an unresolved redirect.");
|
|
4316
|
+
}
|
|
3932
4317
|
if (Buffer.byteLength(document.body) > MAX_RESPONSE_BYTES) throw new Error("Captured response exceeds the 8 MB safety bound.");
|
|
3933
4318
|
const identity = articleIdentity(document);
|
|
3934
4319
|
const final = canonical(identity.toString(), sourceUrl);
|
|
@@ -3936,6 +4321,7 @@ async function runUrlMigration(options) {
|
|
|
3936
4321
|
if (!inScope(document.finalUrl, source, scopePath) && !(role === "index" && isDiscoveryIndex(document.finalUrl, source))) {
|
|
3937
4322
|
record.outcome = "excluded";
|
|
3938
4323
|
record.reason = "Redirected outside the selected origin/path.";
|
|
4324
|
+
if (document.finalUrl.origin === source.origin && (!document.status || document.status >= 200 && document.status < 300)) record.redirectTo = document.finalUrl.href;
|
|
3939
4325
|
return;
|
|
3940
4326
|
}
|
|
3941
4327
|
if (document.status && document.status >= 400) throw new Error(`Source page returned HTTP ${document.status}.`);
|
|
@@ -3953,7 +4339,7 @@ async function runUrlMigration(options) {
|
|
|
3953
4339
|
return;
|
|
3954
4340
|
}
|
|
3955
4341
|
if (/html/i.test(document.contentType)) {
|
|
3956
|
-
const $ =
|
|
4342
|
+
const $ = load5(document.body);
|
|
3957
4343
|
$('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
|
|
3958
4344
|
const alternate = canonical($(node).attr("href"), final);
|
|
3959
4345
|
if (!alternate) return;
|
|
@@ -3967,7 +4353,7 @@ async function runUrlMigration(options) {
|
|
|
3967
4353
|
changed.push(sibling);
|
|
3968
4354
|
});
|
|
3969
4355
|
}
|
|
3970
|
-
const observedNavigation = sourceNavigation(document);
|
|
4356
|
+
const observedNavigation = sourceNavigation(document, scopePath);
|
|
3971
4357
|
if (observedNavigation.length && !(checkpoint.navigation.some((node) => node.kind === "tab") && !observedNavigation.some((node) => node.kind === "tab"))) {
|
|
3972
4358
|
const merged = mergeSourceNavigation(checkpoint.navigation, observedNavigation);
|
|
3973
4359
|
if (JSON.stringify(merged) !== JSON.stringify(checkpoint.navigation)) {
|
|
@@ -4009,6 +4395,7 @@ async function runUrlMigration(options) {
|
|
|
4009
4395
|
record.reason = "Content could not be represented as safe inert MDX.";
|
|
4010
4396
|
return;
|
|
4011
4397
|
}
|
|
4398
|
+
let coveredSchemaGroups = [];
|
|
4012
4399
|
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;
|
|
4013
4400
|
if (/html/i.test(document.contentType) && markdownRepresentation && (unresolvedStaticControls(document.body) || hasSchemaChoice)) {
|
|
4014
4401
|
const schemaCapture = await capture({ url: new URL(markdownRepresentation), signal: options.signal });
|
|
@@ -4024,6 +4411,7 @@ async function runUrlMigration(options) {
|
|
|
4024
4411
|
const path = `openapi/migration-${sha256}.json`;
|
|
4025
4412
|
record.apiSpecification = { path, sha256, document: fragment.document };
|
|
4026
4413
|
page.openapi = `${path} ${fragment.method} ${fragment.path}`;
|
|
4414
|
+
coveredSchemaGroups = apiSchemaTabGroups(document.body, fragment.document, fragment);
|
|
4027
4415
|
}
|
|
4028
4416
|
}
|
|
4029
4417
|
const states = document.states ?? [];
|
|
@@ -4053,17 +4441,24 @@ ${difference}`;
|
|
|
4053
4441
|
}
|
|
4054
4442
|
record.assets = [];
|
|
4055
4443
|
record.issues = [];
|
|
4444
|
+
record.warnings = [];
|
|
4056
4445
|
if (/html/i.test(document.contentType)) {
|
|
4057
4446
|
const { $, root } = migrationHtmlArticle(document.body);
|
|
4058
4447
|
root.find("nav,footer,aside,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
|
|
4059
4448
|
root.find('button svg,a.hash-link svg,[aria-hidden="true"] svg,svg[aria-hidden="true"],svg[data-lucide],svg[data-icon]').remove();
|
|
4449
|
+
root.find("video,iframe").each((_index, node) => {
|
|
4450
|
+
if (projectMigrationMedia($.html(node), document.finalUrl) !== null) {
|
|
4451
|
+
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." });
|
|
4452
|
+
$(node).remove();
|
|
4453
|
+
}
|
|
4454
|
+
});
|
|
4060
4455
|
if (record.apiSpecification) {
|
|
4061
4456
|
root.find('select[aria-label="Select schema type"],select[aria-label="\u9009\u62E9\u67B6\u6784\u7C7B\u578B"]').remove();
|
|
4062
4457
|
root.find('.option-dropdown svg[data-component-part="icon-svg"]').remove();
|
|
4063
4458
|
}
|
|
4064
4459
|
const unsupported = [...new Set(root.find("iframe,object,embed,canvas,svg,form,input,textarea,select,math,video,audio").toArray().map((node) => node.tagName))];
|
|
4065
4460
|
if (unsupported.length) record.issues.push({ code: "unsupported-component", source: record.url, message: `Rendered content requires explicit review for: ${unsupported.join(", ")}.` });
|
|
4066
|
-
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." });
|
|
4461
|
+
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." });
|
|
4067
4462
|
}
|
|
4068
4463
|
const suppliedAssets = new Map((document.assets ?? []).map((asset) => [canonical(asset.url, final), asset]));
|
|
4069
4464
|
const assetUrls = [...new Set([...assetReferences(document), ...states.flatMap((state) => assetReferences(state.response))].map((url) => canonical(url, final)).filter((url) => Boolean(url)))];
|
|
@@ -4077,6 +4472,11 @@ ${difference}`;
|
|
|
4077
4472
|
const supplied = suppliedAssets.get(url);
|
|
4078
4473
|
const asset = supplied ? retainAsset(supplied) : assetRequest ? await assetRequest : null;
|
|
4079
4474
|
if (!asset) throw new Error("Capture host did not supply asset bytes or a binary fetch boundary.");
|
|
4475
|
+
if (asset.unavailable && [404, 410].includes(asset.unavailable.status) && asset.content.byteLength === 0) {
|
|
4476
|
+
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}` });
|
|
4477
|
+
page.body = rewriteMarkdownDestinations(page.body, (destination) => canonical(destination, final) === url ? url : destination);
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
4080
4480
|
const extension = acceptedAsset(asset);
|
|
4081
4481
|
if (!extension) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
4082
4482
|
const sha256 = hash(asset.content);
|
|
@@ -4118,7 +4518,7 @@ ${difference}`;
|
|
|
4118
4518
|
} catch (error) {
|
|
4119
4519
|
if (options.signal?.aborted) return;
|
|
4120
4520
|
const wasImported = record.outcome === "imported";
|
|
4121
|
-
record.outcome = record.discoveredBy.every((origin) => origin === "discovery-index") ? "excluded" : "failed";
|
|
4521
|
+
record.outcome = isDiscoveryIndex(new URL(record.url), source) || record.discoveredBy.every((origin) => origin === "discovery-index") ? "excluded" : "failed";
|
|
4122
4522
|
record.reason = error instanceof Error ? error.message : String(error);
|
|
4123
4523
|
if (record.page) {
|
|
4124
4524
|
if (wasImported) imported--;
|
|
@@ -4143,7 +4543,7 @@ ${difference}`;
|
|
|
4143
4543
|
for (const record of records.values()) {
|
|
4144
4544
|
if (!record.representationOf) continue;
|
|
4145
4545
|
const primary = resolveMigrationRecord(records, records.get(record.representationOf));
|
|
4146
|
-
if (!primary) continue;
|
|
4546
|
+
if (!primary || primary === record) continue;
|
|
4147
4547
|
const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
|
|
4148
4548
|
if (!winner) continue;
|
|
4149
4549
|
const alternate = winner === primary ? record : primary;
|
|
@@ -4269,7 +4669,7 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
4269
4669
|
return { start: start + index, end: start + index + node.url.length, replacement };
|
|
4270
4670
|
}).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
|
|
4271
4671
|
for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
|
|
4272
|
-
if (!attribute.name || !["src", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
|
|
4672
|
+
if (!attribute.name || !["src", "poster", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;
|
|
4273
4673
|
let value = attribute.value;
|
|
4274
4674
|
if (typeof value !== "string") {
|
|
4275
4675
|
try {
|
|
@@ -4300,7 +4700,7 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
4300
4700
|
// src/components.ts
|
|
4301
4701
|
import { createHash as createHash2 } from "crypto";
|
|
4302
4702
|
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
4303
|
-
import { basename, dirname as dirname2, extname as extname3, posix as posix2, relative as relative3, resolve as
|
|
4703
|
+
import { basename, dirname as dirname2, extname as extname3, posix as posix2, relative as relative3, resolve as resolve3 } from "path";
|
|
4304
4704
|
import postcss from "postcss";
|
|
4305
4705
|
import selectorParser from "postcss-selector-parser";
|
|
4306
4706
|
import remarkMdx4 from "remark-mdx";
|
|
@@ -4522,6 +4922,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
4522
4922
|
}
|
|
4523
4923
|
export {
|
|
4524
4924
|
classifyMigrationResponse,
|
|
4925
|
+
inferUrlMigrationScope,
|
|
4525
4926
|
renderMigrationFiles,
|
|
4526
4927
|
runUrlMigration,
|
|
4527
4928
|
validateUrlMigration
|