@thallylabs/migrate 0.2.31 → 0.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14358,7 +14358,7 @@ function mergeSourceNavigation(existing, incoming) {
14358
14358
  }
14359
14359
 
14360
14360
  // src/url-run.ts
14361
- var CONVERTER_VERSION = "url-evidence-v3";
14361
+ var CONVERTER_VERSION = "url-evidence-v4";
14362
14362
  var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
14363
14363
  var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
14364
14364
  var MAX_RESPONSE_BYTES = 8e6;
@@ -14617,21 +14617,22 @@ function resolveMigrationRecord(records, record) {
14617
14617
  }
14618
14618
  return record;
14619
14619
  }
14620
- function navigationConfig(navigation, records) {
14620
+ function navigationConfig(checkpoint, records) {
14621
+ const navigation = checkpoint.navigation;
14622
+ const projection = localeProjection(checkpoint);
14621
14623
  const project2 = (nodes) => nodes.flatMap((node) => {
14622
14624
  const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
14623
14625
  const target2 = resolveMigrationRecord(records, record);
14624
14626
  const page = target2?.page;
14625
14627
  const children = project2(node.children ?? []);
14626
14628
  if (children.length) {
14627
- const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === page?.navigationId : contains(entry.pages));
14628
- return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [page.navigationId] : [], ...children] }];
14629
+ const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
14630
+ return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [projection.navigationId(page)] : [], ...children] }];
14629
14631
  }
14630
- return page ? [page.navigationId] : [];
14632
+ return page ? [projection.navigationId(page)] : [];
14631
14633
  });
14632
14634
  const tabs = navigation.filter((node) => node.kind === "tab");
