@artinstack/migrator 0.1.25 → 0.1.26

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,4 +1,4 @@
1
- import { N as NormalizedPost, a as NormalizedPage, b as NormalizedAsset, c as NormalizedPortfolio, d as NormalizedCategory, e as NormalizedTag, f as NormalizedEntity } from './types-Bicgdp4Z.js';
1
+ import { N as NormalizedPost, a as NormalizedPage, b as NormalizedAsset, c as NormalizedPortfolio, d as NormalizedCategory, e as NormalizedTag, f as NormalizedComment, g as NormalizedEntity } from './types-Bhi-0uKs.js';
2
2
 
3
3
  interface EntityBundle {
4
4
  posts: NormalizedPost[];
@@ -7,6 +7,7 @@ interface EntityBundle {
7
7
  portfolios: NormalizedPortfolio[];
8
8
  categories: NormalizedCategory[];
9
9
  tags: NormalizedTag[];
10
+ comments: NormalizedComment[];
10
11
  }
11
12
  declare function emptyBundle(): EntityBundle;
12
13
  declare function collectEntities(entities: AsyncIterable<NormalizedEntity>): Promise<EntityBundle>;
@@ -17,6 +18,7 @@ interface BundleCounts {
17
18
  portfolios: number;
18
19
  categories: number;
19
20
  tags: number;
21
+ comments: number;
20
22
  }
21
23
  declare function bundleCounts(bundle: EntityBundle): BundleCounts;
22
24
 
@@ -26,7 +26,8 @@ function emptyBundle() {
26
26
  media: [],
27
27
  portfolios: [],
28
28
  categories: [],
29
- tags: []
29
+ tags: [],
30
+ comments: []
30
31
  };
31
32
  }
