@thallylabs/migrate 0.2.31 → 0.2.33

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.
@@ -424,12 +424,21 @@ interface MigrationWarning {
424
424
  source?: string;
425
425
  }
426
426
  /** A repository-configured remote OpenAPI spec, downloaded after the bundle is built. */
427
+ /** A route prefix Mintlify served one spec's generated endpoint pages under (`openapi: { source, directory }`). */
428
+ interface RemoteApiSpecDirectory {
429
+ /** The authored `directory`, without leading or trailing slashes. */
430
+ directory: string;
431
+ /** `METHOD /path` operations docs.json lists under that reference, in authored order; absent when Mintlify generates every operation. */
432
+ operations?: Array<string>;
433
+ }
427
434
  interface RemoteApiSpec {
428
435
  url: string;
429
436
  tabLabel?: string;
430
437
  parentTab?: string;
431
438
  icon?: string;
432
439
  hidden?: boolean;
440
+ /** Every `directory` docs.json mounts this spec under: several API tabs may share one spec URL. */
441
+ directories?: Array<RemoteApiSpecDirectory>;
433
442
  /** Operation keys (`get /x`) only access-restricted pages document; marked `x-excluded` after download. */
434
443
  withheldOperations?: Array<string>;
435
444
  /** Operation keys published pages or docs.json also name; keeps a shared `$ref` path item public. */
@@ -443,6 +452,16 @@ interface MigrationBundle {
443
452
  assets: Array<MigrationAsset>;
444
453
  /** Repository-configured remote specs awaiting a bounded network fetch by the host. */
445
454
  remoteApiSpecs?: Array<RemoteApiSpec>;
455
+ /**
456
+ * Repository OpenAPI specs docs.json mounts under a `directory` (no surrounding slashes), with the tab each is bound to; the remote ones are in
457
+ * `remoteApiSpecs[].directories`. Present only alongside remote specs: a spec's id depends on which remote tabs bind ahead of it, so
458
+ * hydration links the local and remote mounts together once every download has settled, and the links stay as authored until then.
459
+ */
460
+ localApiMounts?: Array<{
461
+ filename: string;
462
+ directory: string;
463
+ tab?: string;
464
+ }>;
446
465
  /** Customer-owned component source and registry; paths are repository-relative. */
447
466
  componentFiles?: Array<RenderedMigrationFile>;
448
467
  /**
@@ -1 +1 @@
1
- export { a as MigrationAsset, f as MigrationBundle, h as MigrationCapture, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, g as MigrationDocsConfig, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, d as MigrationFetcher, o as MigrationJournalAsset, b as MigrationNavigationGroup, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, M as MigrationPage, e as MigrationPlatform, c as MigrationWarning, R as RenderedMigrationFile, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-migration-Coy62q1L.js';
1
+ export { a as MigrationAsset, f as MigrationBundle, h as MigrationCapture, i as MigrationCaptureProvider, j as MigrationCaptureState, k as MigrationCapturedAsset, g as MigrationDocsConfig, l as MigrationEvidence, m as MigrationFetchRequest, n as MigrationFetchResponse, d as MigrationFetcher, o as MigrationJournalAsset, b as MigrationNavigationGroup, p as MigrationNavigationOccurrence, q as MigrationNavigationTab, M as MigrationPage, e as MigrationPlatform, c as MigrationWarning, R as RenderedMigrationFile, U as UrlMigrationCheckpoint, r as UrlMigrationCheckpointDelta, s as UrlMigrationIssue, t as UrlMigrationRecord, u as UrlMigrationRun, v as UrlMigrationRunOptions, w as UrlMigrationValidation, x as classifyMigrationResponse, z as renderMigrationFiles, A as runUrlMigration, B as validateUrlMigration } from './url-migration-_wVauqSg.js';
@@ -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 })) };