@thallylabs/migrate 0.2.38 → 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 +620 -227
- package/dist/index.js.map +1 -1
- package/dist/url-migration.d.ts +11 -1
- package/dist/url-migration.js +460 -66
- 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,7 +3489,7 @@ ${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
3495
|
const language = ["code", "pre"].flatMap((selector) => {
|
|
@@ -3343,8 +3529,127 @@ ${fence}
|
|
|
3343
3529
|
};
|
|
3344
3530
|
}
|
|
3345
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
|
+
|
|
3346
3651
|
// src/url-run.ts
|
|
3347
|
-
var CONVERTER_VERSION = "url-evidence-
|
|
3652
|
+
var CONVERTER_VERSION = "url-evidence-v5";
|
|
3348
3653
|
var parser = unified3().use(remarkParse3).use(remarkMdx3);
|
|
3349
3654
|
var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
|
|
3350
3655
|
var MAX_RESPONSE_BYTES = 8e6;
|
|
@@ -3407,7 +3712,7 @@ function linksOf(document) {
|
|
|
3407
3712
|
return [...root.find("a[href]").toArray(), ...$('link[rel="sitemap"][href],link[rel="alternate"][href]').toArray()].map((node) => $(node).attr("href")).filter(Boolean);
|
|
3408
3713
|
}
|
|
3409
3714
|
if (/xml/i.test(document.contentType) || /<urlset|<sitemapindex/i.test(document.body)) {
|
|
3410
|
-
const $ =
|
|
3715
|
+
const $ = load5(document.body, { xmlMode: true });
|
|
3411
3716
|
return $("loc").toArray().map((node) => $(node).text().trim());
|
|
3412
3717
|
}
|
|
3413
3718
|
return [
|
|
@@ -3450,8 +3755,8 @@ function markdownHeadingAnchors(body) {
|
|
|
3450
3755
|
return occurrence ? `${base}-${occurrence}` : base;
|
|
3451
3756
|
});
|
|
3452
3757
|
}
|
|
3453
|
-
function sourceNavigation(capture) {
|
|
3454
|
-
return extractSourceNavigation(capture);
|
|
3758
|
+
function sourceNavigation(capture, selectedScope) {
|
|
3759
|
+
return extractSourceNavigation(capture, selectedScope);
|
|
3455
3760
|
}
|
|
3456
3761
|
function idForUrl(url, scope) {
|
|
3457
3762
|
const relative4 = scope === "/" ? url.pathname.slice(1) : url.pathname.slice(scope.length).replace(/^\//, "");
|
|
@@ -3460,7 +3765,7 @@ function idForUrl(url, scope) {
|
|
|
3460
3765
|
}
|
|
3461
3766
|
function articleIdentity(document) {
|
|
3462
3767
|
if (!/html/i.test(document.contentType)) return document.finalUrl;
|
|
3463
|
-
const $ =
|
|
3768
|
+
const $ = load5(document.body);
|
|
3464
3769
|
const reference = $('link[rel="canonical"][href]').first().attr("href");
|
|
3465
3770
|
if (!reference) return document.finalUrl;
|
|
3466
3771
|
try {
|
|
@@ -3516,16 +3821,24 @@ function assetReferences(capture) {
|
|
|
3516
3821
|
}
|
|
3517
3822
|
const { $, root } = migrationHtmlArticle(capture.body);
|
|
3518
3823
|
return [
|
|
3519
|
-
...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")),
|
|
3520
3825
|
...root.find("[srcset]").toArray().flatMap((node) => migrationImageCandidates($(node).attr("srcset") ?? "", capture.finalUrl).map((candidate) => candidate.url))
|
|
3521
3826
|
].filter(Boolean);
|
|
3522
3827
|
}
|
|
3523
|
-
function unresolvedStaticControls(body, hasApiSchema = false) {
|
|
3828
|
+
function unresolvedStaticControls(body, hasApiSchema = false, coveredSchemaGroups = []) {
|
|
3524
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
|
+
});
|
|
3525
3833
|
const tabs = root.find('[role="tab"]').toArray();
|
|
3526
3834
|
for (const tab of tabs) {
|
|
3527
3835
|
const controls = $(tab).attr("aria-controls");
|
|
3528
|
-
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)) : $([]);
|
|
3529
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;
|
|
3530
3843
|
}
|
|
3531
3844
|
return root.find("details:not([open])").toArray().some((node) => {
|
|
@@ -3576,12 +3889,12 @@ function hasCompleteLocalApiSchema(document) {
|
|
|
3576
3889
|
if (typeof value === "number" && !Number.isFinite(value)) return false;
|
|
3577
3890
|
if (!value || typeof value !== "object") return true;
|
|
3578
3891
|
if (Array.isArray(value)) return value.every((child) => visit(child, depth + 1));
|
|
3579
|
-
const
|
|
3580
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
3581
|
-
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;
|
|
3582
3895
|
let target2 = document;
|
|
3583
3896
|
try {
|
|
3584
|
-
for (const encoded of
|
|
3897
|
+
for (const encoded of object3.$ref.slice(2).split("/")) {
|
|
3585
3898
|
const key = decodeURIComponent(encoded).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3586
3899
|
if (!target2 || typeof target2 !== "object" || !Object.prototype.hasOwnProperty.call(target2, key)) return false;
|
|
3587
3900
|
target2 = target2[key];
|
|
@@ -3590,7 +3903,7 @@ function hasCompleteLocalApiSchema(document) {
|
|
|
3590
3903
|
return false;
|
|
3591
3904
|
}
|
|
3592
3905
|
}
|
|
3593
|
-
return Object.values(
|
|
3906
|
+
return Object.values(object3).every((child) => visit(child, depth + 1));
|
|
3594
3907
|
};
|
|
3595
3908
|
return visit(document);
|
|
3596
3909
|
}
|
|
@@ -3605,6 +3918,25 @@ function resolveMigrationRecord(records, record) {
|
|
|
3605
3918
|
}
|
|
3606
3919
|
function navigationConfig(checkpoint, records) {
|
|
3607
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
|
+
} } : {};
|
|
3608
3940
|
const projection = localeProjection(checkpoint);
|
|
3609
3941
|
const project = (nodes) => nodes.flatMap((node) => {
|
|
3610
3942
|
const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
|
|
@@ -3625,7 +3957,7 @@ function navigationConfig(checkpoint, records) {
|
|
|
3625
3957
|
return pages2.length || tab.url ? [{ tab: tab.label, ...pages2.length ? { pages: pages2 } : {}, ...tab.url ? { href: tab.url } : {} }] : [];
|
|
3626
3958
|
});
|
|
3627
3959
|
const primary = localizedTabs(projection.defaultLocale);
|
|
3628
|
-
return { tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
|
|
3960
|
+
return { ...externalNavigation, tabs: primary.length ? primary : localizedTabs(locales[0]), ...locales.length > 1 ? {
|
|
3629
3961
|
i18n: {
|
|
3630
3962
|
defaultLocale: projection.defaultLocale,
|
|
3631
3963
|
locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })),
|
|
@@ -3634,7 +3966,7 @@ function navigationConfig(checkpoint, records) {
|
|
|
3634
3966
|
} : {} };
|
|
3635
3967
|
}
|
|
3636
3968
|
const pages = project(navigation);
|
|
3637
|
-
return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
|
|
3969
|
+
return pages.length || shortcuts.length ? { ...externalNavigation, tabs: [{ tab: "Documentation", pages }] } : null;
|
|
3638
3970
|
}
|
|
3639
3971
|
function canonicalLanguage(value) {
|
|
3640
3972
|
if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
|
|
@@ -3654,7 +3986,7 @@ function observedLocale(document, id, scopePath) {
|
|
|
3654
3986
|
}
|
|
3655
3987
|
const urlLocale = [id.split("/", 1)[0], ...scopePath.split("/"), document.finalUrl.pathname.split("/").filter(Boolean)[0]].find((segment) => segment && LOCALES.has(segment.toLowerCase()));
|
|
3656
3988
|
if (urlLocale) candidates.push({ source: "URL", value: canonicalLanguage(urlLocale) });
|
|
3657
|
-
const declared = /html/i.test(document.contentType) ?
|
|
3989
|
+
const declared = /html/i.test(document.contentType) ? load5(document.body)("html").attr("lang")?.trim() : void 0;
|
|
3658
3990
|
if (declared) {
|
|
3659
3991
|
const locale = canonicalLanguage(declared);
|
|
3660
3992
|
if (locale) candidates.push({ source: "html lang", value: locale });
|
|
@@ -3731,13 +4063,15 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3731
4063
|
const projection = localeProjection(checkpoint);
|
|
3732
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)] : []));
|
|
3733
4065
|
const outputIds = /* @__PURE__ */ new Set();
|
|
3734
|
-
const
|
|
4066
|
+
const resolve4 = (url) => {
|
|
3735
4067
|
const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
|
|
3736
4068
|
return resolveMigrationRecord(records, record);
|
|
3737
4069
|
};
|
|
3738
4070
|
for (const record of checkpoint.records) {
|
|
3739
4071
|
counts[record.outcome]++;
|
|
3740
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." });
|
|
3741
4075
|
if (record.outcome === "pending" || record.outcome === "failed") issues.push({ code: "missing-page", source: record.url, message: record.reason ?? `Inventory entry is ${record.outcome}.` });
|
|
3742
4076
|
if (!record.page || !record.evidence) continue;
|
|
3743
4077
|
const outputId = projection.pageId(record.page);
|
|
@@ -3756,8 +4090,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3756
4090
|
continue;
|
|
3757
4091
|
}
|
|
3758
4092
|
if (!inScope(link, source, checkpoint.scopePath)) continue;
|
|
4093
|
+
if (isDiscoveryIndex(link, source)) continue;
|
|
3759
4094
|
if (/\.(?:png|jpe?g|gif|svg|webp|avif|pdf|zip|mp4|webm|css|js|json|ya?ml)$/i.test(link.pathname)) continue;
|
|
3760
|
-
const destination =
|
|
4095
|
+
const destination = resolve4(link.toString());
|
|
3761
4096
|
if (!destination || !["imported", "excluded"].includes(destination.outcome)) {
|
|
3762
4097
|
issues.push({ code: "missing-page", source: record.url, message: `Unresolved internal destination: ${link}` });
|
|
3763
4098
|
} else if (link.hash && destination.outcome === "imported") {
|
|
@@ -3784,7 +4119,9 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3784
4119
|
if (node.url) {
|
|
3785
4120
|
const url = new URL(node.url, source);
|
|
3786
4121
|
if (inScope(url, source, checkpoint.scopePath)) {
|
|
3787
|
-
const
|
|
4122
|
+
const destination = resolve4(url.toString());
|
|
4123
|
+
if (destination?.outcome === "excluded" && destination.redirectTo) continue;
|
|
4124
|
+
const page = destination?.page;
|
|
3788
4125
|
if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
|
|
3789
4126
|
else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
|
|
3790
4127
|
issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
|
|
@@ -3801,16 +4138,32 @@ function validateUrlMigration(checkpoint, cancelled = false) {
|
|
|
3801
4138
|
async function runUrlMigration(options) {
|
|
3802
4139
|
const source = validateMigrationUrl(options.sourceUrl);
|
|
3803
4140
|
const sourceUrl = canonical(source.toString(), source.toString());
|
|
3804
|
-
const requestedScope = options.scopePath ?? (source.pathname.endsWith("/") ? source.pathname : source.pathname.slice(0, source.pathname.lastIndexOf("/")) || "/");
|
|
3805
|
-
let scopeEnd = requestedScope.length;
|
|
3806
|
-
while (scopeEnd > 0 && requestedScope.charCodeAt(scopeEnd - 1) === 47) scopeEnd--;
|
|
3807
|
-
const scopePath = requestedScope.slice(0, scopeEnd) || "/";
|
|
3808
|
-
if (!scopePath.startsWith("/") || scopePath.includes("?") || scopePath.includes("#") || !inScope(source, source, scopePath)) throw new Error("The selected documentation scope must contain the source URL.");
|
|
3809
4141
|
const maxPages = options.maxPages ?? 1e3;
|
|
3810
4142
|
if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 1e4) throw new Error("maxPages must be between 1 and 10000.");
|
|
3811
4143
|
const concurrency = Math.max(1, Math.min(32, Math.floor(options.concurrency ?? 5)));
|
|
3812
4144
|
const batchSize = Math.max(1, Math.min(500, Math.floor(options.batchSize ?? 50)));
|
|
3813
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.");
|
|
3814
4167
|
const captureVersion = options.captureVersion ?? "fetch-v1";
|
|
3815
4168
|
if (options.checkpoint && options.checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
3816
4169
|
const checkpoint = options.checkpoint ? cloneUrlMigrationCheckpoint(options.checkpoint) : { schemaVersion: 1, converterVersion: CONVERTER_VERSION, sourceUrl, scopePath, captureVersion, records: [], navigation: [] };
|
|
@@ -3829,6 +4182,8 @@ async function runUrlMigration(options) {
|
|
|
3829
4182
|
delete record.apiSpecification;
|
|
3830
4183
|
delete record.version;
|
|
3831
4184
|
delete record.issues;
|
|
4185
|
+
delete record.warnings;
|
|
4186
|
+
delete record.redirectTo;
|
|
3832
4187
|
delete record.reason;
|
|
3833
4188
|
delete record.aliasOf;
|
|
3834
4189
|
}
|
|
@@ -3841,6 +4196,7 @@ async function runUrlMigration(options) {
|
|
|
3841
4196
|
const enqueue = (value, base, discoveredBy) => {
|
|
3842
4197
|
const url = canonical(value, base);
|
|
3843
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);
|
|
3844
4200
|
const existing = records.get(url);
|
|
3845
4201
|
if (existing) {
|
|
3846
4202
|
if (!existing.discoveredBy.includes(discoveredBy)) {
|
|
@@ -3850,10 +4206,15 @@ async function runUrlMigration(options) {
|
|
|
3850
4206
|
return;
|
|
3851
4207
|
}
|
|
3852
4208
|
if (records.size >= MAX_RECORDS) throw new Error("Discovered URL inventory exceeded the 100000 URL safety bound.");
|
|
3853
|
-
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
|
+
};
|
|
3854
4215
|
records.set(url, record);
|
|
3855
4216
|
checkpoint.records.push(record);
|
|
3856
|
-
pending.push(record);
|
|
4217
|
+
if (!isAttachment) pending.push(record);
|
|
3857
4218
|
changed.push(record);
|
|
3858
4219
|
};
|
|
3859
4220
|
enqueue(sourceUrl, sourceUrl, "submitted");
|
|
@@ -3886,8 +4247,6 @@ async function runUrlMigration(options) {
|
|
|
3886
4247
|
}
|
|
3887
4248
|
}
|
|
3888
4249
|
enqueueNavigation(checkpoint.navigation, sourceUrl);
|
|
3889
|
-
const fetcher = options.fetcher ?? defaultMigrationFetcher;
|
|
3890
|
-
const capture = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
|
|
3891
4250
|
const assetFetcher = options.assetFetcher ?? (!options.fetcher && !options.captureProvider ? defaultAssetFetcher : void 0);
|
|
3892
4251
|
const usedIds = new Map([...records.values()].filter((record) => record.page).map((record) => [record.page.id, record.url]));
|
|
3893
4252
|
const finalUrls = new Map([...records.values()].filter((record) => record.evidence && record.page).map((record) => [canonical(record.evidence.finalUrl, sourceUrl), record.url]));
|
|
@@ -3895,6 +4254,10 @@ async function runUrlMigration(options) {
|
|
|
3895
4254
|
const assetPayloads = new Map(checkpoint.records.flatMap((record) => (record.assets ?? []).map((asset) => [asset.sha256, asset])));
|
|
3896
4255
|
const assetBytes = /* @__PURE__ */ new Map();
|
|
3897
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
|
+
}
|
|
3898
4261
|
if (!acceptedAsset(asset)) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
3899
4262
|
const sha256 = hash(asset.content);
|
|
3900
4263
|
const content = assetBytes.get(sha256) ?? Buffer.from(asset.content);
|
|
@@ -3927,6 +4290,11 @@ async function runUrlMigration(options) {
|
|
|
3927
4290
|
let markdownRepresentation;
|
|
3928
4291
|
try {
|
|
3929
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
|
+
}
|
|
3930
4298
|
delete record.apiSpecification;
|
|
3931
4299
|
const represented = resolveMigrationRecord(records, record.representationOf ? records.get(record.representationOf) : void 0);
|
|
3932
4300
|
if (represented?.outcome === "imported") {
|
|
@@ -3936,6 +4304,16 @@ async function runUrlMigration(options) {
|
|
|
3936
4304
|
}
|
|
3937
4305
|
const document = await capture({ url: new URL(record.url), signal: options.signal });
|
|
3938
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
|
+
}
|
|
3939
4317
|
if (Buffer.byteLength(document.body) > MAX_RESPONSE_BYTES) throw new Error("Captured response exceeds the 8 MB safety bound.");
|
|
3940
4318
|
const identity = articleIdentity(document);
|
|
3941
4319
|
const final = canonical(identity.toString(), sourceUrl);
|
|
@@ -3943,6 +4321,7 @@ async function runUrlMigration(options) {
|
|
|
3943
4321
|
if (!inScope(document.finalUrl, source, scopePath) && !(role === "index" && isDiscoveryIndex(document.finalUrl, source))) {
|
|
3944
4322
|
record.outcome = "excluded";
|
|
3945
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;
|
|
3946
4325
|
return;
|
|
3947
4326
|
}
|
|
3948
4327
|
if (document.status && document.status >= 400) throw new Error(`Source page returned HTTP ${document.status}.`);
|
|
@@ -3960,7 +4339,7 @@ async function runUrlMigration(options) {
|
|
|
3960
4339
|
return;
|
|
3961
4340
|
}
|
|
3962
4341
|
if (/html/i.test(document.contentType)) {
|
|
3963
|
-
const $ =
|
|
4342
|
+
const $ = load5(document.body);
|
|
3964
4343
|
$('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
|
|
3965
4344
|
const alternate = canonical($(node).attr("href"), final);
|
|
3966
4345
|
if (!alternate) return;
|
|
@@ -3974,7 +4353,7 @@ async function runUrlMigration(options) {
|
|
|
3974
4353
|
changed.push(sibling);
|
|
3975
4354
|
});
|
|
3976
4355
|
}
|
|
3977
|
-
const observedNavigation = sourceNavigation(document);
|
|
4356
|
+
const observedNavigation = sourceNavigation(document, scopePath);
|
|
3978
4357
|
if (observedNavigation.length && !(checkpoint.navigation.some((node) => node.kind === "tab") && !observedNavigation.some((node) => node.kind === "tab"))) {
|
|
3979
4358
|
const merged = mergeSourceNavigation(checkpoint.navigation, observedNavigation);
|
|
3980
4359
|
if (JSON.stringify(merged) !== JSON.stringify(checkpoint.navigation)) {
|
|
@@ -4016,6 +4395,7 @@ async function runUrlMigration(options) {
|
|
|
4016
4395
|
record.reason = "Content could not be represented as safe inert MDX.";
|
|
4017
4396
|
return;
|
|
4018
4397
|
}
|
|
4398
|
+
let coveredSchemaGroups = [];
|
|
4019
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;
|
|
4020
4400
|
if (/html/i.test(document.contentType) && markdownRepresentation && (unresolvedStaticControls(document.body) || hasSchemaChoice)) {
|
|
4021
4401
|
const schemaCapture = await capture({ url: new URL(markdownRepresentation), signal: options.signal });
|
|
@@ -4031,6 +4411,7 @@ async function runUrlMigration(options) {
|
|
|
4031
4411
|
const path = `openapi/migration-${sha256}.json`;
|
|
4032
4412
|
record.apiSpecification = { path, sha256, document: fragment.document };
|
|
4033
4413
|
page.openapi = `${path} ${fragment.method} ${fragment.path}`;
|
|
4414
|
+
coveredSchemaGroups = apiSchemaTabGroups(document.body, fragment.document, fragment);
|
|
4034
4415
|
}
|
|
4035
4416
|
}
|
|
4036
4417
|
const states = document.states ?? [];
|
|
@@ -4060,17 +4441,24 @@ ${difference}`;
|
|
|
4060
4441
|
}
|
|
4061
4442
|
record.assets = [];
|
|
4062
4443
|
record.issues = [];
|
|
4444
|
+
record.warnings = [];
|
|
4063
4445
|
if (/html/i.test(document.contentType)) {
|
|
4064
4446
|
const { $, root } = migrationHtmlArticle(document.body);
|
|
4065
4447
|
root.find("nav,footer,aside,.theme-doc-breadcrumbs,.theme-doc-toc-mobile,.table-of-contents,.hash-link").remove();
|
|
4066
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
|
+
});
|
|
4067
4455
|
if (record.apiSpecification) {
|
|
4068
4456
|
root.find('select[aria-label="Select schema type"],select[aria-label="\u9009\u62E9\u67B6\u6784\u7C7B\u578B"]').remove();
|
|
4069
4457
|
root.find('.option-dropdown svg[data-component-part="icon-svg"]').remove();
|
|
4070
4458
|
}
|
|
4071
4459
|
const unsupported = [...new Set(root.find("iframe,object,embed,canvas,svg,form,input,textarea,select,math,video,audio").toArray().map((node) => node.tagName))];
|
|
4072
4460
|
if (unsupported.length) record.issues.push({ code: "unsupported-component", source: record.url, message: `Rendered content requires explicit review for: ${unsupported.join(", ")}.` });
|
|
4073
|
-
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." });
|
|
4074
4462
|
}
|
|
4075
4463
|
const suppliedAssets = new Map((document.assets ?? []).map((asset) => [canonical(asset.url, final), asset]));
|
|
4076
4464
|
const assetUrls = [...new Set([...assetReferences(document), ...states.flatMap((state) => assetReferences(state.response))].map((url) => canonical(url, final)).filter((url) => Boolean(url)))];
|
|
@@ -4084,6 +4472,11 @@ ${difference}`;
|
|
|
4084
4472
|
const supplied = suppliedAssets.get(url);
|
|
4085
4473
|
const asset = supplied ? retainAsset(supplied) : assetRequest ? await assetRequest : null;
|
|
4086
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
|
+
}
|
|
4087
4480
|
const extension = acceptedAsset(asset);
|
|
4088
4481
|
if (!extension) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
4089
4482
|
const sha256 = hash(asset.content);
|
|
@@ -4125,7 +4518,7 @@ ${difference}`;
|
|
|
4125
4518
|
} catch (error) {
|
|
4126
4519
|
if (options.signal?.aborted) return;
|
|
4127
4520
|
const wasImported = record.outcome === "imported";
|
|
4128
|
-
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";
|
|
4129
4522
|
record.reason = error instanceof Error ? error.message : String(error);
|
|
4130
4523
|
if (record.page) {
|
|
4131
4524
|
if (wasImported) imported--;
|
|
@@ -4150,7 +4543,7 @@ ${difference}`;
|
|
|
4150
4543
|
for (const record of records.values()) {
|
|
4151
4544
|
if (!record.representationOf) continue;
|
|
4152
4545
|
const primary = resolveMigrationRecord(records, records.get(record.representationOf));
|
|
4153
|
-
if (!primary) continue;
|
|
4546
|
+
if (!primary || primary === record) continue;
|
|
4154
4547
|
const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
|
|
4155
4548
|
if (!winner) continue;
|
|
4156
4549
|
const alternate = winner === primary ? record : primary;
|
|
@@ -4276,7 +4669,7 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
4276
4669
|
return { start: start + index, end: start + index + node.url.length, replacement };
|
|
4277
4670
|
}).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
|
|
4278
4671
|
for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
|
|
4279
|
-
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;
|
|
4280
4673
|
let value = attribute.value;
|
|
4281
4674
|
if (typeof value !== "string") {
|
|
4282
4675
|
try {
|
|
@@ -4307,7 +4700,7 @@ function rewriteMarkdownDestinations(body, rewrite) {
|
|
|
4307
4700
|
// src/components.ts
|
|
4308
4701
|
import { createHash as createHash2 } from "crypto";
|
|
4309
4702
|
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
4310
|
-
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";
|
|
4311
4704
|
import postcss from "postcss";
|
|
4312
4705
|
import selectorParser from "postcss-selector-parser";
|
|
4313
4706
|
import remarkMdx4 from "remark-mdx";
|
|
@@ -4529,6 +4922,7 @@ function renderMigrationFiles(bundle, options = {}) {
|
|
|
4529
4922
|
}
|
|
4530
4923
|
export {
|
|
4531
4924
|
classifyMigrationResponse,
|
|
4925
|
+
inferUrlMigrationScope,
|
|
4532
4926
|
renderMigrationFiles,
|
|
4533
4927
|
runUrlMigration,
|
|
4534
4928
|
validateUrlMigration
|