32
33
  async function collectEntities(entities) {
@@ -51,6 +52,9 @@ async function collectEntities(entities) {
51
52
  case "tag":
52
53
  bundle.tags.push(entity);
53
54
  break;
55
+ case "comment":
56
+ bundle.comments.push(entity);
57
+ break;
54
58
  default: {
55
59
  const _exhaustive = entity;
56
60
  throw new Error(`Unknown entity type: ${_exhaustive.type}`);
@@ -66,7 +70,8 @@ function bundleCounts(bundle) {
66
70
  assets: bundle.media.length,
67
71
  portfolios: bundle.portfolios.length,
68
72
  categories: bundle.categories.length,
69
- tags: bundle.tags.length
73
+ tags: bundle.tags.length,
74
+ comments: bundle.comments.length
70
75
  };
71
76
  }
72
77
 
@@ -99,4 +104,4 @@ export {
99
104
  bundleCounts,
100
105
  buildPortfolioMediaLinks
101
106
  };
102
- //# sourceMappingURL=chunk-PD5AZX3B.js.map
107
+ //# sourceMappingURL=chunk-FEDV5UFR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/normalizer/types.ts","../src/normalizer/idempotency.ts","../src/normalizer/bundle.ts","../src/normalizer/portfolio-media.ts"],"sourcesContent":["export type MigrationPlatform = \"wordpress\" | \"smugmug\" | \"squarespace\" | \"wix\";\n\nexport type EntityType =\n | \"post\"\n | \"page\"\n | \"asset\"\n | \"portfolio\"\n | \"category\"\n | \"tag\"\n | \"comment\";\n\nexport type PublishStatus = \"draft\" | \"published\" | \"archived\";\n\n/** AIS comment moderation statuses — import maps legacy values into this set only. */\nexport type CommentModerationStatus = \"approved\" | \"pending\" | \"spam\" | \"rejected\";\n\nexport interface SourceMetadata {\n platform: MigrationPlatform;\n id: string;\n url?: string;\n path?: string;\n exportedAt?: string;\n /** WordPress `post_type` when the DTO shape differs (e.g. portfolio CPT emitted as `page`). */\n postType?: string;\n}\n\n/** Canonical post DTO — raw HTML; sanitize at host sink. */\nexport interface NormalizedPost {\n type: \"post\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n excerpt?: string;\n contentHtml: string;\n publishedAt?: string;\n status: PublishStatus;\n categorySlugs?: string[];\n tagSlugs?: string[];\n /** WordPress attachment id before two-pass resolution. */\n sourceFeaturedMediaId?: string;\n featuredAssetSourceId?: string;\n seoTitle?: string;\n seoDescription?: string;\n /**\n * Unambiguous like total from the source export (M-engagement).\n * Host may opt in to seed `posts.likes_count`; never invents engagement_likes rows.\n */\n sourceLikeCount?: number;\n}\n\n/** RevSlider / MasterSlider reference from theme hero meta (OSS-27) — alias only, no slide payloads in WXR. */\nexport interface PageHeroSliderHint {\n plugin: \"revslider\" | \"masterslider\";\n alias: string;\n slidertitle?: string;\n source?: \"meta-shortcode\" | \"meta-slider-field\" | \"tatsu-json\";\n}\n\n/** Non-body layout signals for host promotion (theme chrome, hero slots, …). */\nexport interface PageLayoutHints {\n heroSlider?: PageHeroSliderHint;\n}\n\n/** Canonical page DTO — raw HTML snapshot. */\nexport interface NormalizedPage {\n type: \"page\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n contentHtml: string;\n contentCss?: string;\n isHomePage?: boolean;\n /** Site portfolio listing shell (distinct from portfolio CPT singles). */\n isPortfolioPage?: boolean;\n layoutHints?: PageLayoutHints;\n status: PublishStatus;\n seoTitle?: string;\n seoDescription?: string;\n}\n\n/** EXIF fields preserved from SmugMug / camera metadata when present. */\nexport interface NormalizedAssetExif {\n iso?: number;\n aperture?: number;\n shutter?: string;\n focalLength?: number;\n}\n\n/** Remote asset to stream into the host sink. */\nexport interface NormalizedAsset {\n type: \"asset\";\n source: SourceMetadata;\n sourceId: string;\n sourceUrl: string;\n filename: string;\n mimeType?: string;\n caption?: string;\n altText?: string;\n keywords?: string[];\n exif?: NormalizedAssetExif;\n portfolioSourceId?: string;\n sort?: number;\n}\n\n/** M2M index: portfolio ↔ asset membership and sort order. */\nexport interface PortfolioMediaLink {\n portfolioSourceId: string;\n assetSourceId: string;\n sort: number;\n}\n\nexport interface NormalizedPortfolio {\n type: \"portfolio\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n description?: string;\n parentSourceId?: string;\n}\n\nexport interface NormalizedCategory {\n type: \"category\";\n source: SourceMetadata;\n sourceId: string;\n name: string;\n slug: string;\n}\n\nexport interface NormalizedTag {\n type: \"tag\";\n source: SourceMetadata;\n sourceId: string;\n name: string;\n slug: string;\n}\n\n/**\n * Independent comment entity (M-engagement) — not nested under NormalizedPost.\n * Authors are always guest provenance; host resolves post/parent via source ids.\n */\nexport interface NormalizedComment {\n type: \"comment\";\n source: SourceMetadata;\n sourceId: string;\n /** Source id of the parent post (`NormalizedPost.sourceId`). */\n postSourceId: string;\n /** Source id of the parent comment when threaded; omit for roots. */\n parentSourceId?: string;\n contentHtml: string;\n status: CommentModerationStatus;\n authorType: \"guest\";\n authorName?: string;\n authorEmail?: string;\n authorUrl?: string;\n publishedAt?: string;\n}\n\nexport type NormalizedEntity =\n | NormalizedPost\n | NormalizedPage\n | NormalizedAsset\n | NormalizedPortfolio\n | NormalizedCategory\n | NormalizedTag\n | NormalizedComment;\n\nexport interface ValidationIssue {\n code: string;\n message: string;\n path?: string;\n}\n\nexport interface ValidationResult {\n ok: boolean;\n issues: ValidationIssue[];\n summary?: {\n posts?: number;\n pages?: number;\n assets?: number;\n portfolios?: number;\n /** WordPress `post_type=portfolio` (and configured CPT slugs) in raw WXR. */\n portfolioCpt?: number;\n categories?: number;\n tags?: number;\n comments?: number;\n /** WXR rows the parser would emit (OSS-19). */\n importableItemCount?: number;\n unsupportedOnly?: boolean;\n skippedPostTypes?: Record<string, number>;\n };\n}\n\nexport interface WxrImportSummary {\n importableItemCount: number;\n unsupportedOnly: boolean;\n skippedPostTypes: Record<string, number>;\n skippedWooCommerceStubPages?: number;\n}\n\nexport interface AdapterContext {\n input: unknown;\n cursor?: MigrationCursor;\n}\n\nexport interface MigrationAdapter {\n platform: MigrationPlatform;\n validateInput(input: unknown): ValidationResult | Promise<ValidationResult>;\n enumerateEntities(ctx: AdapterContext): AsyncIterable<NormalizedEntity>;\n /** Platform-specific import accounting (e.g. WordPress skipped `post_type`s). */\n getImportSummary?(input: unknown): Promise<WxrImportSummary | undefined>;\n}\n\nexport interface MigrationCursor {\n lastEntityKey?: EntityKey;\n state?: Record<string, unknown>;\n}\n\nexport interface EntityKey {\n platform: MigrationPlatform;\n entityType: EntityType;\n sourceId: string;\n}\n\nexport function entityKey(entity: NormalizedEntity, platform: MigrationPlatform): EntityKey {\n return {\n platform,\n entityType: entity.type,\n sourceId: entity.sourceId,\n };\n}\n","import type { EntityKey, MigrationCursor } from \"./types.js\";\n\n/** Portable entity state for resume / idempotency (not Directus field names). */\nexport type EntityState = \"pending\" | \"done\" | \"failed\" | \"skipped\";\n\nexport interface TrackedEntity extends EntityKey {\n state: EntityState;\n targetId?: string;\n errorMessage?: string;\n}\n\nexport interface MigrationCheckpoint {\n jobId: string;\n cursor: MigrationCursor;\n entities: TrackedEntity[];\n updatedAt: string;\n}\n\nexport function isTerminalState(state: EntityState): boolean {\n return state === \"done\" || state === \"skipped\";\n}\n\nexport function shouldProcessEntity(\n key: EntityKey,\n entities: TrackedEntity[],\n): boolean {\n const existing = entities.find(\n (e) =>\n e.platform === key.platform &&\n e.entityType === key.entityType &&\n e.sourceId === key.sourceId,\n );\n return !existing || !isTerminalState(existing.state);\n}\n","import type {\n NormalizedAsset,\n NormalizedCategory,\n NormalizedComment,\n NormalizedEntity,\n NormalizedPage,\n NormalizedPortfolio,\n NormalizedPost,\n NormalizedTag,\n} from \"./types.js\";\n\nexport interface EntityBundle {\n posts: NormalizedPost[];\n pages: NormalizedPage[];\n media: NormalizedAsset[];\n portfolios: NormalizedPortfolio[];\n categories: NormalizedCategory[];\n tags: NormalizedTag[];\n comments: NormalizedComment[];\n}\n\nexport function emptyBundle(): EntityBundle {\n return {\n posts: [],\n pages: [],\n media: [],\n portfolios: [],\n categories: [],\n tags: [],\n comments: [],\n };\n}\n\nexport async function collectEntities(\n entities: AsyncIterable<NormalizedEntity>,\n): Promise<EntityBundle> {\n const bundle = emptyBundle();\n\n for await (const entity of entities) {\n switch (entity.type) {\n case \"post\":\n bundle.posts.push(entity);\n break;\n case \"page\":\n bundle.pages.push(entity);\n break;\n case \"asset\":\n bundle.media.push(entity);\n break;\n case \"portfolio\":\n bundle.portfolios.push(entity);\n break;\n case \"category\":\n bundle.categories.push(entity);\n break;\n case \"tag\":\n bundle.tags.push(entity);\n break;\n case \"comment\":\n bundle.comments.push(entity);\n break;\n default: {\n const _exhaustive: never = entity;\n throw new Error(`Unknown entity type: ${(_exhaustive as NormalizedEntity).type}`);\n }\n }\n }\n\n return bundle;\n}\n\nexport interface BundleCounts {\n posts: number;\n pages: number;\n assets: number;\n portfolios: number;\n categories: number;\n tags: number;\n comments: number;\n}\n\nexport function bundleCounts(bundle: EntityBundle): BundleCounts {\n return {\n posts: bundle.posts.length,\n pages: bundle.pages.length,\n assets: bundle.media.length,\n portfolios: bundle.portfolios.length,\n categories: bundle.categories.length,\n tags: bundle.tags.length,\n comments: bundle.comments.length,\n };\n}\n","import type { EntityBundle } from \"./bundle.js\";\nimport type { PortfolioMediaLink } from \"./types.js\";\n\n/** Derive portfolio↔asset M2M rows from assets carrying `portfolioSourceId`. */\nexport function buildPortfolioMediaLinks(bundle: EntityBundle): PortfolioMediaLink[] {\n const links: PortfolioMediaLink[] = [];\n\n for (const asset of bundle.media) {\n if (!asset.portfolioSourceId) continue;\n links.push({\n portfolioSourceId: asset.portfolioSourceId,\n assetSourceId: asset.sourceId,\n sort: asset.sort ?? 0,\n });\n }\n\n links.sort((a, b) => {\n if (a.portfolioSourceId !== b.portfolioSourceId) {\n return a.portfolioSourceId.localeCompare(b.portfolioSourceId);\n }\n return a.sort - b.sort || a.assetSourceId.localeCompare(b.assetSourceId);\n });\n\n return links;\n}\n"],"mappings":";AAkOO,SAAS,UAAU,QAA0B,UAAwC;AAC1F,SAAO;AAAA,IACL;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,EACnB;AACF;;;ACtNO,SAAS,gBAAgB,OAA6B;AAC3D,SAAO,UAAU,UAAU,UAAU;AACvC;AAEO,SAAS,oBACd,KACA,UACS;AACT,QAAM,WAAW,SAAS;AAAA,IACxB,CAAC,MACC,EAAE,aAAa,IAAI,YACnB,EAAE,eAAe,IAAI,cACrB,EAAE,aAAa,IAAI;AAAA,EACvB;AACA,SAAO,CAAC,YAAY,CAAC,gBAAgB,SAAS,KAAK;AACrD;;;ACZO,SAAS,cAA4B;AAC1C,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,IACR,YAAY,CAAC;AAAA,IACb,YAAY,CAAC;AAAA,IACb,MAAM,CAAC;AAAA,IACP,UAAU,CAAC;AAAA,EACb;AACF;AAEA,eAAsB,gBACpB,UACuB;AACvB,QAAM,SAAS,YAAY;AAE3B,mBAAiB,UAAU,UAAU;AACnC,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,WAAW,KAAK,MAAM;AAC7B;AAAA,MACF,KAAK;AACH,eAAO,WAAW,KAAK,MAAM;AAC7B;AAAA,MACF,KAAK;AACH,eAAO,KAAK,KAAK,MAAM;AACvB;AAAA,MACF,KAAK;AACH,eAAO,SAAS,KAAK,MAAM;AAC3B;AAAA,MACF,SAAS;AACP,cAAM,cAAqB;AAC3B,cAAM,IAAI,MAAM,wBAAyB,YAAiC,IAAI,EAAE;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,aAAa,QAAoC;AAC/D,SAAO;AAAA,IACL,OAAO,OAAO,MAAM;AAAA,IACpB,OAAO,OAAO,MAAM;AAAA,IACpB,QAAQ,OAAO,MAAM;AAAA,IACrB,YAAY,OAAO,WAAW;AAAA,IAC9B,YAAY,OAAO,WAAW;AAAA,IAC9B,MAAM,OAAO,KAAK;AAAA,IAClB,UAAU,OAAO,SAAS;AAAA,EAC5B;AACF;;;ACvFO,SAAS,yBAAyB,QAA4C;AACnF,QAAM,QAA8B,CAAC;AAErC,aAAW,SAAS,OAAO,OAAO;AAChC,QAAI,CAAC,MAAM,kBAAmB;AAC9B,UAAM,KAAK;AAAA,MACT,mBAAmB,MAAM;AAAA,MACzB,eAAe,MAAM;AAAA,MACrB,MAAM,MAAM,QAAQ;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,QAAI,EAAE,sBAAsB,EAAE,mBAAmB;AAC/C,aAAO,EAAE,kBAAkB,cAAc,EAAE,iBAAiB;AAAA,IAC9D;AACA,WAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,cAAc,EAAE,aAAa;AAAA,EACzE,CAAC;AAED,SAAO;AACT;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  enumerateSquarespaceEntities,
4
4
  summarizeSquarespaceExport,
5
5
  validateSquarespaceExportFile
6
- } from "./chunk-FS3KQJCE.js";
6
+ } from "./chunk-XJDLJM7Q.js";
7
7
  import {
8
8
  flattenWordPressBuilders,
9
9
  parseSliderMetaValue,
@@ -63,6 +63,61 @@ function mapPublishStatus(wpStatus) {
63
63
  return "archived";
64
64
  }
65
65
  }
