@thallylabs/migrate 0.2.28 → 0.2.30
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 +123 -22
- package/dist/index.js.map +1 -1
- package/dist/url-migration.js +123 -22
- package/dist/url-migration.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13876,6 +13876,73 @@ function isPassiveSvg(content) {
|
|
|
13876
13876
|
return isPassive && sawRoot && depth === 0;
|
|
13877
13877
|
}
|
|
13878
13878
|
|
|
13879
|
+
// src/url-asset-cache.ts
|
|
13880
|
+
function createMigrationAssetCache() {
|
|
13881
|
+
const settled = /* @__PURE__ */ new Map();
|
|
13882
|
+
const pending = /* @__PURE__ */ new Map();
|
|
13883
|
+
const remember = (url, asset) => {
|
|
13884
|
+
settled.delete(url);
|
|
13885
|
+
settled.set(url, asset);
|
|
13886
|
+
if (settled.size > 480) settled.delete(settled.keys().next().value);
|
|
13887
|
+
return asset;
|
|
13888
|
+
};
|
|
13889
|
+
const get = (url) => {
|
|
13890
|
+
const asset = settled.get(url);
|
|
13891
|
+
if (asset) return Promise.resolve(remember(url, asset));
|
|
13892
|
+
return pending.get(url);
|
|
13893
|
+
};
|
|
13894
|
+
return {
|
|
13895
|
+
remember,
|
|
13896
|
+
get,
|
|
13897
|
+
has(url) {
|
|
13898
|
+
return settled.has(url) || pending.has(url);
|
|
13899
|
+
},
|
|
13900
|
+
get size() {
|
|
13901
|
+
return settled.size + pending.size;
|
|
13902
|
+
},
|
|
13903
|
+
load(url, acquire) {
|
|
13904
|
+
const existing = get(url);
|
|
13905
|
+
if (existing) return existing;
|
|
13906
|
+
const request2 = Promise.resolve().then(acquire).then((asset) => {
|
|
13907
|
+
pending.delete(url);
|
|
13908
|
+
return remember(url, asset);
|
|
13909
|
+
}, (error) => {
|
|
13910
|
+
pending.delete(url);
|
|
13911
|
+
throw error;
|
|
13912
|
+
});
|
|
13913
|
+
pending.set(url, request2);
|
|
13914
|
+
return request2;
|
|
13915
|
+
},
|
|
13916
|
+
delete(url) {
|
|
13917
|
+
settled.delete(url);
|
|
13918
|
+
}
|
|
13919
|
+
};
|
|
13920
|
+
}
|
|
13921
|
+
|
|
13922
|
+
// src/url-journal-clone.ts
|
|
13923
|
+
function cloneUrlMigrationCheckpoint(checkpoint) {
|
|
13924
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
13925
|
+
const clone = (value) => {
|
|
13926
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
13927
|
+
if (typeof value === "number") return Number.isFinite(value) ? value === 0 ? 0 : value : null;
|
|
13928
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
13929
|
+
if (typeof value !== "object") throw new TypeError("Checkpoint must contain JSON-safe values.");
|
|
13930
|
+
if (ancestors.has(value)) throw new TypeError("Checkpoint must not contain circular references.");
|
|
13931
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("Checkpoint must contain plain JSON containers.");
|
|
13932
|
+
ancestors.add(value);
|
|
13933
|
+
try {
|
|
13934
|
+
if (Array.isArray(value)) return Array.from(value, (entry) => clone(entry) ?? null);
|
|
13935
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => {
|
|
13936
|
+
const copied = clone(entry);
|
|
13937
|
+
return copied === void 0 ? [] : [[key, copied]];
|
|
13938
|
+
}));
|
|
13939
|
+
} finally {
|
|
13940
|
+
ancestors.delete(value);
|
|
13941
|
+
}
|
|
13942
|
+
};
|
|
13943
|
+
return clone(checkpoint);
|
|
13944
|
+
}
|
|
13945
|
+
|
|
13879
13946
|
// src/url-run.ts
|
|
13880
13947
|
var CONVERTER_VERSION = "url-evidence-v2";
|
|
13881
13948
|
var parser2 = unified4().use(remarkParse4).use(remarkMdx4);
|
|
@@ -14250,7 +14317,8 @@ async function runUrlMigration(options) {
|
|
|
14250
14317
|
const batchSize = Math.max(1, Math.min(500, Math.floor(options.batchSize ?? 50)));
|
|
14251
14318
|
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.");
|
|
14252
14319
|
const captureVersion = options.captureVersion ?? "fetch-v1";
|
|
14253
|
-
|
|
14320
|
+
if (options.checkpoint && options.checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
14321
|
+
const checkpoint = options.checkpoint ? cloneUrlMigrationCheckpoint(options.checkpoint) : { schemaVersion: 1, converterVersion: CONVERTER_VERSION, sourceUrl, scopePath, captureVersion, records: [], navigation: [] };
|
|
14254
14322
|
if (checkpoint.schemaVersion !== 1 || checkpoint.converterVersion !== CONVERTER_VERSION || checkpoint.sourceUrl !== sourceUrl || checkpoint.scopePath !== scopePath || checkpoint.captureVersion !== captureVersion) throw new Error("Checkpoint does not match this source, scope or capture/converter version.");
|
|
14255
14323
|
if (checkpoint.records.length > MAX_RECORDS) throw new Error("Checkpoint inventory exceeds its bound.");
|
|
14256
14324
|
const records = /* @__PURE__ */ new Map();
|
|
@@ -14291,6 +14359,33 @@ async function runUrlMigration(options) {
|
|
|
14291
14359
|
enqueue(sourceUrl, sourceUrl, "submitted");
|
|
14292
14360
|
for (const path of ["llms.txt", "sitemap.xml", "robots.txt"]) enqueue(`${scopePath === "/" ? "" : scopePath}/${path}`, sourceUrl, "discovery-index");
|
|
14293
14361
|
if (scopePath !== "/") for (const path of ["llms.txt", "sitemap.xml", "robots.txt"]) enqueue(`/${path}`, sourceUrl, "discovery-index");
|
|
14362
|
+
const enqueueArticleLinks = (links, base, discoveredBy) => {
|
|
14363
|
+
for (const link of links) {
|
|
14364
|
+
try {
|
|
14365
|
+
const url = new URL(link, base);
|
|
14366
|
+
if (!/\.(?:png|jpe?g|gif|svg|webp|avif|pdf|zip|mp4|webm|css|js)$/i.test(url.pathname)) enqueue(link, base, discoveredBy);
|
|
14367
|
+
} catch {
|
|
14368
|
+
}
|
|
14369
|
+
}
|
|
14370
|
+
};
|
|
14371
|
+
const enqueueNavigation = (nodes, base) => {
|
|
14372
|
+
for (const node of nodes) {
|
|
14373
|
+
if (node.url) enqueue(node.url, base, "source-navigation");
|
|
14374
|
+
enqueueNavigation(node.children ?? [], base);
|
|
14375
|
+
}
|
|
14376
|
+
};
|
|
14377
|
+
for (const record of [...records.values()]) {
|
|
14378
|
+
if (!record.evidence) continue;
|
|
14379
|
+
const final = canonical(record.evidence.finalUrl, sourceUrl);
|
|
14380
|
+
if (!final) continue;
|
|
14381
|
+
if (record.outcome === "imported") enqueueArticleLinks(record.evidence.links, final, record.url);
|
|
14382
|
+
else if (record.outcome === "excluded") {
|
|
14383
|
+
if (classifyMigrationResponse({ finalUrl: new URL(final), contentType: record.evidence.contentType, body: "" }) === "index") {
|
|
14384
|
+
for (const link of record.evidence.links) enqueue(link, final, record.url);
|
|
14385
|
+
}
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
enqueueNavigation(checkpoint.navigation, sourceUrl);
|
|
14294
14389
|
const fetcher = options.fetcher ?? defaultMigrationFetcher;
|
|
14295
14390
|
const capture = options.captureProvider?.capture.bind(options.captureProvider) ?? (async ({ url }) => fetcher(url, { accept: ACCEPT }));
|
|
14296
14391
|
const assetFetcher = options.assetFetcher ?? (!options.fetcher && !options.captureProvider ? defaultAssetFetcher : void 0);
|
|
@@ -14298,9 +14393,21 @@ async function runUrlMigration(options) {
|
|
|
14298
14393
|
const finalUrls = new Map([...records.values()].filter((record) => record.evidence && record.page).map((record) => [canonical(record.evidence.finalUrl, sourceUrl), record.url]));
|
|
14299
14394
|
let imported = usedIds.size;
|
|
14300
14395
|
const assetPayloads = new Map(checkpoint.records.flatMap((record) => (record.assets ?? []).map((asset) => [asset.sha256, asset])));
|
|
14301
|
-
const
|
|
14396
|
+
const assetBytes = /* @__PURE__ */ new Map();
|
|
14397
|
+
const retainAsset = (asset) => {
|
|
14398
|
+
if (!acceptedAsset(asset)) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
14399
|
+
const sha256 = hash2(asset.content);
|
|
14400
|
+
const content = assetBytes.get(sha256) ?? Buffer.from(asset.content);
|
|
14401
|
+
assetBytes.set(sha256, content);
|
|
14402
|
+
return { ...asset, content };
|
|
14403
|
+
};
|
|
14404
|
+
const assetRequests = createMigrationAssetCache();
|
|
14302
14405
|
for (const record of checkpoint.records) for (const asset of record.assets ?? []) {
|
|
14303
|
-
if (!assetRequests.has(asset.url))
|
|
14406
|
+
if (!assetRequests.has(asset.url)) {
|
|
14407
|
+
const content = assetBytes.get(asset.sha256) ?? Buffer.from(asset.base64, "base64");
|
|
14408
|
+
assetBytes.set(asset.sha256, content);
|
|
14409
|
+
assetRequests.remember(asset.url, { url: asset.url, contentType: asset.contentType, content });
|
|
14410
|
+
}
|
|
14304
14411
|
}
|
|
14305
14412
|
let totalBytes = checkpoint.records.reduce((sum, record) => sum + (record.evidence?.byteLength ?? 0), 0) + [...assetPayloads.values()].reduce((sum, asset) => sum + Buffer.byteLength(asset.base64, "base64"), 0);
|
|
14306
14413
|
let budgetExhausted = false;
|
|
@@ -14412,10 +14519,10 @@ ${difference}`;
|
|
|
14412
14519
|
try {
|
|
14413
14520
|
let assetRequest = assetRequests.get(url);
|
|
14414
14521
|
if (!suppliedAssets.has(url) && !assetRequest && assetFetcher) {
|
|
14415
|
-
assetRequest = assetFetcher(new URL(url), options.signal);
|
|
14416
|
-
assetRequests.set(url, assetRequest);
|
|
14522
|
+
assetRequest = assetRequests.load(url, () => assetFetcher(new URL(url), options.signal).then(retainAsset));
|
|
14417
14523
|
}
|
|
14418
|
-
const
|
|
14524
|
+
const supplied = suppliedAssets.get(url);
|
|
14525
|
+
const asset = supplied ? retainAsset(supplied) : assetRequest ? await assetRequest : null;
|
|
14419
14526
|
if (!asset) throw new Error("Capture host did not supply asset bytes or a binary fetch boundary.");
|
|
14420
14527
|
const extension = acceptedAsset(asset);
|
|
14421
14528
|
if (!extension) throw new Error("Asset MIME/signature is unsupported or unsafe to serve.");
|
|
@@ -14432,7 +14539,7 @@ ${difference}`;
|
|
|
14432
14539
|
const receipt = { url, path, sha256, contentType: asset.contentType, base64: existing?.base64 ?? Buffer.from(asset.content).toString("base64") };
|
|
14433
14540
|
record.assets.push(receipt);
|
|
14434
14541
|
if (!existing) assetPayloads.set(sha256, receipt);
|
|
14435
|
-
if (!assetRequests.has(url)) assetRequests.
|
|
14542
|
+
if (!assetRequests.has(url)) assetRequests.remember(url, asset);
|
|
14436
14543
|
page.body = rewriteMarkdownDestinations(page.body, (destination) => canonical(destination, final) === url ? `/${path}${new URL(destination, final).hash}` : destination);
|
|
14437
14544
|
} catch (error) {
|
|
14438
14545
|
assetRequests.delete(url);
|
|
@@ -14465,20 +14572,8 @@ ${difference}`;
|
|
|
14465
14572
|
checkpoint.navigation = navigation;
|
|
14466
14573
|
navigationChanged = true;
|
|
14467
14574
|
}
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
const url = new URL(link, final);
|
|
14471
|
-
if (!/\.(?:png|jpe?g|gif|svg|webp|avif|pdf|zip|mp4|webm|css|js)$/i.test(url.pathname)) enqueue(link, final, record.url);
|
|
14472
|
-
} catch {
|
|
14473
|
-
}
|
|
14474
|
-
}
|
|
14475
|
-
const visit = (nodes) => {
|
|
14476
|
-
for (const node of nodes) {
|
|
14477
|
-
if (node.url) enqueue(node.url, final, "source-navigation");
|
|
14478
|
-
visit(node.children ?? []);
|
|
14479
|
-
}
|
|
14480
|
-
};
|
|
14481
|
-
visit(navigation);
|
|
14575
|
+
enqueueArticleLinks(evidence.links, final, record.url);
|
|
14576
|
+
enqueueNavigation(navigation, final);
|
|
14482
14577
|
} catch (error) {
|
|
14483
14578
|
if (options.signal?.aborted) return;
|
|
14484
14579
|
const wasImported = record.outcome === "imported";
|
|
@@ -14548,7 +14643,13 @@ ${difference}`;
|
|
|
14548
14643
|
const validation = validateUrlMigration(checkpoint, Boolean(options.signal?.aborted));
|
|
14549
14644
|
validation.issues.push(...navigationIssues);
|
|
14550
14645
|
if (navigationIssues.length && validation.status === "passed") validation.status = "partial";
|
|
14551
|
-
const assets =
|
|
14646
|
+
const assets = /* @__PURE__ */ new Map();
|
|
14647
|
+
for (const record of checkpoint.records) for (const asset of record.assets ?? []) {
|
|
14648
|
+
if (!assets.has(asset.path)) {
|
|
14649
|
+
const content = assetBytes.get(asset.sha256) ?? Buffer.from(asset.base64, "base64");
|
|
14650
|
+
assets.set(asset.path, { path: asset.path, content: Buffer.from(content.buffer, content.byteOffset, content.byteLength) });
|
|
14651
|
+
}
|
|
14652
|
+
}
|
|
14552
14653
|
const bundle = {
|
|
14553
14654
|
sourceUrl,
|
|
14554
14655
|
sourceKind: "url",
|