@@ -59,14 +59,37 @@ jobs:
5959 const lines = fs.readFileSync("/tmp/production.ndjson", "utf8").trim().split("\n").filter(Boolean);
6060 const allDocs = lines.map((l) => JSON.parse(l));
6161
62+ const activeTypes = new Set([
63+ "post",
64+ "podcast",
65+ "guest",
66+ "author",
67+ "sponsor",
68+ "page",
69+ "settings",
70+ "podcastType",
71+ "syndication",
72+ "transcript",
73+ ]);
6274 const assetDocs = allDocs.filter((d) => d._type === "sanity.imageAsset" || d._type === "sanity.fileAsset");
6375 const imageAssetMap = new Map(
6476 allDocs.filter((d) => d._type === "sanity.imageAsset").map((d) => [d._id, d])
6577 );
66- const contentDocs = allDocs.filter((d) => d._type !== "sanity.imageAsset" && d._type !== "sanity.fileAsset");
78+ const rawContentDocs = allDocs.filter((d) => activeTypes.has(d._type));
79+ const activeDocIds = new Set(rawContentDocs.filter((d) => d._type !== "transcript").map((d) => d._id));
80+ const contentDocs = rawContentDocs.filter((d) => {
81+ if (d._type !== "transcript") return true;
82+ // Exclude transcripts that only reference legacy/deleted document types (lesson/short)
83+ for (const v of Object.values(d)) {
84+ if (v && typeof v === "object" && typeof v._ref === "string" && !activeDocIds.has(v._ref)) {
85+ return false;
86+ }
87+ }
88+ return true;
89+ });
6790
6891 // Collect all known IDs (including assets and syndication.* targets) so valid strong references are never weakened
69- const allTargetIds = new Set(allDocs .map((d) => d._id));
92+ const allTargetIds = new Set(contentDocs .map((d) => d._id));
7093 const syndicationStubs = new Map();
7194
7295 for (const doc of contentDocs) {
@@ -260,7 +283,6 @@ jobs:
260283 }
261284 console.log(`Found ${devUploadedIds.size} image binaries in 'dev' CDN bucket (${prodToDevAssetMap.size} mapped IDs).`);
262285
263- const activeTypes = new Set(["post", "podcast", "guest", "author", "sponsor", "page", "settings", "podcastType"]);
264286 const priorityRefs = new Set();
265287 const inlineRefs = new Set();
266288
@@ -274,7 +296,6 @@ jobs:
274296 }
275297
276298 for (const doc of contentDocs) {
277- if (!activeTypes.has(doc._type)) continue;
278299 if (doc.coverImage?.asset?._ref) priorityRefs.add(doc.coverImage.asset._ref);
279300 if (doc.photo?.asset?._ref) priorityRefs.add(doc.photo.asset._ref);
280301 if (doc.ogImage?.asset?._ref) priorityRefs.add(doc.ogImage.asset._ref);
@@ -363,6 +384,34 @@ jobs:
363384 return devUploadedIds;
364385 }
365386
387+ async function cleanupOrphanedDevDocs() {
388+ const expRes = await fetch(`https://${projectId}.api.sanity.io/v2021-06-07/data/export/dev`, {
389+ headers: { Authorization: `Bearer ${activeToken || tokens[0]}` },
390+ });
391+ if (!expRes.ok) return;
392+ const text = await expRes.text();
393+ const devDocs = text.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
394+ const deleteMutations = [];
395+
396+ for (const d of devDocs) {
397+ if (d._type.startsWith("system.")) continue;
398+ if (d._type === "sanity.imageAsset" || d._type === "sanity.fileAsset") {
399+ if (typeof d.path === "string" && d.path.includes("/production/")) {
400+ deleteMutations.push({ delete: { id: d._id } });
401+ }
402+ continue;
403+ }
404+ if (!activeTypes.has(d._type) || (d._type === "transcript" && !allTargetIds.has(d._id))) {
405+ deleteMutations.push({ delete: { id: d._id } });
406+ }
407+ }
408+
409+ if (deleteMutations.length > 0) {
410+ console.log(`Cleaning up ${deleteMutations.length} legacy/orphaned documents in 'dev'...`);
411+ await sendChunked(deleteMutations, "Cleanup Legacy & Orphaned Docs", "raw");
412+ }
413+ }
414+
366415 (async () => {
367416 if (prodDeduplicateMutations.length > 0) {
368417 console.log(`Cleaning up ${prodDeduplicateMutations.length} duplicate syndications arrays in production...`);
@@ -375,30 +424,25 @@ jobs:
375424 }
376425
377426 // 1. Upload/map missing active image binaries to dev (populates prodToDevAssetMap & allTargetIds)
378- const devUploadedIds = await uploadMissingDevImages();
427+ await uploadMissingDevImages();
379428
380429 // 2. Now sanitize content documents so all active image _refs are rewritten to their real dev binary asset IDs
381430 const sanitized = contentDocs.map(sanitizeDoc);
382431 console.log(`Prepared ${sanitized.length} content documents and ${syndicationStubs.size} syndication stubs for dataset "dev"...`);
383432
384- // 3. Import remaining asset metadata docs (for unreferenced/legacy assets) so all asset refs resolve
385- const remainingAssetDocs = assetDocs
386- .filter((d) => !devUploadedIds.has(d._id))
387- .map(({ _rev, ...rest }) => rest);
388- if (remainingAssetDocs.length > 0) {
389- await sendChunked(remainingAssetDocs, "Pass 1a: Remaining Asset Metadata", "createIfNotExists");
390- }
391-
392- // 4. Create content + syndication stubs so all internal document references resolve as strong references
433+ // 3. Create content + syndication stubs so all internal document references resolve as strong references
393434 const stubs = [
394435 ...sanitized.map((d) => ({ _id: d._id, _type: d._type })),
395436 ...Array.from(syndicationStubs.values()),
396437 ];
397- await sendChunked(stubs, "Pass 1b : Document & Syndication Stubs", "createIfNotExists");
438+ await sendChunked(stubs, "Pass 1 : Document & Syndication Stubs", "createIfNotExists");
398439
399- // 5 . Populate full content documents (with strong coverImage.asset & syndications references and unique _keys)
440+ // 4 . Populate full content documents (with strong coverImage.asset & syndications references and unique _keys)
400441 await sendChunked(sanitized, "Pass 2: Content Docs", "createOrReplace");
401442
443+ // 5. Remove any legacy document types and orphaned production-path asset metadata from dev
444+ await cleanupOrphanedDevDocs();
445+
402446 console.log("Dataset sync completed successfully!");
403447 })().catch((err) => {
404448 console.error(err);
0 commit comments