66
+ function mapWpCommentModerationStatus(approved) {
67
+ const value = (approved ?? "").trim().toLowerCase();
68
+ switch (value) {
69
+ case "1":
70
+ case "approve":
71
+ case "approved":
72
+ return { status: "approved" };
73
+ case "0":
74
+ case "hold":
75
+ case "unapproved":
76
+ case "pending":
77
+ return { status: "pending" };
78
+ case "spam":
79
+ return { status: "spam" };
80
+ case "trash":
81
+ return { status: "rejected" };
82
+ default:
83
+ return { status: "pending", unknown: true };
84
+ }
85
+ }
86
+ function isImportableWpCommentType(commentType) {
87
+ const normalized = (commentType ?? "").trim().toLowerCase();
88
+ return normalized === "" || normalized === "comment";
89
+ }
90
+ function parseItemComments(item, postSourceId, exportedAt) {
91
+ const comments = [];
92
+ for (const raw of asArray(item.comment)) {
93
+ if (!isImportableWpCommentType(textValue(raw.comment_type))) continue;
94
+ const commentId = textValue(raw.comment_id).trim();
95
+ const contentHtml = textValue(raw.comment_content);
96
+ if (!commentId || !contentHtml.trim()) continue;
97
+ const parentRaw = textValue(raw.comment_parent).trim();
98
+ const parentSourceId = parentRaw && parentRaw !== "0" ? parentRaw : void 0;
99
+ const moderation = mapWpCommentModerationStatus(textValue(raw.comment_approved));
100
+ const authorName = textValue(raw.comment_author).trim() || void 0;
101
+ const authorEmail = textValue(raw.comment_author_email).trim() || void 0;
102
+ const authorUrl = textValue(raw.comment_author_url).trim() || void 0;
103
+ const publishedAt = textValue(raw.comment_date_gmt).trim() || textValue(raw.comment_date).trim() || void 0;
104
+ comments.push({
105
+ type: "comment",
106
+ source: sourceMeta(commentId, void 0, exportedAt),
107
+ sourceId: commentId,
108
+ postSourceId,
109
+ parentSourceId,
110
+ contentHtml,
111
+ status: moderation.status,
112
+ authorType: "guest",
113
+ authorName,
114
+ authorEmail,
115
+ authorUrl,
116
+ publishedAt
117
+ });
118
+ }
119
+ return comments;
120
+ }
66
121
  function getContentEncoded(item) {
67
122
  const content = item.content;
68
123
  if (content !== void 0) {
@@ -490,6 +545,9 @@ async function* enumerateWxrEntities(options) {
490
545
  featuredAssetSourceId
491
546
  };
492
547
  yield post;
548
+ for (const comment of parseItemComments(item, id, options.exportedAt)) {
549
+ yield comment;
550
+ }
493
551
  } else {
494
552
  const isHomePage = !isPortfolioCpt && (getPostMeta(item, "_wp_show_on_front") === "1" || getPostMeta(item, "page_on_front") === "1");
495
553
  const isPortfolioPage = !isPortfolioCpt && inferPortfolioListingPage(item, {
@@ -546,6 +604,7 @@ async function validateWxrFile(filePath, options = { filePath }) {
546
604
  portfolioCpt: countWxrPortfolioCptItems(items),
547
605
  categories: 0,
548
606
  tags: 0,
607
+ comments: items.filter((i) => textValue(i.post_type) === "post").reduce((count, item) => count + asArray(item.comment).length, 0),
549
608
  importableItemCount: importSummary.importableItemCount
550
609
  };
551
610
  const { categories, tags } = collectTaxonomies(items);
@@ -2839,4 +2898,4 @@ export {
2839
2898
  wixAdapter,
2840
2899
  getAdapter
2841
2900
  };
2842
- //# sourceMappingURL=chunk-YCUTJRNB.js.map
2901
+ //# sourceMappingURL=chunk-ISB7NAA4.js.map