@artinstack/migrator 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -15,7 +15,8 @@ src/
15
15
  normalizer/ Canonical DTOs + portable idempotency types
16
16
  sinks/ filesystem export, MigrationSink interface
17
17
  cli/ artinstack-migrate
18
- transformers/ HtmlToGrapes, css-to-styles, rewrite-inline-images
18
+ transformers/ HtmlToGrapes, css-to-styles, inline image rewrite, media ref expand
19
+ lib/ content-asset-urls, migration-media-ref, origin-url-rewrite
19
20
  ```
20
21
 
21
22
  ## Install
@@ -114,6 +115,31 @@ Each file contains an array of normalized DTOs (`NormalizedPost`, `NormalizedPag
114
115
 
115
116
  Per-platform export file formats and API client usage are documented in [docs/architecture.md](./docs/architecture.md).
116
117
 
118
+ ## Migration media refs
119
+
120
+ WordPress `contentHtml` is stamped with `artinstack-migration://asset/…` refs by default (not CDN URLs). Rationale, ref format, and OSS/host split: [docs/architecture.md § Migration media refs](./docs/architecture.md#migration-media-refs).
121
+
122
+ **Host — expand refs before persist** (`htmlToGrapes`, hero promotion, sink write):
123
+
124
+ ```ts
125
+ import {
126
+ expandMigrationMediaRefs,
127
+ formatMigrationMediaRef,
128
+ isMigrationMediaRef,
129
+ parseMigrationMediaRef,
130
+ rewriteInlineImages,
131
+ stampMigrationMediaRefs,
132
+ } from "@artinstack/migrator";
133
+
134
+ const { html, unresolved } = expandMigrationMediaRefs(contentHtml, (sourceId) =>
135
+ lookupPublicUrl(sourceId), // migration_entities → CDN
136
+ );
137
+ ```
138
+
139
+ **CLI / JSON export:** use `--rewrite-gateway` + `--rewrite-public` so gateway uploads normalize before refs are stamped. Unresolved upload URLs stay in HTML and appear in `conflicts.json` as `unresolvedInlineImages`.
140
+
141
+ **Tests:** `fixtures/wordpress/pages-export.test.ts` (naikonpixels pages WXR).
142
+
117
143
  ## Development
118
144
 
119
145
  ```bash
@@ -129,6 +155,8 @@ pnpm dev # watch build
129
155
  |-------|------------------------|------------------|
130
156
  | Parsers + normalizer DTOs | Yes | No |
131
157
  | WordPress builder flattening + origin URL rewrite (pre-DTO) | Yes | Optional same config on adapter input |
158
+ | Stamp `artinstack-migration://asset/…` refs in content HTML | Yes | No |
159
+ | Expand refs → CDN URLs at persist | Exported helper | Call site + DB lookup |
132
160
  | CLI + filesystem JSON export | Yes | No |
133
161
  | `MigrationSink` interface | Yes | Implementation |
134
162
  | Dynamic shortcodes (`[portfolio]`, `[recent_posts]`), forms, sanitize | No | Yes |
@@ -0,0 +1,175 @@
1
+ import {
2
+ normalizeAssetUrl
3
+ } from "./chunk-XYP3VYDH.js";
4
+
5
+ // src/lib/migration-media-ref.ts
6
+ var MIGRATION_MEDIA_REF_SCHEME = "artinstack-migration://asset/";
7
+ function formatMigrationMediaRef(sourceAssetId) {
8
+ return `${MIGRATION_MEDIA_REF_SCHEME}${encodeURIComponent(sourceAssetId)}`;
9
+ }
10
+ function isMigrationMediaRef(value) {
11
+ return value.trim().startsWith(MIGRATION_MEDIA_REF_SCHEME);
12
+ }
13
+ function parseMigrationMediaRef(value) {
14
+ const trimmed = value.trim();
15
+ if (!trimmed.startsWith(MIGRATION_MEDIA_REF_SCHEME)) return void 0;
16
+ const encoded = trimmed.slice(MIGRATION_MEDIA_REF_SCHEME.length);
17
+ if (!encoded) return void 0;
18
+ try {
19
+ return decodeURIComponent(encoded);
20
+ } catch {
21
+ return void 0;
22
+ }
23
+ }
24
+ function createMigrationMediaRefReplaceWith() {
25
+ return (ref) => {
26
+ if (!ref.sourceAssetId) return "";
27
+ return formatMigrationMediaRef(ref.sourceAssetId);
28
+ };
29
+ }
30
+
31
+ // src/lib/migration-media-url-index.ts
32
+ function urlPathname(url) {
33
+ try {
34
+ return new URL(url, "http://migration.local").pathname;
35
+ } catch {
36
+ return void 0;
37
+ }
38
+ }
39
+ function buildMigrationMediaUrlIndex(entries) {
40
+ const index = /* @__PURE__ */ new Map();
41
+ for (const entry of entries) {
42
+ index.set(entry.sourceUrl, entry.sourceId);
43
+ const normalized = normalizeAssetUrl(entry.sourceUrl);
44
+ if (normalized) index.set(normalized, entry.sourceId);
45
+ const pathname = urlPathname(entry.sourceUrl);
46
+ if (pathname) index.set(pathname, entry.sourceId);
47
+ }
48
+ return index;
49
+ }
50
+ function resolveMigrationMediaSourceId(src, urlIndex) {
51
+ const normalized = normalizeAssetUrl(src);
52
+ if (!normalized) return void 0;
53
+ return urlIndex.get(normalized) ?? urlIndex.get(src) ?? (urlPathname(normalized) ? urlIndex.get(urlPathname(normalized)) : void 0);
54
+ }
55
+
56
+ // src/transformers/rewrite-inline-images.ts
57
+ import * as cheerio from "cheerio";
58
+ var BACKGROUND_IMAGE_URL_PATTERN = /background(?:-image)?\s*:[^;]*?url\s*\(\s*(['"]?)([^'")]+)\1\s*\)/gi;
59
+ function resolveRewriteOptions(options) {
60
+ const replaceWith = options.replaceWith ?? createMigrationMediaRefReplaceWith();
61
+ const requireUploaded = options.requireUploaded ?? Boolean(options.replaceWith);
62
+ return { replaceWith, requireUploaded };
63
+ }
64
+ function tryRewriteUrl(src, options, uploadedBySourceId, referencedSources, unresolved) {
65
+ const normalized = normalizeAssetUrl(src);
66
+ if (!normalized) return void 0;
67
+ if (isMigrationMediaRef(normalized)) {
68
+ referencedSources.add(normalized);
69
+ return normalized;
70
+ }
71
+ referencedSources.add(normalized);
72
+ const ref = options.resolveAsset(normalized);
73
+ if (!ref?.sourceAssetId) {
74
+ unresolved.add(normalized);
75
+ return void 0;
76
+ }
77
+ const { replaceWith, requireUploaded } = resolveRewriteOptions(options);
78
+ const uploaded = uploadedBySourceId.get(ref.sourceAssetId);
79
+ if (requireUploaded && !uploaded) {
80
+ unresolved.add(normalized);
81
+ return void 0;
82
+ }
83
+ return replaceWith(ref, uploaded);
84
+ }
85
+ function rewriteBackgroundUrlsInStyle(style, options, uploadedBySourceId, referencedSources, unresolved) {
86
+ return style.replace(BACKGROUND_IMAGE_URL_PATTERN, (full, quote, rawUrl) => {
87
+ const replaced = tryRewriteUrl(rawUrl.trim(), options, uploadedBySourceId, referencedSources, unresolved);
88
+ if (!replaced) return full;
89
+ const urlCall = quote ? `url(${quote}${replaced}${quote})` : `url(${replaced})`;
90
+ return full.replace(/url\s*\(\s*(['"]?)([^'")]+)\1\s*\)/i, urlCall);
91
+ });
92
+ }
93
+ function rewriteSrcset(srcset, options, uploadedBySourceId, referencedSources, unresolved) {
94
+ return srcset.split(",").map((entry) => {
95
+ const trimmed = entry.trim();
96
+ if (!trimmed) return entry;
97
+ const [urlPart, descriptor] = trimmed.split(/\s+/, 2);
98
+ const replaced = tryRewriteUrl(urlPart ?? "", options, uploadedBySourceId, referencedSources, unresolved);
99
+ if (!replaced) return entry;
100
+ return descriptor ? `${replaced} ${descriptor}` : replaced;
101
+ }).join(", ");
102
+ }
103
+ function rewriteInlineImages(html, options, uploadedBySourceId) {
104
+ if (!html.trim()) {
105
+ return { html, referencedSources: [], unresolved: [] };
106
+ }
107
+ const $ = cheerio.load(html, { xml: false });
108
+ const referencedSources = /* @__PURE__ */ new Set();
109
+ const unresolved = /* @__PURE__ */ new Set();
110
+ $("img").each((_, element) => {
111
+ const img = $(element);
112
+ const src = img.attr("src")?.trim();
113
+ if (src && !src.startsWith("data:")) {
114
+ const replaced = tryRewriteUrl(src, options, uploadedBySourceId, referencedSources, unresolved);
115
+ if (replaced) img.attr("src", replaced);
116
+ }
117
+ const srcset = img.attr("srcset")?.trim();
118
+ if (srcset) {
119
+ img.attr("srcset", rewriteSrcset(srcset, options, uploadedBySourceId, referencedSources, unresolved));
120
+ }
121
+ });
122
+ $("[data-bg-image]").each((_, element) => {
123
+ const node = $(element);
124
+ const bgImage = node.attr("data-bg-image")?.trim();
125
+ if (!bgImage || bgImage.startsWith("data:")) return;
126
+ const replaced = tryRewriteUrl(bgImage, options, uploadedBySourceId, referencedSources, unresolved);
127
+ if (replaced) node.attr("data-bg-image", replaced);
128
+ });
129
+ $("[style]").each((_, element) => {
130
+ const node = $(element);
131
+ const style = node.attr("style");
132
+ if (!style?.includes("background")) return;
133
+ const rewritten = rewriteBackgroundUrlsInStyle(
134
+ style,
135
+ options,
136
+ uploadedBySourceId,
137
+ referencedSources,
138
+ unresolved
139
+ );
140
+ if (rewritten !== style) node.attr("style", rewritten);
141
+ });
142
+ return {
143
+ html: $.root().html() ?? html,
144
+ referencedSources: [...referencedSources],
145
+ unresolved: [...unresolved]
146
+ };
147
+ }
148
+ function stampMigrationMediaRefs(html, options) {
149
+ return rewriteInlineImages(
150
+ html,
151
+ {
152
+ resolveAsset: (src) => {
153
+ const sourceAssetId = resolveMigrationMediaSourceId(src, options.urlToSourceId);
154
+ if (!sourceAssetId) return void 0;
155
+ return { originalSrc: src, sourceAssetId };
156
+ },
157
+ replaceWith: options.replaceWith,
158
+ requireUploaded: options.requireUploaded ?? false
159
+ },
160
+ /* @__PURE__ */ new Map()
161
+ );
162
+ }
163
+
164
+ export {
165
+ MIGRATION_MEDIA_REF_SCHEME,
166
+ formatMigrationMediaRef,
167
+ isMigrationMediaRef,
168
+ parseMigrationMediaRef,
169
+ createMigrationMediaRefReplaceWith,
170
+ buildMigrationMediaUrlIndex,
171
+ resolveMigrationMediaSourceId,
172
+ rewriteInlineImages,
173
+ stampMigrationMediaRefs
174
+ };
175
+ //# sourceMappingURL=chunk-BOYB6XRA.js.map
@@ -0,0 +1 @@
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":[]}
@@ -6,11 +6,16 @@ import {
6
6
  sanitizeSlug,
7
7
  summarizeSquarespaceExport,
8
8
  validateSquarespaceExportFile
9
- } from "./chunk-YLFVYPB3.js";
9
+ } from "./chunk-MDSY3FEZ.js";
10
+ import {
11
+ buildMigrationMediaUrlIndex,
12
+ stampMigrationMediaRefs
13
+ } from "./chunk-BOYB6XRA.js";
10
14
  import {
11
15
  discoverContentAssetUrls,
12
- normalizeAssetUrl
13
- } from "./chunk-2PNSVE5Y.js";
16
+ normalizeAssetUrl,
17
+ resolveFeaturedContentAssetUrl
18
+ } from "./chunk-XYP3VYDH.js";
14
19
 
15
20
  // src/lib/origin-url-rewrite.ts
16
21
  function rewriteOriginUrlsInText(text, config) {
@@ -75,6 +80,20 @@ function extractQuotedParam(params, name) {
75
80
  function escapeLayoutAttr(value) {
76
81
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
77
82
  }
83
+ function parseFractionWidth(fraction) {
84
+ if (!fraction?.trim()) return void 0;
85
+ const trimmed = fraction.trim();
86
+ const match = trimmed.match(/^(\d+)\s*\/\s*(\d+)$/);
87
+ if (!match) return void 0;
88
+ const numerator = Number(match[1]);
89
+ const denominator = Number(match[2]);
90
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0) {
91
+ return void 0;
92
+ }
93
+ const percent = numerator / denominator * 100;
94
+ const rounded = Math.round(percent * 100) / 100;
95
+ return `${rounded % 1 === 0 ? rounded.toFixed(0) : rounded}%`;
96
+ }
78
97
  function parseRowLayoutCols(layout) {
79
98
  if (!layout?.trim()) return void 0;
80
99
  const parts = layout.split("+").map((part) => part.trim()).filter(Boolean);
@@ -94,6 +113,12 @@ function openRowDiv(params, colsParamName) {
94
113
  if (cols) attrs.push(`data-cols="${cols}"`);
95
114
  return `<div ${attrs.join(" ")}>`;
96
115
  }
116
+ function openColumnDiv(params, widthParamName) {
117
+ const attrs = ['data-layout="column"'];
118
+ const width = parseFractionWidth(extractQuotedParam(params, widthParamName ?? "width"));
119
+ if (width) attrs.push(`data-col-width="${width}"`);
120
+ return `<div ${attrs.join(" ")}>`;
121
+ }
97
122
  function applyPrefixedLayoutMap(content, map) {
98
123
  let html = content;
99
124
  html = html.replace(map.sectionRegex, (_, params) => openSectionDiv(params, map.bgParamName));
@@ -124,14 +149,49 @@ function applyFractionalLayoutMap(content, map) {
124
149
  }
125
150
  return html;
126
151
  }
152
+ function applyExtendedPrefixedLayoutMap(content, map) {
153
+ let html = content;
154
+ const levels = [...map.levels].sort((left, right) => {
155
+ const leftMax = Math.max(...left.tokens.map((token) => token.length));
156
+ const rightMax = Math.max(...right.tokens.map((token) => token.length));
157
+ return rightMax - leftMax;
158
+ });
159
+ for (const level of levels) {
160
+ const tokens = [...level.tokens].sort((left, right) => right.length - left.length);
161
+ for (const token of tokens) {
162
+ const openRegex = new RegExp(`\\[${escapeRegExp(token)}\\b([^\\]]*)\\]`, "gi");
163
+ const closeRegex = new RegExp(`\\[\\/${escapeRegExp(token)}\\b[^\\]]*\\]`, "gi");
164
+ html = html.replace(openRegex, (_, params) => {
165
+ switch (level.role) {
166
+ case "section":
167
+ return openSectionDiv(params, level.bgParamName);
168
+ case "row":
169
+ return openRowDiv(params, level.colsParamName);
170
+ case "column":
171
+ return openColumnDiv(params, level.widthParamName);
172
+ }
173
+ });
174
+ html = html.replace(closeRegex, "</div>");
175
+ }
176
+ }
177
+ return html;
178
+ }
127
179
  function applyStructuralLayoutMap(content, map) {
128
180
  switch (map.kind) {
129
181
  case "prefixed":
130
182
  return applyPrefixedLayoutMap(content, map);
131
183
  case "fractional":
132
184
  return applyFractionalLayoutMap(content, map);
185
+ case "extended-prefixed":
186
+ return applyExtendedPrefixedLayoutMap(content, map);
133
187
  }
134
188
  }
189
+ function collectLayoutMaps(theme) {
190
+ const maps = [];
191
+ if (theme.layoutMap) maps.push(theme.layoutMap);
192
+ if (theme.layoutMaps?.length) maps.push(...theme.layoutMaps);
193
+ return maps;
194
+ }
135
195
  function extractShortcodeParam(params, names) {
136
196
  for (const name of names) {
137
197
  const value = extractQuotedParam(params, name);
@@ -282,8 +342,8 @@ function flattenWordPressBuilders(content, options = {}) {
282
342
  for (const rule of theme.iconImageRules ?? []) {
283
343
  html = convertIconImageRule(html, rule);
284
344
  }
285
- if (theme.layoutMap) {
286
- html = applyStructuralLayoutMap(html, theme.layoutMap);
345
+ for (const layoutMap of collectLayoutMaps(theme)) {
346
+ html = applyStructuralLayoutMap(html, layoutMap);
287
347
  }
288
348
  for (const prefix of theme.scaffoldingPrefixes ?? []) {
289
349
  html = stripScaffoldingPrefix(html, prefix);
@@ -482,8 +542,8 @@ function resolveFeaturedAssetSourceId(thumbnailId, attachmentIndex, contentHtml)
482
542
  if (thumbnailId && attachmentIndex.has(thumbnailId)) {
483
543
  return thumbnailId;
484
544
  }
485
- const firstInline = discoverContentAssetUrls(contentHtml)[0];
486
- return firstInline ? `url:${firstInline}` : void 0;
545
+ const featuredUrl = resolveFeaturedContentAssetUrl(contentHtml);
546
+ return featuredUrl ? `url:${featuredUrl}` : void 0;
487
547
  }
488
548
  function maybeRewriteUrl(url, config) {
489
549
  if (!url) return void 0;
@@ -497,6 +557,12 @@ async function* enumerateWxrEntities(options) {
497
557
  const { categories, tags } = collectTaxonomies(items);
498
558
  const seenAssetUrls = /* @__PURE__ */ new Set();
499
559
  const emittedAttachmentIds = /* @__PURE__ */ new Set();
560
+ const attachmentUrlIndex = buildMigrationMediaUrlIndex(
561
+ [...attachmentIndex.entries()].map(([sourceId, entry]) => ({
562
+ sourceId,
563
+ sourceUrl: entry.sourceUrl
564
+ }))
565
+ );
500
566
  for (const category of categories.values()) {
501
567
  yield category;
502
568
  }
@@ -522,18 +588,28 @@ async function* enumerateWxrEntities(options) {
522
588
  const id = textValue(item.post_id);
523
589
  const link = maybeRewriteUrl(textValue(item.link), options.originUrlRewrite);
524
590
  const slug = sanitizeSlug(textValue(item.post_name) || textValue(item.title) || id);
525
- const contentHtml = preprocessContent(getContentEncoded(item), options);
591
+ let contentHtml = preprocessContent(getContentEncoded(item), options);
526
592
  if (postType === "page" && options.skipWooCommerceStubPages !== false && isWooCommerceStubPage(slug, contentHtml)) {
527
593
  continue;
528
594
  }
529
- for (const asset of collectInlineAssets(
595
+ const inlineAssets = collectInlineAssets(
530
596
  contentHtml,
531
597
  attachmentIndex,
532
598
  seenAssetUrls,
533
599
  options.exportedAt
534
- )) {
600
+ );
601
+ for (const asset of inlineAssets) {
535
602
  yield asset;
536
603
  }
604
+ if (options.stampMigrationMediaRefs !== false) {
605
+ const urlIndex = new Map(attachmentUrlIndex);
606
+ for (const asset of inlineAssets) {
607
+ urlIndex.set(asset.sourceUrl, asset.sourceId);
608
+ const normalized = normalizeAssetUrl(asset.sourceUrl);
609
+ if (normalized) urlIndex.set(normalized, asset.sourceId);
610
+ }
611
+ contentHtml = stampMigrationMediaRefs(contentHtml, { urlToSourceId: urlIndex }).html;
612
+ }
537
613
  const categorySlugs = [];
538
614
  const tagSlugs = [];
539
615
  for (const cat of asArray(item.category)) {
@@ -2621,4 +2697,4 @@ export {
2621
2697
  wixAdapter,
2622
2698
  getAdapter
2623
2699
  };
2624
- //# sourceMappingURL=chunk-XUBCG3IA.js.map
2700
+ //# sourceMappingURL=chunk-KF7G7DM6.js.map