@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.
@@ -3299,7 +3299,7 @@ ${fence}
3299
3299
  }
3300
3300
 
3301
3301
  // src/url-run.ts
3302
- var CONVERTER_VERSION = "url-evidence-v3";
3302
+ var CONVERTER_VERSION = "url-evidence-v4";
3303
3303
  var parser = unified3().use(remarkParse3).use(remarkMdx3);
3304
3304
  var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
3305
3305
  var MAX_RESPONSE_BYTES = 8e6;
@@ -3558,21 +3558,22 @@ function resolveMigrationRecord(records, record) {
3558
3558
  }
3559
3559
  return record;
3560
3560
  }
3561
- function navigationConfig(navigation, records) {
3561
+ function navigationConfig(checkpoint, records) {
3562
+ const navigation = checkpoint.navigation;
3563
+ const projection = localeProjection(checkpoint);
3562
3564
  const project = (nodes) => nodes.flatMap((node) => {
3563
3565
  const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
3564
3566
  const target2 = resolveMigrationRecord(records, record);
3565
3567
  const page = target2?.page;
3566
3568
  const children = project(node.children ?? []);
3567
3569
  if (children.length) {
3568
- const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === page?.navigationId : contains(entry.pages));
3569
- return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [page.navigationId] : [], ...children] }];
3570
+ const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
3571
+ return [{ group: node.label || "Documentation", pages: [...page && !contains(children) ? [projection.navigationId(page)] : [], ...children] }];
3570
3572
  }
3571
- return page ? [page.navigationId] : [];
3573
+ return page ? [projection.navigationId(page)] : [];
3572
3574
  });
3573
3575
  const tabs = navigation.filter((node) => node.kind === "tab");
