@artinstack/migrator 0.1.7 → 0.1.9

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.
Files changed (41) hide show
  1. package/README.md +2 -1
  2. package/dist/{bundle-uAAHehbv.d.ts → bundle-B3XS20r_.d.ts} +1 -1
  3. package/dist/{chunk-BOYB6XRA.js → chunk-BONZ3U3I.js} +11 -62
  4. package/dist/chunk-BONZ3U3I.js.map +1 -0
  5. package/dist/chunk-EJTWYEAX.js +1 -0
  6. package/dist/chunk-EJTWYEAX.js.map +1 -0
  7. package/dist/{chunk-KF7G7DM6.js → chunk-FB3MMCHY.js} +315 -84
  8. package/dist/chunk-FB3MMCHY.js.map +1 -0
  9. package/dist/{chunk-HI7JHWZU.js → chunk-KTQGOM45.js} +1 -1
  10. package/dist/chunk-KTQGOM45.js.map +1 -0
  11. package/dist/{chunk-MDSY3FEZ.js → chunk-PPT5RIZ4.js} +83 -39
  12. package/dist/chunk-PPT5RIZ4.js.map +1 -0
  13. package/dist/{chunk-XYP3VYDH.js → chunk-S4GMDRGX.js} +149 -6
  14. package/dist/chunk-S4GMDRGX.js.map +1 -0
  15. package/dist/{chunk-S4XYG4SM.js → chunk-S4SUJT2D.js} +36 -2
  16. package/dist/chunk-S4SUJT2D.js.map +1 -0
  17. package/dist/chunk-XRCF73DA.js +24 -0
  18. package/dist/chunk-XRCF73DA.js.map +1 -0
  19. package/dist/cli/index.js +8 -6
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/index.d.ts +9 -33
  22. package/dist/index.js +31 -16
  23. package/dist/lib/index.d.ts +7 -25
  24. package/dist/lib/index.js +34 -3
  25. package/dist/media-urls-u49RCyPn.d.ts +88 -0
  26. package/dist/normalizer/index.d.ts +4 -4
  27. package/dist/normalizer/index.js +1 -1
  28. package/dist/{rewrite-inline-images-Bq16bJA5.d.ts → rewrite-inline-images-BsgSquzV.d.ts} +4 -0
  29. package/dist/sinks/index.d.ts +21 -5
  30. package/dist/sinks/index.js +7 -4
  31. package/dist/transformers/index.d.ts +170 -4
  32. package/dist/transformers/index.js +5 -4
  33. package/dist/{types-DWOP8Dcy.d.ts → types-Ce4r6zqt.d.ts} +4 -0
  34. package/package.json +1 -1
  35. package/dist/chunk-BOYB6XRA.js.map +0 -1
  36. package/dist/chunk-HI7JHWZU.js.map +0 -1
  37. package/dist/chunk-KF7G7DM6.js.map +0 -1
  38. package/dist/chunk-MDSY3FEZ.js.map +0 -1
  39. package/dist/chunk-S4XYG4SM.js.map +0 -1
  40. package/dist/chunk-XYP3VYDH.js.map +0 -1
  41. package/dist/index-Dp6nqBqe.d.ts +0 -177