14633
14635
  if (tabs.length) {
14634
- const projection = localeProjection({ records: [...records.values()] });
14635
14636
  const locales = [...new Set(tabs.map((tab) => tab.locale ?? projection.defaultLocale))];
14636
14637
  const localizedTabs = (locale) => tabs.filter((tab) => (tab.locale ?? projection.defaultLocale) === locale).flatMap((tab) => {
14637
14638
  const pages2 = project2(tab.children ?? []);
@@ -14649,16 +14650,16 @@ function navigationConfig(navigation, records) {
14649
14650
  const pages = project2(navigation);
14650
14651
  return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
14651
14652
  }
14653
+ function canonicalLanguage(value) {
14654
+ if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
14655
+ try {
14656
+ return Intl.getCanonicalLocales(value)[0];
14657
+ } catch {
14658
+ return void 0;
14659
+ }
14660
+ }
14652
14661
  function observedLocale(document, id, scopePath) {
14653
14662
  const issues = [];
14654
- const canonicalLanguage = (value) => {
14655
- if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
14656
- try {
14657
- return Intl.getCanonicalLocales(value)[0];
14658
- } catch {
14659
- return void 0;
14660
- }
14661
- };
14662
14663
  const candidates = [];
14663
14664
  if (document.locale) {
14664
14665
  const locale = canonicalLanguage(document.locale);
@@ -14680,25 +14681,60 @@ function observedLocale(document, id, scopePath) {
14680
14681
  }
14681
14682
  function localeProjection(checkpoint) {
14682
14683
  const pages = checkpoint.records.flatMap((record) => record.page ? [record.page] : []);
14683
- const canonicalLocale = (page) => {
14684
+ const declaredLocale = (page) => {
14684
14685
  try {
14685
14686
  return page.locale ? Intl.getCanonicalLocales(page.locale)[0] : void 0;
14686
14687
  } catch {
14687
14688
  return void 0;
14688
14689
  }
14689
14690
  };
14691
+ const declaredLocales = [...new Set(pages.map(declaredLocale).filter((locale) => Boolean(locale)))];
14692
+ const defaultLocale = pages.some((page) => !page.locale) || declaredLocales.includes("en") ? "en" : declaredLocales[0] ?? "en";
14693
+ const records = new Map(checkpoint.records.map((record) => [record.url, record]));
14694
+ const memberships = /* @__PURE__ */ new Map();
14695
+ const observe = (nodes, parentLocale) => {
14696
+ for (const node of nodes) {
14697
+ const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
14698
+ const entry = node.url ? records.get(canonical(node.url, checkpoint.sourceUrl) ?? "") : void 0;
14699
+ const page = resolveMigrationRecord(records, entry)?.page;
14700
+ if (page && locale) {
14701
+ const languages = memberships.get(page) ?? /* @__PURE__ */ new Set();
14702
+ languages.add(locale);
14703
+ memberships.set(page, languages);
14704
+ }
14705
+ observe(node.children ?? [], locale);
14706
+ }
14707
+ };
14708
+ observe(checkpoint.navigation);
14709
+ const primaryIds = new Set(pages.filter((page) => !declaredLocale(page) || declaredLocale(page) === defaultLocale).filter((page) => memberships.get(page)?.has(defaultLocale)).map((page) => page.navigationId));
14710
+ const remapped = /* @__PURE__ */ new Map();
14711
+ for (const page of pages) {
14712
+ const languages = memberships.get(page);
14713
+ if (languages?.size !== 1) continue;
14714
+ const locale = [...languages][0];
14715
+ const prefix = `${locale.toLowerCase()}-`;
14716
+ if (locale === defaultLocale || !page.navigationId.toLowerCase().startsWith(prefix)) continue;
14717
+ const navigationId2 = page.navigationId.slice(prefix.length);
14718
+ if (primaryIds.has(navigationId2)) remapped.set(page, { locale, navigationId: navigationId2 });
14719
+ }
14720
+ const canonicalLocale = (page) => remapped.get(page)?.locale ?? declaredLocale(page);
14721
+ const navigationId = (page) => remapped.get(page)?.navigationId ?? page.navigationId;
14690
14722
  const locales = [...new Set(pages.map(canonicalLocale).filter((locale) => Boolean(locale)))];
14691
- const defaultLocale = pages.some((page) => !page.locale) || locales.includes("en") ? "en" : locales[0] ?? "en";
14692
14723
  const pageId = (page) => {
14693
14724
  const locale = canonicalLocale(page);
14694
- return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${page.navigationId}` : page.id;
14725
+ return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${navigationId(page)}` : page.id;
14695
14726
  };
14696
14727
  const route = (page) => {
14697
14728
  const locale = canonicalLocale(page);
14698
14729
  const prefix = locale && locale !== defaultLocale ? `/${locale}` : "";
14699
- return `${prefix}${page.navigationId === "introduction" ? "/" : `/${page.navigationId}`}`;
14730
+ return `${prefix}${navigationId(page) === "introduction" ? "/" : `/${navigationId(page)}`}`;
14700
14731
  };
14701
- return { canonicalLocale, locales, defaultLocale, pageId, route };
14732
+ const redirects = checkpoint.records.flatMap((record) => {
14733
+ if (!record.page || !remapped.has(record.page)) return [];
14734
+ const source = new URL(record.url);
14735
+ return source.search || !/^\/[A-Za-z0-9/_~.%+-]*$/.test(source.pathname) ? [] : [{ source: source.pathname, destination: route(record.page), permanent: true }];
14736
+ });
14737
+ return { canonicalLocale, navigationId, locales, defaultLocale, pageId, route, redirects };
14702
14738
  }
14703
14739
  function validateUrlMigration(checkpoint, cancelled = false) {
14704
14740
  const issues = [];
@@ -14707,7 +14743,7 @@ function validateUrlMigration(checkpoint, cancelled = false) {
14707
14743
  const records = new Map(checkpoint.records.map((record) => [record.url, record]));
14708
14744
  const source = new URL(checkpoint.sourceUrl);
14709
14745
  const projection = localeProjection(checkpoint);
14710
- const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!record.page.locale || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [record.page.navigationId] : []));
14746
+ const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!projection.canonicalLocale(record.page) || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [projection.navigationId(record.page)] : []));
14711
14747
  const outputIds = /* @__PURE__ */ new Set();
14712
14748
  const resolve4 = (url) => {
14713
14749
  const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
@@ -14721,8 +14757,8 @@ function validateUrlMigration(checkpoint, cancelled = false) {
14721
14757
  const outputId = projection.pageId(record.page);
14722
14758
  if (outputIds.has(outputId)) issues.push({ code: "collision", source: record.url, message: `Multiple source pages map to runtime path ${outputId}.` });
14723
14759
  outputIds.add(outputId);
14724
- if (record.page.locale && projection.canonicalLocale(record.page) !== projection.defaultLocale && !primaryIds.has(record.page.navigationId)) {
14725
- issues.push({ code: "missing-page", source: record.url, message: `Secondary locale ${record.page.locale} requires a primary ${projection.defaultLocale} page at ${record.page.navigationId}; the runtime cannot serve an orphan translation.` });
14760
+ if (projection.canonicalLocale(record.page) && projection.canonicalLocale(record.page) !== projection.defaultLocale && !primaryIds.has(projection.navigationId(record.page))) {
14761
+ issues.push({ code: "missing-page", source: record.url, message: `Secondary locale ${projection.canonicalLocale(record.page)} requires a primary ${projection.defaultLocale} page at ${projection.navigationId(record.page)}; the runtime cannot serve an orphan translation.` });
14726
14762
  }
14727
14763
  const actual = markdownCode(record.page.body);
14728
14764
  if (JSON.stringify(actual) !== JSON.stringify(record.evidence.code)) issues.push({ code: "code-mismatch", source: record.url, message: "Source code payloads differ from converted code payloads." });
@@ -14756,13 +14792,20 @@ function validateUrlMigration(checkpoint, cancelled = false) {
14756
14792
  }
14757
14793
  }
14758
14794
  }
14759
- const visit = (nodes) => {
14795
+ const visit = (nodes, parentLocale) => {
14760
14796
  for (const node of nodes) {
14797
+ const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
14761
14798
  if (node.url) {
14762
14799
  const url = new URL(node.url, source);
14763
- if (inScope(url, source, checkpoint.scopePath) && !resolve4(url.toString())?.page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
14800
+ if (inScope(url, source, checkpoint.scopePath)) {
14801
+ const page = resolve4(url.toString())?.page;
14802
+ if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
14803
+ else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
14804
+ issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
14805
+ }
14806
+ }
14764
14807
  }
14765
- visit(node.children ?? []);
14808
+ visit(node.children ?? [], locale);
14766
14809
  }
14767
14810
  };
14768
14811
  visit(checkpoint.navigation);
@@ -15180,7 +15223,7 @@ ${difference}`;
15180
15223
  }
15181
15224
  };
15182
15225
  labelPages(checkpoint.navigation);
15183
- const pages = checkpoint.records.flatMap((record) => record.page ? [{ ...record.page, id: projection.pageId(record.page), ...record.page.locale ? { locale: projection.canonicalLocale(record.page) } : {}, ...navigationLabels.has(record.url) ? { navTitle: navigationLabels.get(record.url) } : {}, body: rewriteMarkdownDestinations(record.page.body, (value) => {
15226
+ const pages = checkpoint.records.flatMap((record) => record.page ? [{ ...record.page, id: projection.pageId(record.page), navigationId: projection.navigationId(record.page), ...projection.canonicalLocale(record.page) ? { locale: projection.canonicalLocale(record.page) } : {}, ...navigationLabels.has(record.url) ? { navTitle: navigationLabels.get(record.url) } : {}, body: rewriteMarkdownDestinations(record.page.body, (value) => {
15184
15227
  let url;
15185
15228
  try {
15186
15229
  url = new URL(value, record.evidence.finalUrl);
@@ -15210,11 +15253,12 @@ ${difference}`;
15210
15253
  platform: options.platform ?? "generic",
15211
15254
  pages,
15212
15255
  assets: [...assets.values()],
15213
- docsConfig: navigationConfig(checkpoint.navigation, records) ?? buildNavigationFromPages(pages),
15256
+ docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
15214
15257
  warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
15215
15258
  urlMigrationValidation: validation,
15216
15259
  stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
15217
15260
  };
15261
+ if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
15218
15262
  if (projection.locales.length) {
15219
15263
  const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
15220
15264
  bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };