@thallylabs/migrate 0.2.41 → 0.2.42

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.
@@ -1,5 +1,5 @@
1
- import { h as MigrationCapture } from './url-run-mSKFqHx_.js';
2
- export { a as MigrationAsset, f as MigrationBundle, 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-run-mSKFqHx_.js';
1
+ import { h as MigrationCapture } from './url-run-CPzQfjRW.js';
2
+ export { a as MigrationAsset, f as MigrationBundle, 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-run-CPzQfjRW.js';
3
3
 
4
4
  interface UrlMigrationScope {
5
5
  scopePath: string;
@@ -1,6 +1,7 @@
1
1
  // src/url-run.ts
2
2
  import { createHash as createHash4 } from "crypto";
3
3
  import { load as load7 } from "cheerio";
4
+ import { parse as parse2, postprocess, preprocess } from "micromark";
4
5
  import remarkParse4 from "remark-parse";
5
6
  import remarkMdx4 from "remark-mdx";
6
7
  import { unified as unified4 } from "unified";
@@ -1815,7 +1816,10 @@ var VIDEO_ATTRIBUTES = /* @__PURE__ */ new Set([
1815
1816
  "width",
1816
1817
  "height",
1817
1818
  "preload",
1818
- "aria-label"
1819
+ "aria-label",
1820
+ // Publisher asset provenance does not change playback and is never emitted.
1821
+ // Keep this explicit: arbitrary data attributes may drive custom controls.
1822
+ "data-path"
1819
1823
  ]);
1820
1824
  var YOUTUBE_ATTRIBUTES = /* @__PURE__ */ new Set([
1821
1825
  "src",
@@ -5216,9 +5220,9 @@ function apiOperationCodeControls(body, specification, operation) {
5216
5220
  }
5217
5221
 
5218
5222
  // src/url-run.ts
5219
- var CONVERTER_VERSION = "url-evidence-v6";
5223
+ var CONVERTER_VERSION = "url-evidence-v7";
5220
5224
  var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
5221
- var ACCEPT = "text/markdown,text/html,application/xhtml+xml;q=0.9";
5225
+ var ACCEPT = "text/html,application/xhtml+xml";
5222
5226
  var MAX_RESPONSE_BYTES = 8e6;
5223
5227
  var MAX_ASSET_BYTES = 1e7;
5224
5228
  var MAX_RECORDS = 1e5;
@@ -5248,6 +5252,11 @@ function canonical(value, base) {
5248
5252
  return null;
5249
5253
  }
5250
5254
  }
5255
+ function sameRouteMarkdownRepresentation(alternate, primary) {
5256
+ const target2 = new URL(alternate), source = new URL(primary);
5257
+ const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
5258
+ return target2.origin === source.origin && target2.search === source.search && directory2(target2.pathname) === directory2(source.pathname) && /\.mdx?$/i.test(target2.pathname);
5259
+ }
5251
5260
  function classifyMigrationResponse(document) {
5252
5261
  if (document.status && document.status >= 400) return "unsupported";
5253
5262
  if (/\/(?:llms(?:-full)?|robots)\.txt$|\/sitemap[^/]*\.xml$|\/search(?:\.html)?$/i.test(document.finalUrl.pathname) || /\.(?:json|ya?ml)$/i.test(document.finalUrl.pathname) || /(?:xml|json)/i.test(document.contentType)) return "index";
@@ -5498,7 +5507,7 @@ function resolveMigrationRecord(records, record) {
5498
5507
  }
5499
5508
  return record;
5500
5509
  }
5501
- function navigationConfig(checkpoint, records) {
5510
+ function navigationConfig(checkpoint, records, occurrencePages = /* @__PURE__ */ new Map()) {
5502
5511
  const navigation = checkpoint.navigation;
5503
5512
  const shortcuts = [];
5504
5513
  const collectShortcuts = (nodes) => {
@@ -5523,7 +5532,7 @@ function navigationConfig(checkpoint, records) {
5523
5532
  const project = (nodes) => nodes.flatMap((node) => {
5524
5533
  const record = node.url ? records.get(canonical(node.url, node.url) ?? "") : void 0;
5525
5534
  const target2 = resolveMigrationRecord(records, record);
5526
- const page = target2?.page;
5535
+ const page = occurrencePages.get(node) ?? target2?.page;
5527
5536
  const children = project(node.children ?? []);
5528
5537
  if (children.length) {
5529
5538
  const contains = (entries) => entries.some((entry) => typeof entry === "string" ? entry === (page && projection.navigationId(page)) : contains(entry.pages));
@@ -5823,8 +5832,19 @@ async function runUrlMigration(options) {
5823
5832
  if (!record.evidence) continue;
5824
5833
  const final = canonical(record.evidence.finalUrl, sourceUrl);
5825
5834
  if (!final) continue;
5826
- if (record.outcome === "imported") enqueueArticleLinks(record.evidence.links, final, record.url);
5827
- else if (record.outcome === "excluded") {
5835
+ if (record.outcome === "imported") {
5836
+ enqueueArticleLinks(record.evidence.links, final, record.url);
5837
+ for (const reference of record.evidence.markdownRepresentations ?? []) {
5838
+ const alternate = canonical(reference, final);
5839
+ if (!alternate || !sameRouteMarkdownRepresentation(alternate, final)) continue;
5840
+ enqueue(alternate, final, "declared-markdown-representation");
5841
+ const sibling = records.get(alternate);
5842
+ if (sibling && sibling.representationOf !== record.url) {
5843
+ sibling.representationOf = record.url;
5844
+ changed.push(sibling);
5845
+ }
5846
+ }
5847
+ } else if (record.outcome === "excluded") {
5828
5848
  if (classifyMigrationResponse({ finalUrl: new URL(final), contentType: record.evidence.contentType, body: "" }) === "index") {
5829
5849
  for (const link of record.evidence.links) enqueue(link, final, record.url);
5830
5850
  }
@@ -5907,6 +5927,7 @@ async function runUrlMigration(options) {
5907
5927
  let claimedId;
5908
5928
  let claimedFinal;
5909
5929
  let markdownRepresentation;
5930
+ const markdownRepresentations = [];
5910
5931
  let exactKey;
5911
5932
  try {
5912
5933
  delete record.reason;
@@ -5999,13 +6020,13 @@ async function runUrlMigration(options) {
5999
6020
  $('link[rel="alternate"][type="text/markdown"][href]').each((_index, node) => {
6000
6021
  const alternate = canonical($(node).attr("href"), final);
6001
6022
  if (!alternate) return;
6002
- const target2 = new URL(alternate), primary = new URL(final);
6003
- const directory2 = (path) => path.replace(/\.mdx?$/i, "").replace(/\/index\/?$/i, "").replace(/\/$/, "");
6004
- if (target2.origin !== primary.origin || target2.search !== primary.search || directory2(target2.pathname) !== directory2(primary.pathname) || !/\.mdx?$/i.test(target2.pathname)) return;
6023
+ if (!sameRouteMarkdownRepresentation(alternate, final)) return;
6005
6024
  enqueue(alternate, final, "declared-markdown-representation");
6006
6025
  const sibling = records.get(alternate);
6026
+ if (!sibling) return;
6007
6027
  sibling.representationOf = record.url;
6008
6028
  markdownRepresentation ??= alternate;
6029
+ markdownRepresentations.push(alternate);
6009
6030
  changed.push(sibling);
6010
6031
  });
6011
6032
  }
@@ -6018,7 +6039,8 @@ async function runUrlMigration(options) {
6018
6039
  }
6019
6040
  enqueueNavigation(observedNavigation, final);
6020
6041
  }
6021
- if (imported >= maxPages) {
6042
+ const replacesRepresentation = imported >= maxPages && markdownRepresentations.some((url) => records.get(url)?.page);
6043
+ if (imported >= maxPages && !replacesRepresentation) {
6022
6044
  record.outcome = "excluded";
6023
6045
  record.reason = "A discovery endpoint resolved to an article after the selected page limit.";
6024
6046
  return;
@@ -6037,14 +6059,17 @@ async function runUrlMigration(options) {
6037
6059
  const locale = language.locale;
6038
6060
  const navigationId = locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id;
6039
6061
  const identityId = locale ? `${locale}/${navigationId}` : id;
6040
- if (usedIds.has(identityId) && usedIds.get(identityId) !== record.url) {
6062
+ const owner = records.get(usedIds.get(identityId) ?? "");
6063
+ const sharesDeclaredRepresentation = owner && (owner.representationOf === record.url || record.representationOf === owner.url);
6064
+ if (owner && owner !== record && !sharesDeclaredRepresentation) {
6041
6065
  record.outcome = "failed";
6042
6066
  record.reason = `Destination collision for ${identityId}.`;
6043
6067
  return;
6044
6068
  }
6045
- usedIds.set(identityId, record.url);
6069
+ if (!owner || owner === record || owner.representationOf === record.url) usedIds.set(identityId, record.url);
6046
6070
  claimedId = identityId;
6047
6071
  const evidence = evidenceOf(document);
6072
+ if (markdownRepresentations.length) evidence.markdownRepresentations = markdownRepresentations;
6048
6073
  const page = convert(document, id);
6049
6074
  if (!page) {
6050
6075
  record.outcome = "failed";
@@ -6255,6 +6280,7 @@ ${difference}`;
6255
6280
  if (!primary || primary === record) continue;
6256
6281
  const winner = primary.outcome === "imported" ? primary : record.outcome === "imported" && primary.outcome === "failed" ? record : void 0;
6257
6282
  if (!winner) continue;
6283
+ if (winner.page) usedIds.set(winner.page.id, winner.url);
6258
6284
  const alternate = winner === primary ? record : primary;
6259
6285
  if (alternate.outcome === "alias" && alternate.aliasOf === winner.url) continue;
6260
6286
  if (alternate.outcome === "imported") {
@@ -6276,7 +6302,17 @@ ${difference}`;
6276
6302
  settleArticleIdentities();
6277
6303
  while (cursor < pending.length && !options.signal?.aborted && !budgetExhausted && cursor < (options.maxCaptures ?? Infinity)) {
6278
6304
  if (imported >= maxPages) {
6279
- const index = pending.findIndex((record, position) => position >= cursor && isDiscoveryIndex(new URL(record.url), source));
6305
+ const index = pending.findIndex((record, position) => {
6306
+ if (position < cursor || record.outcome !== "pending") return false;
6307
+ const url = new URL(record.url);
6308
+ if (isDiscoveryIndex(url, source)) return true;
6309
+ if (/\.mdx?$/i.test(url.pathname)) return false;
6310
+ return [".md", ".mdx", "/index.md", "/index.mdx"].some((extension) => {
6311
+ const alternate = new URL(url);
6312
+ alternate.pathname = alternate.pathname.replace(/\/index$/i, "") + extension;
6313
+ return records.get(alternate.href)?.outcome === "imported";
6314
+ });
6315
+ });
6280
6316
  if (index < 0) break;
6281
6317
  pending.splice(cursor, 0, pending.splice(index, 1)[0]);
6282
6318
  }
@@ -6300,19 +6336,60 @@ ${difference}`;
6300
6336
  }
6301
6337
  const navigationLabels = /* @__PURE__ */ new Map();
6302
6338
  const navigationIssues = [];
6339
+ const navigationOccurrences = /* @__PURE__ */ new Map();
6303
6340
  const labelPages = (nodes) => {
6304
6341
  for (const node of nodes) {
6305
6342
  const entry = node.url ? records.get(canonical(node.url, sourceUrl) ?? "") : void 0;
6306
6343
  const target2 = resolveMigrationRecord(records, entry);
6307
- if (target2?.page && node.label && !node.children?.length && !entry?.aliasAnchor) {
6308
- const previous = navigationLabels.get(target2.url);
6309
- if (previous && previous !== node.label) navigationIssues.push({ code: "unsupported-component", source: target2.url, message: `This page has multiple source navigation labels (${previous}, ${node.label}); review the shared sidebar title.` });
6310
- else navigationLabels.set(target2.url, node.label);
6344
+ if (entry && target2?.page && node.label && !node.children?.length && !entry.aliasAnchor) {
6345
+ const occurrences = navigationOccurrences.get(target2) ?? [];
6346
+ occurrences.push({ node, entry });
6347
+ navigationOccurrences.set(target2, occurrences);
6311
6348
  }
6312
6349
  labelPages(node.children ?? []);
6313
6350
  }
6314
6351
  };
6315
6352
  labelPages(checkpoint.navigation);
6353
+ const occurrencePages = /* @__PURE__ */ new Map();
6354
+ const navigationAliases = /* @__PURE__ */ new Map();
6355
+ const occupiedPageIds = new Set(checkpoint.records.flatMap((record) => record.page ? [projection.pageId(record.page)] : []));
6356
+ const navigationRedirects = [];
6357
+ for (const [target2, occurrences] of navigationOccurrences) {
6358
+ const final = target2.evidence && canonical(target2.evidence.finalUrl, sourceUrl);
6359
+ const primary = occurrences.find(({ entry }) => entry.url === final) ?? occurrences.find(({ entry }) => entry === target2) ?? occurrences[0];
6360
+ navigationLabels.set(target2.url, primary.node.label);
6361
+ for (const { node, entry } of occurrences) {
6362
+ if (node.label === primary.node.label) continue;
6363
+ const existing = navigationAliases.get(entry.url);
6364
+ if (existing?.page.navTitle === node.label && existing.target === target2) {
6365
+ occurrencePages.set(node, existing.page);
6366
+ continue;
6367
+ }
6368
+ const sourceRoute = new URL(entry.url);
6369
+ const id = inScope(sourceRoute, source, scopePath) ? idForUrl(sourceRoute, scopePath) : null;
6370
+ const locale = projection.canonicalLocale(target2.page);
6371
+ let navigationId = id && (locale ? id.split("/").filter((segment) => segment.toLowerCase() !== locale.toLowerCase()).join("/") || "introduction" : id);
6372
+ const legacyPrefix = locale && `${locale.toLowerCase()}-`;
6373
+ if (navigationId && legacyPrefix && projection.navigationId(target2.page) !== target2.page.navigationId && navigationId.toLowerCase().startsWith(legacyPrefix)) navigationId = navigationId.slice(legacyPrefix.length);
6374
+ const page = navigationId ? {
6375
+ ...target2.page,
6376
+ id: locale ? `${locale}/${navigationId}` : navigationId,
6377
+ navigationId,
6378
+ ...locale ? { locale } : {},
6379
+ navTitle: node.label,
6380
+ source: entry.url,
6381
+ noindex: true
6382
+ } : void 0;
6383
+ if (!page || existing || occupiedPageIds.has(projection.pageId(page))) {
6384
+ navigationIssues.push({ code: "unsupported-component", source: target2.url, message: `This page has multiple source navigation labels (${primary.node.label}, ${node.label}); their source routes cannot retain distinct sidebar titles safely.` });
6385
+ continue;
6386
+ }
6387
+ occupiedPageIds.add(projection.pageId(page));
6388
+ navigationAliases.set(entry.url, { page, target: target2 });
6389
+ occurrencePages.set(node, page);
6390
+ navigationRedirects.push({ source: projection.route(page), destination: projection.route(target2.page), permanent: true });
6391
+ }
6392
+ }
6316
6393
  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) => {
6317
6394
  let url;
6318
6395
  try {
@@ -6333,6 +6410,10 @@ ${difference}`;
6333
6410
  const hasBrokenSourceAnchor = Boolean(url.hash && destination?.outcome === "imported" && destination.evidence && !destination.evidence.anchors.includes(anchor));
6334
6411
  return `${route}${hasBrokenSourceAnchor ? "" : effectiveHash}`;
6335
6412
  }) }] : []);
6413
+ const projectedPages = new Map(pages.map((page) => [page.id, page]));
6414
+ for (const { page, target: target2 } of navigationAliases.values()) {
6415
+ pages.push({ ...page, id: projection.pageId(page), body: projectedPages.get(projection.pageId(target2.page)).body });
6416
+ }
6336
6417
  const validation = validateUrlMigration(checkpoint, Boolean(options.signal?.aborted));
6337
6418
  validation.issues.push(...navigationIssues);
6338
6419
  if (navigationIssues.length && validation.status === "passed") validation.status = "partial";
@@ -6353,12 +6434,12 @@ ${difference}`;
6353
6434
  platform: options.platform ?? "generic",
6354
6435
  pages,
6355
6436
  assets: [...assets.values()],
6356
- docsConfig: navigationConfig(checkpoint, records) ?? buildNavigationFromPages(pages),
6437
+ docsConfig: navigationConfig(checkpoint, records, occurrencePages) ?? buildNavigationFromPages(pages),
6357
6438
  warnings: [...validation.issues, ...validation.warnings ?? []].map((issue) => ({ code: issue.code === "missing-page" ? "missing-page" : "unsupported-config", message: issue.message, source: issue.source })),
6358
6439
  urlMigrationValidation: validation,
6359
6440
  stats: { discovered: records.size, imported: pages.length, skipped: records.size - pages.length }
6360
6441
  };
6361
- if (projection.redirects.length) bundle.docsConfig.redirects = projection.redirects;
6442
+ if (projection.redirects.length || navigationRedirects.length) bundle.docsConfig.redirects = [...projection.redirects, ...navigationRedirects];
6362
6443
  if (projection.locales.length) {
6363
6444
  const locales = [.../* @__PURE__ */ new Set([projection.defaultLocale, ...projection.locales])];
6364
6445
  bundle.docsConfig.i18n = { ...bundle.docsConfig.i18n, defaultLocale: projection.defaultLocale, locales: locales.map((code) => ({ code, label: code === "en" ? "English" : code })) };
@@ -6376,9 +6457,11 @@ function rewriteMarkdownDestinations(body, rewrite) {
6376
6457
  const original = body.slice(start, end);
6377
6458
  const replacement = rewrite(node.url);
6378
6459
  if (replacement === node.url) return null;
6379
- const index = original.lastIndexOf(node.url);
6380
- if (index < 0) return null;
6381
- return { start: start + index, end: start + index + node.url.length, replacement };
6460
+ const destinations = postprocess(parse2().document().write(preprocess()(original, void 0, true))).filter(([event, token]) => event === "enter" && ["resourceDestinationString", "definitionDestinationString", "autolinkProtocol", "autolinkEmail"].includes(token.type));
6461
+ const destination = destinations.at(-1)?.[1];
6462
+ if (!destination) return null;
6463
+ const escaped = replacement.replace(/[\\&()<>]/g, "\\$&").replace(/[\t\n\r ]/g, (character) => encodeURIComponent(character));
6464
+ return { start: start + destination.start.offset, end: start + destination.end.offset, replacement: escaped };
6382
6465
  }).filter((edit) => edit !== null).sort((a, b) => b.start - a.start);
6383
6466
  for (const node of markdownNodes(body)) for (const attribute of node.attributes ?? []) {
6384
6467
  if (!attribute.name || !["src", "poster", "href", "srcSet", "srcset", "img", "primaryHref", "secondaryHref"].includes(attribute.name) || !attribute.position) continue;