@@ -1,4 +1,170 @@
1
- export { E as ExpandMigrationMediaRefsResult, G as GrapesComponent, a as GrapesProjectSnapshot, b as GrapesStyleRule, H as HtmlToGrapesOptions, c as HtmlToTiptapOptions, L as LayoutKind, d as LayoutTypeMap, T as TiptapDoc, e as TiptapMark, f as TiptapNode, V as ValidateGrapesProjectSnapshotOptions, g as ValidateTiptapDocOptions, h as buildMigrationMediaUrlIndex, i as cssToStyles, j as expandMigrationMediaRefs, k as grapesComponentSchema, l as grapesProjectSnapshotSchema, m as grapesStyleRuleSchema, n as htmlToGrapes, o as htmlToTiptap, t as tiptapDocSchema, p as tiptapMarkSchema, q as tiptapNodeSchema, v as validateGrapesProjectSnapshot, s as validateTiptapDoc } from '../index-Dp6nqBqe.js';
2
- export { R as RewriteInlineImageRef, a as RewriteInlineImagesOptions, b as RewriteInlineImagesResult, S as StampMigrationMediaRefsOptions, U as UploadedAssetRef, r as rewriteInlineImages, s as stampMigrationMediaRefs } from '../rewrite-inline-images-Bq16bJA5.js';
3
- import 'zod';
4
- import '../types-DWOP8Dcy.js';
1
+ import { z } from 'zod';
2
+ import { l as ValidationResult } from '../types-Ce4r6zqt.js';
3
+ export { R as RewriteInlineImageRef, a as RewriteInlineImagesOptions, b as RewriteInlineImagesResult, S as StampMigrationMediaRefsOptions, U as UploadedAssetRef, r as rewriteInlineImages, s as stampMigrationMediaRefs } from '../rewrite-inline-images-BsgSquzV.js';
4
+ export { d as buildMigrationMediaUrlIndex } from '../media-urls-u49RCyPn.js';
5
+
6
+ type LayoutKind = "section" | "row" | "column";
7
+ /** Map OSS-2 `data-layout` markers to Grapes component types (host may override). */
8
+ interface LayoutTypeMap {
9
+ section?: string;
10
+ row?: string;
11
+ column?: string;
12
+ }
13
+ interface HtmlToGrapesOptions {
14
+ /** Map source class names to Grapes component types. */
15
+ componentMap?: Record<string, string>;
16
+ /** Map HTML tag names to Grapes component types (e.g. `h2` → `heading`). */
17
+ tagMap?: Record<string, string>;
18
+ /** Map `data-layout` section/row/column markers to Grapes component types. */
19
+ layoutTypeMap?: LayoutTypeMap;
20
+ /** Grapes type for OSS-12 `data-wp-widget` markers. Default: `wp-widget`. */
21
+ widgetComponentType?: string;
22
+ }
23
+ interface GrapesStyleRule {
24
+ selectors: string[];
25
+ style: Record<string, string>;
26
+ }
27
+ interface GrapesComponent {
28
+ type: string;
29
+ tagName?: string;
30
+ attributes?: Record<string, string>;
31
+ classes?: string[];
32
+ components?: GrapesComponent[];
33
+ content?: string;
34
+ void?: boolean;
35
+ }
36
+ interface GrapesProjectSnapshot {
37
+ content: GrapesComponent[];
38
+ styles: GrapesStyleRule[];
39
+ contentHtml?: string;
40
+ contentCss?: string;
41
+ }
42
+
43
+ /** Cheerio HTML walk → Grapes `content` + root `styles`. */
44
+ declare function htmlToGrapes(html: string, options?: HtmlToGrapesOptions): GrapesProjectSnapshot;
45
+
46
+ /** ProseMirror / Tiptap mark (inline formatting). */
47
+ interface TiptapMark {
48
+ type: string;
49
+ attrs?: Record<string, string>;
50
+ }
51
+ /** ProseMirror / Tiptap node — text nodes use `text`; others use `content`. */
52
+ interface TiptapNode {
53
+ type: string;
54
+ attrs?: Record<string, unknown>;
55
+ content?: TiptapNode[];
56
+ marks?: TiptapMark[];
57
+ text?: string;
58
+ }
59
+ /** Root Tiptap document (`content_json` shape). */
60
+ interface TiptapDoc {
61
+ type: "doc";
62
+ content: TiptapNode[];
63
+ }
64
+ interface HtmlToTiptapOptions {
65
+ /**
66
+ * Unwrap OSS-2 `data-layout` scaffolding (section/row/column divs) into prose blocks.
67
+ * @default true
68
+ */
69
+ unwrapLayoutMarkers?: boolean;
70
+ }
71
+
72
+ /** Cheerio HTML walk → Tiptap / ProseMirror `doc` JSON for blog `content_json`. */
73
+ declare function htmlToTiptap(html: string, options?: HtmlToTiptapOptions): TiptapDoc;
74
+
75
+ /** Parse `<style>` blocks and class rules into Grapes root `styles[]`. */
76
+ declare function cssToStyles(css: string): GrapesStyleRule[];
77
+
78
+ declare const grapesStyleRuleSchema: z.ZodObject<{
79
+ selectors: z.ZodArray<z.ZodString, "many">;
80
+ style: z.ZodRecord<z.ZodString, z.ZodString>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ style: Record<string, string>;
83
+ selectors: string[];
84
+ }, {
85
+ style: Record<string, string>;
86
+ selectors: string[];
87
+ }>;
88
+ declare const grapesComponentSchema: z.ZodType<GrapesComponent>;
89
+ declare const grapesProjectSnapshotSchema: z.ZodObject<{
90
+ content: z.ZodArray<z.ZodType<GrapesComponent, z.ZodTypeDef, GrapesComponent>, "many">;
91
+ styles: z.ZodArray<z.ZodObject<{
92
+ selectors: z.ZodArray<z.ZodString, "many">;
93
+ style: z.ZodRecord<z.ZodString, z.ZodString>;
94
+ }, "strip", z.ZodTypeAny, {
95
+ style: Record<string, string>;
96
+ selectors: string[];
97
+ }, {
98
+ style: Record<string, string>;
99
+ selectors: string[];
100
+ }>, "many">;
101
+ contentHtml: z.ZodOptional<z.ZodString>;
102
+ contentCss: z.ZodOptional<z.ZodString>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ content: GrapesComponent[];
105
+ styles: {
106
+ style: Record<string, string>;
107
+ selectors: string[];
108
+ }[];
109
+ contentHtml?: string | undefined;
110
+ contentCss?: string | undefined;
111
+ }, {
112
+ content: GrapesComponent[];
113
+ styles: {
114
+ style: Record<string, string>;
115
+ selectors: string[];
116
+ }[];
117
+ contentHtml?: string | undefined;
118
+ contentCss?: string | undefined;
119
+ }>;
120
+ interface ValidateGrapesProjectSnapshotOptions {
121
+ /** When set, every component `type` in the tree must be in this allowlist. */
122
+ allowedComponentTypes?: string[];
123
+ }
124
+ /**
125
+ * Opt-in structural check for a Grapes project snapshot (not a full Grapes editor project file).
126
+ * Does not validate host-specific component registries unless `allowedComponentTypes` is passed.
127
+ */
128
+ declare function validateGrapesProjectSnapshot(snapshot: unknown, options?: ValidateGrapesProjectSnapshotOptions): ValidationResult;
129
+
130
+ declare const tiptapMarkSchema: z.ZodObject<{
131
+ type: z.ZodString;
132
+ attrs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
133
+ }, "strip", z.ZodTypeAny, {
134
+ type: string;
135
+ attrs?: Record<string, string> | undefined;
136
+ }, {
137
+ type: string;
138
+ attrs?: Record<string, string> | undefined;
139
+ }>;
140
+ declare const tiptapNodeSchema: z.ZodType<TiptapNode>;
141
+ declare const tiptapDocSchema: z.ZodObject<{
142
+ type: z.ZodLiteral<"doc">;
143
+ content: z.ZodArray<z.ZodType<TiptapNode, z.ZodTypeDef, TiptapNode>, "many">;
144
+ }, "strip", z.ZodTypeAny, {
145
+ type: "doc";
146
+ content: TiptapNode[];
147
+ }, {
148
+ type: "doc";
149
+ content: TiptapNode[];
150
+ }>;
151
+ interface ValidateTiptapDocOptions {
152
+ /** When set, every node `type` in the tree must be in this allowlist. */
153
+ allowedNodeTypes?: string[];
154
+ /** When set, every mark `type` must be in this allowlist. */
155
+ allowedMarkTypes?: string[];
156
+ }
157
+ /** Opt-in structural check for a Tiptap / ProseMirror document. */
158
+ declare function validateTiptapDoc(doc: unknown, options?: ValidateTiptapDocOptions): ValidationResult;
159
+
160
+ interface ExpandMigrationMediaRefsResult {
161
+ html: string;
162
+ unresolved: string[];
163
+ }
164
+ /**
165
+ * Expand OSS-14 `artinstack-migration://asset/…` refs to host CDN URLs.
166
+ * Lookup (`migration_entities` → `publicUrl`) is host-supplied via `resolvePublicUrl`.
167
+ */
168
+ declare function expandMigrationMediaRefs(html: string, resolvePublicUrl: (sourceId: string) => string | undefined): ExpandMigrationMediaRefsResult;
169
+
170
+ export { type ExpandMigrationMediaRefsResult, type GrapesComponent, type GrapesProjectSnapshot, type GrapesStyleRule, type HtmlToGrapesOptions, type HtmlToTiptapOptions, type LayoutKind, type LayoutTypeMap, type TiptapDoc, type TiptapMark, type TiptapNode, type ValidateGrapesProjectSnapshotOptions, type ValidateTiptapDocOptions, cssToStyles, expandMigrationMediaRefs, grapesComponentSchema, grapesProjectSnapshotSchema, grapesStyleRuleSchema, htmlToGrapes, htmlToTiptap, tiptapDocSchema, tiptapMarkSchema, tiptapNodeSchema, validateGrapesProjectSnapshot, validateTiptapDoc };
@@ -11,13 +11,14 @@ import {
11
11
  tiptapNodeSchema,
12
12
  validateGrapesProjectSnapshot,
13
13
  validateTiptapDoc
14
- } from "../chunk-S4XYG4SM.js";
14
+ } from "../chunk-S4SUJT2D.js";
15
15
  import {
16
- buildMigrationMediaUrlIndex,
17
16
  rewriteInlineImages,
18
17
  stampMigrationMediaRefs
19
- } from "../chunk-BOYB6XRA.js";
20
- import "../chunk-XYP3VYDH.js";
18
+ } from "../chunk-BONZ3U3I.js";
19
+ import {
20
+ buildMigrationMediaUrlIndex
21
+ } from "../chunk-S4GMDRGX.js";
21
22
  export {
22
23
  buildMigrationMediaUrlIndex,
23
24
  cssToStyles,
@@ -7,6 +7,8 @@ interface SourceMetadata {
7
7
  url?: string;
8
8
  path?: string;
9
9
  exportedAt?: string;
10
+ /** WordPress `post_type` when the DTO shape differs (e.g. portfolio CPT emitted as `page`). */
11
+ postType?: string;
10
12
  }
11
13
  /** Canonical post DTO — raw HTML; sanitize at host sink. */
12
14
  interface NormalizedPost {
@@ -106,6 +108,8 @@ interface ValidationResult {
106
108
  pages?: number;
107
109
  assets?: number;
108
110
  portfolios?: number;
111
+ /** WordPress `post_type=portfolio` (and configured CPT slugs) in raw WXR. */
112
+ portfolioCpt?: number;
109
113
  categories?: number;
110
114
  tags?: number;
111
115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artinstack/migrator",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Stateless content normalizer and migration framework — WordPress, SmugMug, Squarespace → platform-agnostic schema",
5
5
  "license": "MIT",
6
6
  "author": "ArtInStack",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/migration-media-ref.ts","../src/lib/migration-media-url-index.ts","../src/transformers/rewrite-inline-images.ts"],"sourcesContent":["/** Pseudo-URL scheme for portable migration asset pointers (not WordPress shortcodes). */\nexport const MIGRATION_MEDIA_REF_SCHEME = \"artinstack-migration://asset/\";\n\n/** Build `artinstack-migration://asset/{sourceId}` (percent-encodes the normalizer source id). */\nexport function formatMigrationMediaRef(sourceAssetId: string): string {\n return `${MIGRATION_MEDIA_REF_SCHEME}${encodeURIComponent(sourceAssetId)}`;\n}\n\nexport function isMigrationMediaRef(value: string): boolean {\n return value.trim().startsWith(MIGRATION_MEDIA_REF_SCHEME);\n}\n\n/** Parse a migration media ref back to the normalizer `sourceId`, or `undefined` if not a ref. */\nexport function parseMigrationMediaRef(value: string): string | undefined {\n const trimmed = value.trim();\n if (!trimmed.startsWith(MIGRATION_MEDIA_REF_SCHEME)) return undefined;\n const encoded = trimmed.slice(MIGRATION_MEDIA_REF_SCHEME.length);\n if (!encoded) return undefined;\n try {\n return decodeURIComponent(encoded);\n } catch {\n return undefined;\n }\n}\n\n/** Default `replaceWith` for `rewriteInlineImages` / `stampMigrationMediaRefs` (OSS-14). */\nexport function createMigrationMediaRefReplaceWith(): (\n ref: { sourceAssetId?: string },\n) => string {\n return (ref) => {\n if (!ref.sourceAssetId) return \"\";\n return formatMigrationMediaRef(ref.sourceAssetId);\n };\n}\n","import { normalizeAssetUrl } from \"./content-asset-urls.js\";\n\nfunction urlPathname(url: string): string | undefined {\n try {\n return new URL(url, \"http://migration.local\").pathname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Map normalized upload URLs (and pathnames) → normalizer `sourceId`.\n * Attachment ids are WXR `post_id` strings; inline discoveries use `url:{src}`.\n */\nexport function buildMigrationMediaUrlIndex(\n entries: Iterable<{ sourceUrl: string; sourceId: string }>,\n): Map<string, string> {\n const index = new Map<string, string>();\n\n for (const entry of entries) {\n index.set(entry.sourceUrl, entry.sourceId);\n const normalized = normalizeAssetUrl(entry.sourceUrl);\n if (normalized) index.set(normalized, entry.sourceId);\n const pathname = urlPathname(entry.sourceUrl);\n if (pathname) index.set(pathname, entry.sourceId);\n }\n\n return index;\n}\n\nexport function resolveMigrationMediaSourceId(\n src: string,\n urlIndex: Map<string, string>,\n): string | undefined {\n const normalized = normalizeAssetUrl(src);\n if (!normalized) return undefined;\n\n return (\n urlIndex.get(normalized) ??\n urlIndex.get(src) ??\n (urlPathname(normalized) ? urlIndex.get(urlPathname(normalized)!) : undefined)\n );\n}\n","import * as cheerio from \"cheerio\";\n\nimport { normalizeAssetUrl } from \"../lib/content-asset-urls.js\";\nimport {\n createMigrationMediaRefReplaceWith,\n isMigrationMediaRef,\n} from \"../lib/migration-media-ref.js\";\nimport {\n buildMigrationMediaUrlIndex,\n resolveMigrationMediaSourceId,\n} from \"../lib/migration-media-url-index.js\";\n\nexport interface RewriteInlineImageRef {\n originalSrc: string;\n sourceAssetId?: string;\n}\n\nexport interface UploadedAssetRef {\n targetId: string;\n publicUrl?: string;\n}\n\nexport interface RewriteInlineImagesOptions {\n resolveAsset: (src: string) => RewriteInlineImageRef | undefined;\n /**\n * Replace a resolved source id with a migration ref or CDN URL.\n * When omitted, defaults to OSS-14 `artinstack-migration://asset/…` refs.\n */\n replaceWith?: (ref: RewriteInlineImageRef, uploaded?: UploadedAssetRef) => string;\n /**\n * When true, skip URLs that cannot be matched to an uploaded vault target.\n * Default: false when using migration refs; true when a custom `replaceWith` is supplied.\n */\n requireUploaded?: boolean;\n}\n\nexport interface RewriteInlineImagesResult {\n html: string;\n referencedSources: string[];\n unresolved: string[];\n}\n\n/** Inline CSS `background` / `background-image: url(…)` (quoted or bare). */\nconst BACKGROUND_IMAGE_URL_PATTERN =\n /background(?:-image)?\\s*:[^;]*?url\\s*\\(\\s*(['\"]?)([^'\")]+)\\1\\s*\\)/gi;\n\nfunction resolveRewriteOptions(\n options: RewriteInlineImagesOptions,\n): Required<Pick<RewriteInlineImagesOptions, \"replaceWith\" | \"requireUploaded\">> {\n const replaceWith = options.replaceWith ?? createMigrationMediaRefReplaceWith();\n const requireUploaded = options.requireUploaded ?? Boolean(options.replaceWith);\n return { replaceWith, requireUploaded };\n}\n\nfunction tryRewriteUrl(\n src: string,\n options: RewriteInlineImagesOptions,\n uploadedBySourceId: Map<string, UploadedAssetRef>,\n referencedSources: Set<string>,\n unresolved: Set<string>,\n): string | undefined {\n const normalized = normalizeAssetUrl(src);\n if (!normalized) return undefined;\n\n if (isMigrationMediaRef(normalized)) {\n referencedSources.add(normalized);\n return normalized;\n }\n\n referencedSources.add(normalized);\n const ref = options.resolveAsset(normalized);\n if (!ref?.sourceAssetId) {\n unresolved.add(normalized);\n return undefined;\n }\n\n const { replaceWith, requireUploaded } = resolveRewriteOptions(options);\n const uploaded = uploadedBySourceId.get(ref.sourceAssetId);\n if (requireUploaded && !uploaded) {\n unresolved.add(normalized);\n return undefined;\n }\n\n return replaceWith(ref, uploaded);\n}\n\nfunction rewriteBackgroundUrlsInStyle(\n style: string,\n options: RewriteInlineImagesOptions,\n uploadedBySourceId: Map<string, UploadedAssetRef>,\n referencedSources: Set<string>,\n unresolved: Set<string>,\n): string {\n return style.replace(BACKGROUND_IMAGE_URL_PATTERN, (full, quote: string, rawUrl: string) => {\n const replaced = tryRewriteUrl(rawUrl.trim(), options, uploadedBySourceId, referencedSources, unresolved);\n if (!replaced) return full;\n\n const urlCall = quote\n ? `url(${quote}${replaced}${quote})`\n : `url(${replaced})`;\n return full.replace(/url\\s*\\(\\s*(['\"]?)([^'\")]+)\\1\\s*\\)/i, urlCall);\n });\n}\n\nfunction rewriteSrcset(\n srcset: string,\n options: RewriteInlineImagesOptions,\n uploadedBySourceId: Map<string, UploadedAssetRef>,\n referencedSources: Set<string>,\n unresolved: Set<string>,\n): string {\n return srcset\n .split(\",\")\n .map((entry) => {\n const trimmed = entry.trim();\n if (!trimmed) return entry;\n const [urlPart, descriptor] = trimmed.split(/\\s+/, 2);\n const replaced = tryRewriteUrl(urlPart ?? \"\", options, uploadedBySourceId, referencedSources, unresolved);\n if (!replaced) return entry;\n return descriptor ? `${replaced} ${descriptor}` : replaced;\n })\n .join(\", \");\n}\n\n/** Rewrite `<img src>` / `srcset`, `data-bg-image`, and inline CSS backgrounds using uploaded asset targets. */\nexport function rewriteInlineImages(\n html: string,\n options: RewriteInlineImagesOptions,\n uploadedBySourceId: Map<string, UploadedAssetRef>,\n): RewriteInlineImagesResult {\n if (!html.trim()) {\n return { html, referencedSources: [], unresolved: [] };\n }\n\n const $ = cheerio.load(html, { xml: false });\n const referencedSources = new Set<string>();\n const unresolved = new Set<string>();\n\n $(\"img\").each((_, element) => {\n const img = $(element);\n const src = img.attr(\"src\")?.trim();\n if (src && !src.startsWith(\"data:\")) {\n const replaced = tryRewriteUrl(src, options, uploadedBySourceId, referencedSources, unresolved);\n if (replaced) img.attr(\"src\", replaced);\n }\n\n const srcset = img.attr(\"srcset\")?.trim();\n if (srcset) {\n img.attr(\"srcset\", rewriteSrcset(srcset, options, uploadedBySourceId, referencedSources, unresolved));\n }\n });\n\n $(\"[data-bg-image]\").each((_, element) => {\n const node = $(element);\n const bgImage = node.attr(\"data-bg-image\")?.trim();\n if (!bgImage || bgImage.startsWith(\"data:\")) return;\n const replaced = tryRewriteUrl(bgImage, options, uploadedBySourceId, referencedSources, unresolved);\n if (replaced) node.attr(\"data-bg-image\", replaced);\n });\n\n $(\"[style]\").each((_, element) => {\n const node = $(element);\n const style = node.attr(\"style\");\n if (!style?.includes(\"background\")) return;\n const rewritten = rewriteBackgroundUrlsInStyle(\n style,\n options,\n uploadedBySourceId,\n referencedSources,\n unresolved,\n );\n if (rewritten !== style) node.attr(\"style\", rewritten);\n });\n\n return {\n html: $.root().html() ?? html,\n referencedSources: [...referencedSources],\n unresolved: [...unresolved],\n };\n}\n\nexport interface StampMigrationMediaRefsOptions {\n /** Pre-built url/pathname → sourceId map (from attachments + inline assets). */\n urlToSourceId: Map<string, string>;\n replaceWith?: RewriteInlineImagesOptions[\"replaceWith\"];\n requireUploaded?: boolean;\n}\n\n/**\n * OSS-14 — replace resolved `wp-content/uploads` URLs with `artinstack-migration://asset/…`\n * refs. Does not invent refs for unknown URLs (left unchanged + listed in `unresolved`).\n */\nexport function stampMigrationMediaRefs(\n html: string,\n options: StampMigrationMediaRefsOptions,\n): RewriteInlineImagesResult {\n return rewriteInlineImages(\n html,\n {\n resolveAsset: (src) => {\n const sourceAssetId = resolveMigrationMediaSourceId(src, options.urlToSourceId);\n if (!sourceAssetId) return undefined;\n return { originalSrc: src, sourceAssetId };\n },\n replaceWith: options.replaceWith,\n requireUploaded: options.requireUploaded ?? false,\n },\n new Map(),\n );\n}\n\n/** Build a url index from attachment rows and/or normalized assets. */\nexport { buildMigrationMediaUrlIndex };\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
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 = \"post\" | \"page\" | \"asset\" | \"portfolio\" | \"category\" | \"tag\";\n\nexport type PublishStatus = \"draft\" | \"published\" | \"archived\";\n\nexport interface SourceMetadata {\n platform: MigrationPlatform;\n id: string;\n url?: string;\n path?: string;\n exportedAt?: 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\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 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\nexport type NormalizedEntity =\n | NormalizedPost\n | NormalizedPage\n | NormalizedAsset\n | NormalizedPortfolio\n | NormalizedCategory\n | NormalizedTag;\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 categories?: number;\n tags?: number;\n };\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}\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 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}\n\nexport function emptyBundle(): EntityBundle {\n return {\n posts: [],\n pages: [],\n media: [],\n portfolios: [],\n categories: [],\n tags: [],\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 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}\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 };\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":";AA2JO,SAAS,UAAU,QAA0B,UAAwC;AAC1F,SAAO;AAAA,IACL;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,EACnB;AACF;;;AC/IO,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;;;ACdO,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,EACT;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,SAAS;AACP,cAAM,cAAqB;AAC3B,cAAM,IAAI,MAAM,wBAAyB,YAAiC,IAAI,EAAE;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAWO,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,EACpB;AACF;;;AC/EO,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":[]}