3574
3576
  if (tabs.length) {
3575
- const projection = localeProjection({ records: [...records.values()] });
3576
3577
  const locales = [...new Set(tabs.map((tab) => tab.locale ?? projection.defaultLocale))];
3577
3578
  const localizedTabs = (locale) => tabs.filter((tab) => (tab.locale ?? projection.defaultLocale) === locale).flatMap((tab) => {
3578
3579
  const pages2 = project(tab.children ?? []);
@@ -3590,16 +3591,16 @@ function navigationConfig(navigation, records) {
3590
3591
  const pages = project(navigation);
3591
3592
  return pages.length ? { tabs: [{ tab: "Documentation", pages }] } : null;
3592
3593
  }
3594
+ function canonicalLanguage(value) {
3595
+ if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
3596
+ try {
3597
+ return Intl.getCanonicalLocales(value)[0];
3598
+ } catch {
3599
+ return void 0;
3600
+ }
3601
+ }
3593
3602
  function observedLocale(document, id, scopePath) {
3594
3603
  const issues = [];
3595
- const canonicalLanguage = (value) => {
3596
- if (!/^[a-z]{2,3}(?:-[a-z0-9]{2,8}){0,4}$/i.test(value)) return void 0;
3597
- try {
3598
- return Intl.getCanonicalLocales(value)[0];
3599
- } catch {
3600
- return void 0;
3601
- }
3602
- };
3603
3604
  const candidates = [];
3604
3605
  if (document.locale) {
3605
3606
  const locale = canonicalLanguage(document.locale);
@@ -3621,25 +3622,60 @@ function observedLocale(document, id, scopePath) {
3621
3622
  }
3622
3623
  function localeProjection(checkpoint) {
3623
3624
  const pages = checkpoint.records.flatMap((record) => record.page ? [record.page] : []);
3624
- const canonicalLocale = (page) => {
3625
+ const declaredLocale = (page) => {
3625
3626
  try {
3626
3627
  return page.locale ? Intl.getCanonicalLocales(page.locale)[0] : void 0;
3627
3628
  } catch {
3628
3629
  return void 0;
3629
3630
  }
3630
3631
  };
3632
+ const declaredLocales = [...new Set(pages.map(declaredLocale).filter((locale) => Boolean(locale)))];
3633
+ const defaultLocale = pages.some((page) => !page.locale) || declaredLocales.includes("en") ? "en" : declaredLocales[0] ?? "en";
3634
+ const records = new Map(checkpoint.records.map((record) => [record.url, record]));
3635
+ const memberships = /* @__PURE__ */ new Map();
3636
+ const observe = (nodes, parentLocale) => {
3637
+ for (const node of nodes) {
3638
+ const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
3639
+ const entry = node.url ? records.get(canonical(node.url, checkpoint.sourceUrl) ?? "") : void 0;
3640
+ const page = resolveMigrationRecord(records, entry)?.page;
3641
+ if (page && locale) {
3642
+ const languages = memberships.get(page) ?? /* @__PURE__ */ new Set();
3643
+ languages.add(locale);
3644
+ memberships.set(page, languages);
3645
+ }
3646
+ observe(node.children ?? [], locale);
3647
+ }
3648
+ };
3649
+ observe(checkpoint.navigation);
3650
+ const primaryIds = new Set(pages.filter((page) => !declaredLocale(page) || declaredLocale(page) === defaultLocale).filter((page) => memberships.get(page)?.has(defaultLocale)).map((page) => page.navigationId));
3651
+ const remapped = /* @__PURE__ */ new Map();
3652
+ for (const page of pages) {
3653
+ const languages = memberships.get(page);
3654
+ if (languages?.size !== 1) continue;
3655
+ const locale = [...languages][0];
3656
+ const prefix = `${locale.toLowerCase()}-`;
3657
+ if (locale === defaultLocale || !page.navigationId.toLowerCase().startsWith(prefix)) continue;
3658
+ const navigationId2 = page.navigationId.slice(prefix.length);
3659
+ if (primaryIds.has(navigationId2)) remapped.set(page, { locale, navigationId: navigationId2 });
3660
+ }
3661
+ const canonicalLocale = (page) => remapped.get(page)?.locale ?? declaredLocale(page);
3662
+ const navigationId = (page) => remapped.get(page)?.navigationId ?? page.navigationId;
3631
3663
  const locales = [...new Set(pages.map(canonicalLocale).filter((locale) => Boolean(locale)))];
3632
- const defaultLocale = pages.some((page) => !page.locale) || locales.includes("en") ? "en" : locales[0] ?? "en";
3633
3664
  const pageId = (page) => {
3634
3665
  const locale = canonicalLocale(page);
3635
- return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${page.navigationId}` : page.id;
3666
+ return locale ? `${locale === defaultLocale ? "" : `${locale}/`}${navigationId(page)}` : page.id;
3636
3667
  };
3637
3668
  const route = (page) => {
3638
3669
  const locale = canonicalLocale(page);
3639
3670
  const prefix = locale && locale !== defaultLocale ? `/${locale}` : "";
3640
- return `${prefix}${page.navigationId === "introduction" ? "/" : `/${page.navigationId}`}`;
3671
+ return `${prefix}${navigationId(page) === "introduction" ? "/" : `/${navigationId(page)}`}`;
3641
3672
  };
3642
- return { canonicalLocale, locales, defaultLocale, pageId, route };
3673
+ const redirects = checkpoint.records.flatMap((record) => {
3674
+ if (!record.page || !remapped.has(record.page)) return [];
3675
+ const source = new URL(record.url);
3676
+ return source.search || !/^\/[A-Za-z0-9/_~.%+-]*$/.test(source.pathname) ? [] : [{ source: source.pathname, destination: route(record.page), permanent: true }];
3677
+ });
3678
+ return { canonicalLocale, navigationId, locales, defaultLocale, pageId, route, redirects };
3643
3679
  }
3644
3680
  function validateUrlMigration(checkpoint, cancelled = false) {
3645
3681
  const issues = [];
@@ -3648,7 +3684,7 @@ function validateUrlMigration(checkpoint, cancelled = false) {
3648
3684
  const records = new Map(checkpoint.records.map((record) => [record.url, record]));
3649
3685
  const source = new URL(checkpoint.sourceUrl);
3650
3686
  const projection = localeProjection(checkpoint);
3651
- const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!record.page.locale || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [record.page.navigationId] : []));
3687
+ const primaryIds = new Set(checkpoint.records.flatMap((record) => record.page && (!projection.canonicalLocale(record.page) || projection.canonicalLocale(record.page) === projection.defaultLocale) ? [projection.navigationId(record.page)] : []));
3652
3688
  const outputIds = /* @__PURE__ */ new Set();
3653
3689
  const resolve3 = (url) => {
3654
3690
  const record = records.get(canonical(url, checkpoint.sourceUrl) ?? "");
@@ -3662,8 +3698,8 @@ function validateUrlMigration(checkpoint, cancelled = false) {
3662
3698
  const outputId = projection.pageId(record.page);
3663
3699
  if (outputIds.has(outputId)) issues.push({ code: "collision", source: record.url, message: `Multiple source pages map to runtime path ${outputId}.` });
3664
3700
  outputIds.add(outputId);
3665
- if (record.page.locale && projection.canonicalLocale(record.page) !== projection.defaultLocale && !primaryIds.has(record.page.navigationId)) {
3666
- 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.` });
3701
+ if (projection.canonicalLocale(record.page) && projection.canonicalLocale(record.page) !== projection.defaultLocale && !primaryIds.has(projection.navigationId(record.page))) {
3702
+ 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.` });
3667
3703
  }
3668
3704
  const actual = markdownCode(record.page.body);
3669
3705
  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." });
@@ -3697,13 +3733,20 @@ function validateUrlMigration(checkpoint, cancelled = false) {
3697
3733
  }
3698
3734
  }
3699
3735
  }
3700
- const visit = (nodes) => {
3736
+ const visit = (nodes, parentLocale) => {
3701
3737
  for (const node of nodes) {
3738
+ const locale = node.locale ? canonicalLanguage(node.locale) : parentLocale;
3702
3739
  if (node.url) {
3703
3740
  const url = new URL(node.url, source);
3704
- if (inScope(url, source, checkpoint.scopePath) && !resolve3(url.toString())?.page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
3741
+ if (inScope(url, source, checkpoint.scopePath)) {
3742
+ const page = resolve3(url.toString())?.page;
3743
+ if (!page) issues.push({ code: "navigation-missing", source: node.url, message: `Source navigation target ${node.label} was not imported.` });
3744
+ else if (locale && locale !== projection.defaultLocale && !outputIds.has(`${locale}/${projection.navigationId(page)}`)) {
3745
+ issues.push({ code: "navigation-missing", source: node.url, message: `The ${locale} sidebar target ${node.label} has no authored localized runtime page.` });
3746
+ }
3747
+ }
3705
3748
  }
3706
- visit(node.children ?? []);
3749
+ visit(node.children ?? [], locale);
3707
3750
  }
3708
3751
  };
3709
3752
  visit(checkpoint.navigation);
@@ -4121,7 +4164,7 @@ ${difference}`;
4121
4164
  }
4122
4165
  };
4123
4166
  labelPages(checkpoint.navigation);
4124
- 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) => {
4167
+ 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) => {
4125
4168
  let url;
4126
4169
  try {
4127
4170
  url = new URL(value, record.evidence.finalUrl);
@@ -4151,11 +4194,12 @@ ${difference}`;
4151
4194
  platform: options.platform ?? "generic",
4152
4195
  pages,
4153
4196
  assets: [...assets.values()],
4154
- docsConfig: navigationConfig(checkpoint.navigation, records) ?? buildNavigationFromPages(pages),
4197
+ docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
4155
4198
  warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
4156
4199
  urlMigrationValidation: validation,
4157
4200
  stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
4158
4201
  };
4202
+ if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
4159
4203
  if (projection.locales.length) {
4160
4204
  const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
4161
4205
  bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };