@artinstack/migrator 0.1.0
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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/bundle-BfZqiKV_.d.ts +153 -0
- package/dist/chunk-2RWAXT6O.js +1 -0
- package/dist/chunk-2RWAXT6O.js.map +1 -0
- package/dist/chunk-FXXKLYO5.js +1076 -0
- package/dist/chunk-FXXKLYO5.js.map +1 -0
- package/dist/chunk-JKDRTL24.js +102 -0
- package/dist/chunk-JKDRTL24.js.map +1 -0
- package/dist/chunk-LKNIQQJO.js +1579 -0
- package/dist/chunk-LKNIQQJO.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +242 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index-DQNzrygx.d.ts +279 -0
- package/dist/index.d.ts +336 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/normalizer/index.d.ts +23 -0
- package/dist/normalizer/index.js +20 -0
- package/dist/normalizer/index.js.map +1 -0
- package/dist/sinks/index.d.ts +3 -0
- package/dist/sinks/index.js +46 -0
- package/dist/sinks/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,1579 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPortfolioMediaLinks,
|
|
3
|
+
bundleCounts,
|
|
4
|
+
collectEntities,
|
|
5
|
+
emptyBundle,
|
|
6
|
+
entityKey,
|
|
7
|
+
shouldProcessEntity
|
|
8
|
+
} from "./chunk-JKDRTL24.js";
|
|
9
|
+
|
|
10
|
+
// src/sinks/types.ts
|
|
11
|
+
var MIGRATION_WRITE_STAGES = [
|
|
12
|
+
"taxonomy",
|
|
13
|
+
"assets",
|
|
14
|
+
"portfolios",
|
|
15
|
+
"content",
|
|
16
|
+
"bindings",
|
|
17
|
+
"redirects"
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// src/sinks/run-migration.ts
|
|
21
|
+
import { Readable } from "stream";
|
|
22
|
+
|
|
23
|
+
// src/lib/content-asset-urls.ts
|
|
24
|
+
import * as cheerio from "cheerio";
|
|
25
|
+
var ASSET_URL_PARAM_PATTERN = /\b(?:src|image|url)\s*=\s*["']([^"']+)["']/gi;
|
|
26
|
+
var IMAGE_EXTENSION_PATTERN = /\.(?:jpe?g|png|gif|webp|avif|svg)(?:[?#]|$)/i;
|
|
27
|
+
var WP_UPLOADS_PATTERN = /\/wp-content\/uploads\//i;
|
|
28
|
+
function extractImgTagSrcs(content) {
|
|
29
|
+
if (!content.trim()) return [];
|
|
30
|
+
const $ = cheerio.load(content, { xml: false });
|
|
31
|
+
const srcs = [];
|
|
32
|
+
$("img[src]").each((_, el) => {
|
|
33
|
+
const src = $(el).attr("src")?.trim();
|
|
34
|
+
if (src) srcs.push(src);
|
|
35
|
+
});
|
|
36
|
+
return srcs;
|
|
37
|
+
}
|
|
38
|
+
function discoverRawImgSrcs(content) {
|
|
39
|
+
return extractImgTagSrcs(content).filter((src) => !src.startsWith("data:"));
|
|
40
|
+
}
|
|
41
|
+
function normalizeAssetUrl(raw) {
|
|
42
|
+
const trimmed = raw.trim();
|
|
43
|
+
if (!trimmed || trimmed.startsWith("data:")) return void 0;
|
|
44
|
+
if (trimmed.startsWith("//")) return `https:${trimmed}`;
|
|
45
|
+
return trimmed;
|
|
46
|
+
}
|
|
47
|
+
function isLikelyImageUrl(url) {
|
|
48
|
+
if (!url || url.startsWith("data:")) return false;
|
|
49
|
+
if (url.startsWith("/")) {
|
|
50
|
+
return WP_UPLOADS_PATTERN.test(url) || IMAGE_EXTENSION_PATTERN.test(url);
|
|
51
|
+
}
|
|
52
|
+
if (!/^https?:\/\//i.test(url)) return false;
|
|
53
|
+
if (WP_UPLOADS_PATTERN.test(url)) return true;
|
|
54
|
+
try {
|
|
55
|
+
const pathname = new URL(url).pathname;
|
|
56
|
+
return IMAGE_EXTENSION_PATTERN.test(pathname);
|
|
57
|
+
} catch {
|
|
58
|
+
return IMAGE_EXTENSION_PATTERN.test(url);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function discoverContentAssetUrls(content) {
|
|
62
|
+
if (!content.trim()) return [];
|
|
63
|
+
const urls = /* @__PURE__ */ new Set();
|
|
64
|
+
for (const raw of extractImgTagSrcs(content)) {
|
|
65
|
+
const normalized = normalizeAssetUrl(raw);
|
|
66
|
+
if (normalized && isLikelyImageUrl(normalized)) {
|
|
67
|
+
urls.add(normalized);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const match of content.matchAll(ASSET_URL_PARAM_PATTERN)) {
|
|
71
|
+
const normalized = normalizeAssetUrl(match[1] ?? "");
|
|
72
|
+
if (normalized && isLikelyImageUrl(normalized)) {
|
|
73
|
+
urls.add(normalized);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [...urls];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/transformers/rewrite-inline-images.ts
|
|
80
|
+
import * as cheerio2 from "cheerio";
|
|
81
|
+
function rewriteSrcset(srcset, options, uploadedBySourceId, referencedSources, unresolved) {
|
|
82
|
+
return srcset.split(",").map((entry) => {
|
|
83
|
+
const trimmed = entry.trim();
|
|
84
|
+
if (!trimmed) return entry;
|
|
85
|
+
const [urlPart, descriptor] = trimmed.split(/\s+/, 2);
|
|
86
|
+
const normalized = normalizeAssetUrl(urlPart ?? "");
|
|
87
|
+
if (!normalized) return entry;
|
|
88
|
+
referencedSources.add(normalized);
|
|
89
|
+
const ref = options.resolveAsset(normalized);
|
|
90
|
+
if (!ref?.sourceAssetId) {
|
|
91
|
+
unresolved.add(normalized);
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
const uploaded = uploadedBySourceId.get(ref.sourceAssetId);
|
|
95
|
+
if (!uploaded) {
|
|
96
|
+
unresolved.add(normalized);
|
|
97
|
+
return entry;
|
|
98
|
+
}
|
|
99
|
+
const replaced = options.replaceWith(ref, uploaded);
|
|
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 $ = cheerio2.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 normalized = normalizeAssetUrl(src);
|
|
115
|
+
if (normalized) {
|
|
116
|
+
referencedSources.add(normalized);
|
|
117
|
+
const ref = options.resolveAsset(normalized);
|
|
118
|
+
if (!ref?.sourceAssetId) {
|
|
119
|
+
unresolved.add(normalized);
|
|
120
|
+
} else {
|
|
121
|
+
const uploaded = uploadedBySourceId.get(ref.sourceAssetId);
|
|
122
|
+
if (!uploaded) {
|
|
123
|
+
unresolved.add(normalized);
|
|
124
|
+
} else {
|
|
125
|
+
img.attr("src", options.replaceWith(ref, uploaded));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const srcset = img.attr("srcset")?.trim();
|
|
131
|
+
if (srcset) {
|
|
132
|
+
img.attr("srcset", rewriteSrcset(srcset, options, uploadedBySourceId, referencedSources, unresolved));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
html: $.root().html() ?? html,
|
|
137
|
+
referencedSources: [...referencedSources],
|
|
138
|
+
unresolved: [...unresolved]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/sinks/conflicts.ts
|
|
143
|
+
import * as cheerio4 from "cheerio";
|
|
144
|
+
|
|
145
|
+
// src/parsers/squarespace/parse-export.ts
|
|
146
|
+
import { readFile } from "fs/promises";
|
|
147
|
+
import { basename } from "path";
|
|
148
|
+
|
|
149
|
+
// src/lib/utility.ts
|
|
150
|
+
function sanitizeSlug(raw) {
|
|
151
|
+
return raw.trim().toLowerCase().replace(/&[^;]+;/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 200);
|
|
152
|
+
}
|
|
153
|
+
function linkToPath(link) {
|
|
154
|
+
if (!link) return void 0;
|
|
155
|
+
try {
|
|
156
|
+
const url = new URL(link);
|
|
157
|
+
const path = url.pathname;
|
|
158
|
+
if (!path || path === "/") return "/";
|
|
159
|
+
return path.endsWith("/") ? path : `${path}/`;
|
|
160
|
+
} catch {
|
|
161
|
+
if (link.startsWith("/")) {
|
|
162
|
+
return link.endsWith("/") ? link : `${link}/`;
|
|
163
|
+
}
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/parsers/squarespace/collect.ts
|
|
169
|
+
import * as cheerio3 from "cheerio";
|
|
170
|
+
import { z } from "zod";
|
|
171
|
+
var SQUARESPACE_JSON_FORMAT = "json-pretty";
|
|
172
|
+
var squarespaceClientOptionsSchema = z.object({
|
|
173
|
+
format: z.enum(["json", "json-pretty"]).default("json-pretty"),
|
|
174
|
+
maxRetries: z.number().int().min(0).max(10).default(3),
|
|
175
|
+
retryBaseDelayMs: z.number().int().min(0).default(500),
|
|
176
|
+
maxRetryDelayMs: z.number().int().min(0).default(8e3),
|
|
177
|
+
requestIntervalMs: z.number().int().min(0).default(200),
|
|
178
|
+
fetchImpl: z.custom().optional()
|
|
179
|
+
});
|
|
180
|
+
function isRecord(value) {
|
|
181
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
182
|
+
}
|
|
183
|
+
function asRecord(value) {
|
|
184
|
+
return isRecord(value) ? value : void 0;
|
|
185
|
+
}
|
|
186
|
+
function asStringArray(value) {
|
|
187
|
+
if (!Array.isArray(value)) return [];
|
|
188
|
+
return value.filter((entry) => typeof entry === "string");
|
|
189
|
+
}
|
|
190
|
+
function sleep(ms) {
|
|
191
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
192
|
+
}
|
|
193
|
+
function buildJsonPrettyUrl(pageUrl, format = SQUARESPACE_JSON_FORMAT) {
|
|
194
|
+
const url = new URL(pageUrl);
|
|
195
|
+
url.searchParams.set("format", format);
|
|
196
|
+
return url.toString();
|
|
197
|
+
}
|
|
198
|
+
function mapWorkflowState(state) {
|
|
199
|
+
if (state === 1 || state === "1") return "published";
|
|
200
|
+
if (state === 2 || state === "2") return "draft";
|
|
201
|
+
return "published";
|
|
202
|
+
}
|
|
203
|
+
function mapPublishOn(value) {
|
|
204
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
205
|
+
return new Date(value).toISOString();
|
|
206
|
+
}
|
|
207
|
+
function readSeo(seoData, field) {
|
|
208
|
+
const record = asRecord(seoData);
|
|
209
|
+
if (!record) return void 0;
|
|
210
|
+
const value = record[field];
|
|
211
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
212
|
+
}
|
|
213
|
+
function inferBlockTypeFromClassName(className) {
|
|
214
|
+
const match = className.match(/\bsqs-block-([a-z0-9-]+)\b/i);
|
|
215
|
+
if (!match?.[1]) return "html";
|
|
216
|
+
const raw = match[1].toLowerCase();
|
|
217
|
+
if (raw === "horizontalrule") return "line";
|
|
218
|
+
return raw.replace(/-block$/g, "");
|
|
219
|
+
}
|
|
220
|
+
function extractBlocksFromBodyHtml(html) {
|
|
221
|
+
if (!html.trim()) return [];
|
|
222
|
+
const $ = cheerio3.load(html, { xml: false });
|
|
223
|
+
const blocks = [];
|
|
224
|
+
$(".sqs-block").each((_, element) => {
|
|
225
|
+
const el = $(element);
|
|
226
|
+
const className = el.attr("class") ?? "";
|
|
227
|
+
const type = inferBlockTypeFromClassName(className);
|
|
228
|
+
const id = el.attr("id")?.replace(/^block-/, "");
|
|
229
|
+
const content = el.find(".sqs-block-content").first();
|
|
230
|
+
const innerHtml = (content.length ? content.html() : el.html()) ?? "";
|
|
231
|
+
if (type === "image") {
|
|
232
|
+
const img = content.find("img").first();
|
|
233
|
+
const imageUrl = img.attr("data-src") ?? img.attr("src") ?? void 0;
|
|
234
|
+
blocks.push({
|
|
235
|
+
id,
|
|
236
|
+
type,
|
|
237
|
+
imageUrl,
|
|
238
|
+
altText: img.attr("alt") ?? void 0,
|
|
239
|
+
caption: content.find("figcaption").text() || void 0,
|
|
240
|
+
html: innerHtml || void 0
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (type === "gallery") {
|
|
245
|
+
const items = [];
|
|
246
|
+
content.find("img").each((idx, imgEl) => {
|
|
247
|
+
const img = $(imgEl);
|
|
248
|
+
const imageUrl = img.attr("data-src") ?? img.attr("src");
|
|
249
|
+
if (!imageUrl) return;
|
|
250
|
+
items.push({
|
|
251
|
+
id: img.attr("data-image-id") ?? `gallery-${idx}`,
|
|
252
|
+
imageUrl,
|
|
253
|
+
altText: img.attr("alt") ?? void 0
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
blocks.push({ id, type, items, html: innerHtml || void 0 });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (type === "button") {
|
|
260
|
+
const anchor = content.find("a").first();
|
|
261
|
+
blocks.push({
|
|
262
|
+
id,
|
|
263
|
+
type,
|
|
264
|
+
url: anchor.attr("href") ?? void 0,
|
|
265
|
+
label: anchor.text() || void 0,
|
|
266
|
+
html: innerHtml || void 0
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (type === "video" || type === "embed") {
|
|
271
|
+
const iframe = content.find("iframe").first();
|
|
272
|
+
blocks.push({
|
|
273
|
+
id,
|
|
274
|
+
type: type === "embed" ? "embed" : "video",
|
|
275
|
+
embedHtml: iframe.length ? $.html(iframe) : innerHtml || void 0,
|
|
276
|
+
html: innerHtml || void 0
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
blocks.push({
|
|
281
|
+
id,
|
|
282
|
+
type,
|
|
283
|
+
html: innerHtml || void 0,
|
|
284
|
+
value: type === "markdown" || type === "quote" ? content.text() : void 0
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
return blocks;
|
|
288
|
+
}
|
|
289
|
+
function mapWireBlocks(value) {
|
|
290
|
+
if (!Array.isArray(value)) return void 0;
|
|
291
|
+
const blocks = [];
|
|
292
|
+
for (const entry of value) {
|
|
293
|
+
const record = asRecord(entry);
|
|
294
|
+
if (!record) continue;
|
|
295
|
+
const type = String(record.type ?? record.blockType ?? "html");
|
|
296
|
+
blocks.push({
|
|
297
|
+
id: record.id ? String(record.id) : void 0,
|
|
298
|
+
type,
|
|
299
|
+
html: typeof record.html === "string" ? record.html : void 0,
|
|
300
|
+
value: typeof record.value === "string" ? record.value : void 0,
|
|
301
|
+
imageUrl: typeof record.imageUrl === "string" ? record.imageUrl : typeof record.assetUrl === "string" ? record.assetUrl : void 0,
|
|
302
|
+
altText: typeof record.altText === "string" ? record.altText : void 0,
|
|
303
|
+
caption: typeof record.caption === "string" ? record.caption : void 0,
|
|
304
|
+
url: typeof record.url === "string" ? record.url : void 0,
|
|
305
|
+
label: typeof record.label === "string" ? record.label : void 0,
|
|
306
|
+
embedHtml: typeof record.embedHtml === "string" ? record.embedHtml : void 0,
|
|
307
|
+
items: Array.isArray(record.items) ? record.items.map((item) => asRecord(item)).filter((item) => !!item).map((item) => ({
|
|
308
|
+
id: item.id ? String(item.id) : void 0,
|
|
309
|
+
imageUrl: String(item.imageUrl ?? item.assetUrl ?? ""),
|
|
310
|
+
altText: typeof item.altText === "string" ? item.altText : void 0,
|
|
311
|
+
caption: typeof item.caption === "string" ? item.caption : void 0
|
|
312
|
+
})).filter((item) => item.imageUrl.length > 0) : void 0
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return blocks.length > 0 ? blocks : void 0;
|
|
316
|
+
}
|
|
317
|
+
function mapStructuredBlocksFromItem(item) {
|
|
318
|
+
const direct = mapWireBlocks(item.blocks);
|
|
319
|
+
if (direct?.length) return direct;
|
|
320
|
+
const sections = item.sections;
|
|
321
|
+
if (!Array.isArray(sections)) return void 0;
|
|
322
|
+
const flattened = [];
|
|
323
|
+
for (const section of sections) {
|
|
324
|
+
const sectionRecord = asRecord(section);
|
|
325
|
+
if (!sectionRecord) continue;
|
|
326
|
+
const rows = sectionRecord.rows;
|
|
327
|
+
if (!Array.isArray(rows)) continue;
|
|
328
|
+
for (const row of rows) {
|
|
329
|
+
const rowRecord = asRecord(row);
|
|
330
|
+
if (!rowRecord) continue;
|
|
331
|
+
const columns = rowRecord.columns;
|
|
332
|
+
if (!Array.isArray(columns)) continue;
|
|
333
|
+
for (const column of columns) {
|
|
334
|
+
const columnRecord = asRecord(column);
|
|
335
|
+
if (!columnRecord) continue;
|
|
336
|
+
const blocks = mapWireBlocks(columnRecord.blocks);
|
|
337
|
+
if (blocks) flattened.push(...blocks);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return flattened.length > 0 ? flattened : void 0;
|
|
342
|
+
}
|
|
343
|
+
function mapWireItemContent(item) {
|
|
344
|
+
const structured = mapStructuredBlocksFromItem(item);
|
|
345
|
+
if (structured?.length) {
|
|
346
|
+
return { blocks: structured };
|
|
347
|
+
}
|
|
348
|
+
const body = typeof item.body === "string" ? item.body : void 0;
|
|
349
|
+
if (!body) return { contentHtml: "" };
|
|
350
|
+
const parsedBlocks = extractBlocksFromBodyHtml(body);
|
|
351
|
+
if (parsedBlocks.length > 0) {
|
|
352
|
+
return { blocks: parsedBlocks };
|
|
353
|
+
}
|
|
354
|
+
return { contentHtml: body };
|
|
355
|
+
}
|
|
356
|
+
function isBlogCollection(collection) {
|
|
357
|
+
if (!collection) return false;
|
|
358
|
+
const ordering = String(collection.ordering ?? "").toLowerCase();
|
|
359
|
+
if (ordering === "chronological" || ordering === "calendar") return true;
|
|
360
|
+
const typeName = String(collection.typeName ?? collection.typeLabel ?? "").toLowerCase();
|
|
361
|
+
return typeName.includes("blog");
|
|
362
|
+
}
|
|
363
|
+
function isStaticPageItem(item, collection) {
|
|
364
|
+
const recordTypeLabel = String(item.recordTypeLabel ?? "").toLowerCase();
|
|
365
|
+
if (recordTypeLabel.includes("page")) return true;
|
|
366
|
+
const collectionType = String(collection?.typeName ?? collection?.typeLabel ?? "").toLowerCase();
|
|
367
|
+
return collectionType === "page" || collectionType.includes("page-collection");
|
|
368
|
+
}
|
|
369
|
+
function mapWireItemToPost(item) {
|
|
370
|
+
const content = mapWireItemContent(item);
|
|
371
|
+
return {
|
|
372
|
+
id: String(item.id ?? item.systemDataId ?? item.urlId ?? ""),
|
|
373
|
+
title: String(item.title ?? "Untitled"),
|
|
374
|
+
slug: sanitizeSlug(String(item.urlId ?? item.title ?? item.id ?? "post")),
|
|
375
|
+
url: typeof item.fullUrl === "string" ? item.fullUrl : void 0,
|
|
376
|
+
excerpt: typeof item.excerpt === "string" ? item.excerpt : void 0,
|
|
377
|
+
publishedAt: mapPublishOn(item.publishOn ?? item.addedOn),
|
|
378
|
+
status: mapWorkflowState(item.workflowState),
|
|
379
|
+
categorySlugs: asStringArray(item.categories).map((slug) => sanitizeSlug(slug)),
|
|
380
|
+
tagSlugs: asStringArray(item.tags).map((slug) => sanitizeSlug(slug)),
|
|
381
|
+
featuredImageUrl: typeof item.assetUrl === "string" ? item.assetUrl : void 0,
|
|
382
|
+
seoTitle: readSeo(item.seoData, "title"),
|
|
383
|
+
seoDescription: readSeo(item.seoData, "description"),
|
|
384
|
+
...content
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function mapWireItemToPage(item, options) {
|
|
388
|
+
const content = mapWireItemContent(item);
|
|
389
|
+
return {
|
|
390
|
+
id: String(item.id ?? item.urlId ?? ""),
|
|
391
|
+
title: String(item.title ?? "Untitled"),
|
|
392
|
+
slug: sanitizeSlug(String(item.urlId ?? item.title ?? item.id ?? "page")),
|
|
393
|
+
url: typeof item.fullUrl === "string" ? item.fullUrl : options?.fallbackUrl,
|
|
394
|
+
status: mapWorkflowState(item.workflowState),
|
|
395
|
+
isHomePage: options?.isHomePage,
|
|
396
|
+
seoTitle: readSeo(item.seoData, "title"),
|
|
397
|
+
seoDescription: readSeo(item.seoData, "description"),
|
|
398
|
+
...content
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function mapWireCategories(collection) {
|
|
402
|
+
if (!collection) return [];
|
|
403
|
+
return asStringArray(collection.categories).map((name) => ({
|
|
404
|
+
id: `cat-${sanitizeSlug(name)}`,
|
|
405
|
+
name,
|
|
406
|
+
slug: sanitizeSlug(name)
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
function mapWireTags(items) {
|
|
410
|
+
const seen = /* @__PURE__ */ new Map();
|
|
411
|
+
for (const item of items) {
|
|
412
|
+
for (const tag of asStringArray(item.tags)) {
|
|
413
|
+
const slug = sanitizeSlug(tag);
|
|
414
|
+
if (!seen.has(slug)) {
|
|
415
|
+
seen.set(slug, { id: `tag-${slug}`, name: tag, slug });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return [...seen.values()];
|
|
420
|
+
}
|
|
421
|
+
function siteFromWire(wire) {
|
|
422
|
+
const website = asRecord(wire.website);
|
|
423
|
+
if (!website) return void 0;
|
|
424
|
+
const url = typeof website.authenticUrl === "string" ? website.authenticUrl : typeof website.baseUrl === "string" ? website.baseUrl : void 0;
|
|
425
|
+
const title = typeof website.siteTitle === "string" ? website.siteTitle : void 0;
|
|
426
|
+
if (!url && !title) return void 0;
|
|
427
|
+
return { url, title };
|
|
428
|
+
}
|
|
429
|
+
function mapJsonPrettyWire(wire, context) {
|
|
430
|
+
if (!isRecord(wire)) {
|
|
431
|
+
throw new Error("Invalid Squarespace json-pretty response");
|
|
432
|
+
}
|
|
433
|
+
const collection = asRecord(wire.collection);
|
|
434
|
+
const partial = {
|
|
435
|
+
site: siteFromWire(wire),
|
|
436
|
+
pages: [],
|
|
437
|
+
posts: [],
|
|
438
|
+
categories: mapWireCategories(collection),
|
|
439
|
+
tags: []
|
|
440
|
+
};
|
|
441
|
+
if (Array.isArray(wire.items)) {
|
|
442
|
+
const itemRecords = wire.items.map((entry) => asRecord(entry)).filter((entry) => !!entry);
|
|
443
|
+
partial.tags = mapWireTags(itemRecords);
|
|
444
|
+
for (const item2 of itemRecords) {
|
|
445
|
+
if (isStaticPageItem(item2, collection)) {
|
|
446
|
+
partial.pages.push(
|
|
447
|
+
mapWireItemToPage(item2, { fallbackUrl: context?.fetchedUrl, isHomePage: context?.isHomePage })
|
|
448
|
+
);
|
|
449
|
+
} else {
|
|
450
|
+
partial.posts.push(mapWireItemToPost(item2));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return partial;
|
|
454
|
+
}
|
|
455
|
+
const item = asRecord(wire.item);
|
|
456
|
+
if (item) {
|
|
457
|
+
if (isStaticPageItem(item, collection)) {
|
|
458
|
+
partial.pages.push(
|
|
459
|
+
mapWireItemToPage(item, { fallbackUrl: context?.fetchedUrl, isHomePage: context?.isHomePage })
|
|
460
|
+
);
|
|
461
|
+
} else {
|
|
462
|
+
partial.posts.push(mapWireItemToPost(item));
|
|
463
|
+
partial.tags = mapWireTags([item]);
|
|
464
|
+
}
|
|
465
|
+
return partial;
|
|
466
|
+
}
|
|
467
|
+
if (collection && isBlogCollection(collection) === false) {
|
|
468
|
+
partial.pages.push(
|
|
469
|
+
mapWireItemToPage(
|
|
470
|
+
{
|
|
471
|
+
id: collection.id ?? collection.urlId ?? collection.fullUrl,
|
|
472
|
+
title: collection.title ?? collection.navigationTitle,
|
|
473
|
+
urlId: collection.urlId,
|
|
474
|
+
fullUrl: collection.fullUrl ?? context?.fetchedUrl,
|
|
475
|
+
body: collection.description,
|
|
476
|
+
workflowState: collection.draft ? 2 : 1,
|
|
477
|
+
seoData: collection.seoData
|
|
478
|
+
},
|
|
479
|
+
{ fallbackUrl: context?.fetchedUrl, isHomePage: context?.isHomePage }
|
|
480
|
+
)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return partial;
|
|
484
|
+
}
|
|
485
|
+
function dedupeById(items) {
|
|
486
|
+
const seen = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const item of items) {
|
|
488
|
+
seen.set(item.id, item);
|
|
489
|
+
}
|
|
490
|
+
return [...seen.values()];
|
|
491
|
+
}
|
|
492
|
+
function dedupeBySlug(items) {
|
|
493
|
+
const seen = /* @__PURE__ */ new Map();
|
|
494
|
+
for (const item of items) {
|
|
495
|
+
seen.set(item.slug, item);
|
|
496
|
+
}
|
|
497
|
+
return [...seen.values()];
|
|
498
|
+
}
|
|
499
|
+
function mergeSquarespaceExportPartials(partials) {
|
|
500
|
+
const merged = {
|
|
501
|
+
exportVersion: 1,
|
|
502
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
503
|
+
pages: [],
|
|
504
|
+
posts: [],
|
|
505
|
+
categories: [],
|
|
506
|
+
tags: []
|
|
507
|
+
};
|
|
508
|
+
for (const partial of partials) {
|
|
509
|
+
if (partial.site) merged.site = { ...merged.site, ...partial.site };
|
|
510
|
+
merged.pages.push(...partial.pages ?? []);
|
|
511
|
+
merged.posts.push(...partial.posts ?? []);
|
|
512
|
+
merged.categories.push(...partial.categories ?? []);
|
|
513
|
+
merged.tags.push(...partial.tags ?? []);
|
|
514
|
+
}
|
|
515
|
+
merged.pages = dedupeById(merged.pages);
|
|
516
|
+
merged.posts = dedupeById(merged.posts ?? []);
|
|
517
|
+
merged.categories = dedupeBySlug(merged.categories ?? []);
|
|
518
|
+
merged.tags = dedupeBySlug(merged.tags ?? []);
|
|
519
|
+
return merged;
|
|
520
|
+
}
|
|
521
|
+
function paginationFromWire(wire) {
|
|
522
|
+
return asRecord(wire)?.pagination;
|
|
523
|
+
}
|
|
524
|
+
function inferTargetKind(target) {
|
|
525
|
+
if (target.kind && target.kind !== "auto") return target.kind;
|
|
526
|
+
try {
|
|
527
|
+
const pathname = new URL(target.url).pathname;
|
|
528
|
+
if (pathname === "/" || pathname.endsWith("/")) return "collection";
|
|
529
|
+
} catch {
|
|
530
|
+
return "page";
|
|
531
|
+
}
|
|
532
|
+
return "page";
|
|
533
|
+
}
|
|
534
|
+
var SquarespaceCollectionClient = class {
|
|
535
|
+
format;
|
|
536
|
+
maxRetries;
|
|
537
|
+
retryBaseDelayMs;
|
|
538
|
+
maxRetryDelayMs;
|
|
539
|
+
requestIntervalMs;
|
|
540
|
+
fetchImpl;
|
|
541
|
+
lastRequestAt = 0;
|
|
542
|
+
constructor(options = {}) {
|
|
543
|
+
const parsed = squarespaceClientOptionsSchema.parse(options);
|
|
544
|
+
this.format = parsed.format;
|
|
545
|
+
this.maxRetries = parsed.maxRetries;
|
|
546
|
+
this.retryBaseDelayMs = parsed.retryBaseDelayMs;
|
|
547
|
+
this.maxRetryDelayMs = parsed.maxRetryDelayMs;
|
|
548
|
+
this.requestIntervalMs = parsed.requestIntervalMs;
|
|
549
|
+
this.fetchImpl = parsed.fetchImpl ?? fetch;
|
|
550
|
+
}
|
|
551
|
+
buildJsonPrettyUrl(pageUrl) {
|
|
552
|
+
return buildJsonPrettyUrl(pageUrl, this.format);
|
|
553
|
+
}
|
|
554
|
+
async fetchWire(url) {
|
|
555
|
+
const response = await this.requestWithRetry(this.buildJsonPrettyUrl(url));
|
|
556
|
+
return response.json();
|
|
557
|
+
}
|
|
558
|
+
async collectExport(targets) {
|
|
559
|
+
if (targets.length === 0) {
|
|
560
|
+
throw new Error("Squarespace collector requires at least one target URL");
|
|
561
|
+
}
|
|
562
|
+
const partials = [];
|
|
563
|
+
for (const target of targets) {
|
|
564
|
+
const kind = inferTargetKind(target);
|
|
565
|
+
if (kind === "collection") {
|
|
566
|
+
partials.push(...await this.collectCollectionPages(target));
|
|
567
|
+
} else {
|
|
568
|
+
const wire = await this.fetchWire(target.url);
|
|
569
|
+
partials.push(
|
|
570
|
+
mapJsonPrettyWire(wire, { fetchedUrl: target.url, isHomePage: target.isHomePage })
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return mergeSquarespaceExportPartials(partials);
|
|
575
|
+
}
|
|
576
|
+
async collectCollectionPages(target) {
|
|
577
|
+
const partials = [];
|
|
578
|
+
let nextUrl = target.url;
|
|
579
|
+
while (nextUrl) {
|
|
580
|
+
const wire = await this.fetchWire(nextUrl);
|
|
581
|
+
partials.push(
|
|
582
|
+
mapJsonPrettyWire(wire, { fetchedUrl: nextUrl, isHomePage: target.isHomePage })
|
|
583
|
+
);
|
|
584
|
+
const pagination = paginationFromWire(wire);
|
|
585
|
+
if (!pagination?.nextPage || !pagination.nextPageUrl) break;
|
|
586
|
+
const base = new URL(target.url);
|
|
587
|
+
nextUrl = new URL(pagination.nextPageUrl, `${base.origin}/`).toString();
|
|
588
|
+
}
|
|
589
|
+
return partials;
|
|
590
|
+
}
|
|
591
|
+
async requestWithRetry(url) {
|
|
592
|
+
let attempt = 0;
|
|
593
|
+
while (true) {
|
|
594
|
+
await this.throttle();
|
|
595
|
+
const response = await this.fetchImpl(url, {
|
|
596
|
+
method: "GET",
|
|
597
|
+
headers: { Accept: "application/json" }
|
|
598
|
+
});
|
|
599
|
+
if (response.ok) {
|
|
600
|
+
return response;
|
|
601
|
+
}
|
|
602
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
603
|
+
if (!retryable || attempt >= this.maxRetries) {
|
|
604
|
+
const detail = await response.text().catch(() => "");
|
|
605
|
+
throw new Error(
|
|
606
|
+
`Squarespace HTTP ${response.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
const retryAfter = Number.parseInt(response.headers.get("retry-after") ?? "", 10);
|
|
610
|
+
const delay = Number.isFinite(retryAfter) ? retryAfter * 1e3 : Math.min(this.maxRetryDelayMs, this.retryBaseDelayMs * 2 ** attempt);
|
|
611
|
+
await sleep(delay);
|
|
612
|
+
attempt += 1;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async throttle() {
|
|
616
|
+
if (this.requestIntervalMs <= 0) return;
|
|
617
|
+
const elapsed = Date.now() - this.lastRequestAt;
|
|
618
|
+
if (elapsed < this.requestIntervalMs) {
|
|
619
|
+
await sleep(this.requestIntervalMs - elapsed);
|
|
620
|
+
}
|
|
621
|
+
this.lastRequestAt = Date.now();
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// src/parsers/squarespace/parse-export.ts
|
|
626
|
+
var PLATFORM = "squarespace";
|
|
627
|
+
var UNSUPPORTED_ATTR = "data-artinstack-unsupported-block";
|
|
628
|
+
var BLOCK_ID_ATTR = "data-artinstack-block-id";
|
|
629
|
+
var SUPPORTED_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
630
|
+
"html",
|
|
631
|
+
"text",
|
|
632
|
+
"markdown",
|
|
633
|
+
"image",
|
|
634
|
+
"gallery",
|
|
635
|
+
"quote",
|
|
636
|
+
"button",
|
|
637
|
+
"video",
|
|
638
|
+
"code",
|
|
639
|
+
"spacer",
|
|
640
|
+
"line",
|
|
641
|
+
"horizontalrule",
|
|
642
|
+
"hr"
|
|
643
|
+
]);
|
|
644
|
+
var UNSUPPORTED_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
645
|
+
"product",
|
|
646
|
+
"products",
|
|
647
|
+
"form",
|
|
648
|
+
"newsletter",
|
|
649
|
+
"donation",
|
|
650
|
+
"calendar",
|
|
651
|
+
"chart",
|
|
652
|
+
"map",
|
|
653
|
+
"music",
|
|
654
|
+
"social",
|
|
655
|
+
"summary",
|
|
656
|
+
"archive",
|
|
657
|
+
"acuity",
|
|
658
|
+
"member-area",
|
|
659
|
+
"digital-product",
|
|
660
|
+
"folder",
|
|
661
|
+
"index",
|
|
662
|
+
"tock",
|
|
663
|
+
"opentable",
|
|
664
|
+
"soundcloud",
|
|
665
|
+
"foursquare"
|
|
666
|
+
]);
|
|
667
|
+
function sourceMeta(id, url, exportedAt) {
|
|
668
|
+
return {
|
|
669
|
+
platform: PLATFORM,
|
|
670
|
+
id,
|
|
671
|
+
url,
|
|
672
|
+
path: linkToPath(url),
|
|
673
|
+
exportedAt
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function mapPublishStatus(status) {
|
|
677
|
+
switch ((status ?? "published").toLowerCase()) {
|
|
678
|
+
case "published":
|
|
679
|
+
return "published";
|
|
680
|
+
case "draft":
|
|
681
|
+
case "scheduled":
|
|
682
|
+
return "draft";
|
|
683
|
+
default:
|
|
684
|
+
return "archived";
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function escapeHtml(text) {
|
|
688
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
689
|
+
}
|
|
690
|
+
function blockShell(type, inner, blockId) {
|
|
691
|
+
const idAttr = blockId ? ` id="sqs-block-${blockId}"` : "";
|
|
692
|
+
return `<div class="sqs-block sqs-block-${type}"${idAttr}>${inner}</div>`;
|
|
693
|
+
}
|
|
694
|
+
function unsupportedPlaceholder(type, blockId) {
|
|
695
|
+
const idPart = blockId ? ` ${BLOCK_ID_ATTR}="${escapeHtml(blockId)}"` : "";
|
|
696
|
+
return `<div class="sqs-block sqs-block-unsupported" ${UNSUPPORTED_ATTR}="${escapeHtml(type)}"${idPart} aria-hidden="true"></div>`;
|
|
697
|
+
}
|
|
698
|
+
function flattenGalleryItem(item) {
|
|
699
|
+
const alt = item.altText ? ` alt="${escapeHtml(item.altText)}"` : "";
|
|
700
|
+
const caption = item.caption ? `<figcaption>${item.caption}</figcaption>` : "";
|
|
701
|
+
return `<figure><img src="${escapeHtml(item.imageUrl)}"${alt} />${caption}</figure>`;
|
|
702
|
+
}
|
|
703
|
+
function flattenSquarespaceBlock(block) {
|
|
704
|
+
const type = block.type.toLowerCase();
|
|
705
|
+
const blockId = block.id;
|
|
706
|
+
const assetUrls = [];
|
|
707
|
+
if (UNSUPPORTED_BLOCK_TYPES.has(type)) {
|
|
708
|
+
return {
|
|
709
|
+
contentHtml: unsupportedPlaceholder(type, blockId),
|
|
710
|
+
assetUrls
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (!SUPPORTED_BLOCK_TYPES.has(type)) {
|
|
714
|
+
return {
|
|
715
|
+
contentHtml: unsupportedPlaceholder(type || "unknown", blockId),
|
|
716
|
+
assetUrls
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
switch (type) {
|
|
720
|
+
case "html":
|
|
721
|
+
case "text":
|
|
722
|
+
return {
|
|
723
|
+
contentHtml: blockShell(type, block.html ?? block.value ?? "", blockId),
|
|
724
|
+
assetUrls
|
|
725
|
+
};
|
|
726
|
+
case "markdown":
|
|
727
|
+
return {
|
|
728
|
+
contentHtml: blockShell(
|
|
729
|
+
type,
|
|
730
|
+
block.html ?? `<div class="sqs-markdown">${escapeHtml(block.value ?? "")}</div>`,
|
|
731
|
+
blockId
|
|
732
|
+
),
|
|
733
|
+
assetUrls
|
|
734
|
+
};
|
|
735
|
+
case "image": {
|
|
736
|
+
const url = block.imageUrl ?? "";
|
|
737
|
+
if (url) assetUrls.push(url);
|
|
738
|
+
const alt = block.altText ? ` alt="${escapeHtml(block.altText)}"` : "";
|
|
739
|
+
const caption = block.caption ? `<figcaption>${block.caption}</figcaption>` : "";
|
|
740
|
+
const inner = url ? `<figure><img src="${escapeHtml(url)}"${alt} />${caption}</figure>` : "";
|
|
741
|
+
return { contentHtml: blockShell(type, inner, blockId), assetUrls };
|
|
742
|
+
}
|
|
743
|
+
case "gallery": {
|
|
744
|
+
const figures = (block.items ?? []).map((item) => {
|
|
745
|
+
assetUrls.push(item.imageUrl);
|
|
746
|
+
return flattenGalleryItem(item);
|
|
747
|
+
}).join("");
|
|
748
|
+
return {
|
|
749
|
+
contentHtml: blockShell(type, `<div class="sqs-gallery">${figures}</div>`, blockId),
|
|
750
|
+
assetUrls
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
case "quote": {
|
|
754
|
+
const inner = block.html ?? `<blockquote>${escapeHtml(block.value ?? "")}</blockquote>`;
|
|
755
|
+
return { contentHtml: blockShell(type, inner, blockId), assetUrls };
|
|
756
|
+
}
|
|
757
|
+
case "button": {
|
|
758
|
+
const href = block.url ?? "#";
|
|
759
|
+
const label = escapeHtml(block.label ?? block.value ?? "Learn more");
|
|
760
|
+
return {
|
|
761
|
+
contentHtml: blockShell(
|
|
762
|
+
type,
|
|
763
|
+
`<p><a class="sqs-block-button" href="${escapeHtml(href)}">${label}</a></p>`,
|
|
764
|
+
blockId
|
|
765
|
+
),
|
|
766
|
+
assetUrls
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
case "video": {
|
|
770
|
+
const inner = block.embedHtml ?? block.html ?? (block.url ? `<p><a href="${escapeHtml(block.url)}">Video</a></p>` : "");
|
|
771
|
+
return { contentHtml: blockShell(type, inner, blockId), assetUrls };
|
|
772
|
+
}
|
|
773
|
+
case "code": {
|
|
774
|
+
const inner = block.html ?? `<pre><code>${escapeHtml(block.value ?? "")}</code></pre>`;
|
|
775
|
+
return { contentHtml: blockShell(type, inner, blockId), assetUrls };
|
|
776
|
+
}
|
|
777
|
+
case "spacer":
|
|
778
|
+
return {
|
|
779
|
+
contentHtml: blockShell(type, `<div class="sqs-spacer" aria-hidden="true"></div>`, blockId),
|
|
780
|
+
assetUrls
|
|
781
|
+
};
|
|
782
|
+
case "line":
|
|
783
|
+
case "horizontalrule":
|
|
784
|
+
case "hr":
|
|
785
|
+
return { contentHtml: blockShell("line", "<hr />", blockId), assetUrls };
|
|
786
|
+
default:
|
|
787
|
+
return {
|
|
788
|
+
contentHtml: unsupportedPlaceholder(type, blockId),
|
|
789
|
+
assetUrls
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function flattenSquarespaceBlocks(blocks) {
|
|
794
|
+
const parts = [];
|
|
795
|
+
const assetUrls = [];
|
|
796
|
+
for (const block of blocks) {
|
|
797
|
+
const flattened = flattenSquarespaceBlock(block);
|
|
798
|
+
parts.push(flattened.contentHtml);
|
|
799
|
+
assetUrls.push(...flattened.assetUrls);
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
contentHtml: parts.join("\n"),
|
|
803
|
+
assetUrls
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function resolveContentHtml(entity) {
|
|
807
|
+
if (entity.blocks?.length) {
|
|
808
|
+
return flattenSquarespaceBlocks(entity.blocks);
|
|
809
|
+
}
|
|
810
|
+
const html = entity.contentHtml ?? "";
|
|
811
|
+
return {
|
|
812
|
+
contentHtml: html,
|
|
813
|
+
assetUrls: [...discoverContentAssetUrls(html)]
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function guessMime(filename) {
|
|
817
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
818
|
+
const map = {
|
|
819
|
+
jpg: "image/jpeg",
|
|
820
|
+
jpeg: "image/jpeg",
|
|
821
|
+
png: "image/png",
|
|
822
|
+
gif: "image/gif",
|
|
823
|
+
webp: "image/webp",
|
|
824
|
+
svg: "image/svg+xml"
|
|
825
|
+
};
|
|
826
|
+
return ext ? map[ext] : void 0;
|
|
827
|
+
}
|
|
828
|
+
function filenameFromUrl(url, fallback) {
|
|
829
|
+
try {
|
|
830
|
+
return basename(new URL(url).pathname) || fallback;
|
|
831
|
+
} catch {
|
|
832
|
+
return basename(url.split("?")[0] ?? "") || fallback;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function assetFromUrl(url, exportedAt, index) {
|
|
836
|
+
const filename = filenameFromUrl(url, `asset-${index}.jpg`);
|
|
837
|
+
const sourceId = `asset-${sanitizeSlug(filename)}-${index}`;
|
|
838
|
+
return {
|
|
839
|
+
type: "asset",
|
|
840
|
+
source: sourceMeta(sourceId, url, exportedAt),
|
|
841
|
+
sourceId,
|
|
842
|
+
sourceUrl: url,
|
|
843
|
+
filename,
|
|
844
|
+
mimeType: guessMime(filename)
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function isSquarespaceExport(value) {
|
|
848
|
+
if (!value || typeof value !== "object") return false;
|
|
849
|
+
const record = value;
|
|
850
|
+
const version = record.exportVersion;
|
|
851
|
+
return (version === 1 || version === "1") && Array.isArray(record.pages);
|
|
852
|
+
}
|
|
853
|
+
async function loadSquarespaceExport(options) {
|
|
854
|
+
if (options.data) return options.data;
|
|
855
|
+
if (options.client) {
|
|
856
|
+
if (!options.collectTargets?.length) {
|
|
857
|
+
throw new Error("Squarespace client.collectExport requires collectTargets");
|
|
858
|
+
}
|
|
859
|
+
return options.client.collectExport(options.collectTargets);
|
|
860
|
+
}
|
|
861
|
+
if (options.collectTargets?.length) {
|
|
862
|
+
const client = new SquarespaceCollectionClient(options.clientOptions);
|
|
863
|
+
return client.collectExport(options.collectTargets);
|
|
864
|
+
}
|
|
865
|
+
if (!options.filePath) {
|
|
866
|
+
throw new Error("Squarespace parser requires filePath, data, client, or collectTargets");
|
|
867
|
+
}
|
|
868
|
+
const raw = JSON.parse(await readFile(options.filePath, "utf8"));
|
|
869
|
+
if (isSquarespaceExport(raw)) return raw;
|
|
870
|
+
throw new Error("Invalid Squarespace export: expected exportVersion 1 with pages[]");
|
|
871
|
+
}
|
|
872
|
+
function* emitAssetsFromContent(contentHtml, explicitUrls, exportedAt) {
|
|
873
|
+
const seen = /* @__PURE__ */ new Set();
|
|
874
|
+
const urls = [...explicitUrls, ...discoverContentAssetUrls(contentHtml)];
|
|
875
|
+
let index = 0;
|
|
876
|
+
for (const url of urls) {
|
|
877
|
+
if (seen.has(url)) continue;
|
|
878
|
+
seen.add(url);
|
|
879
|
+
yield assetFromUrl(url, exportedAt, index);
|
|
880
|
+
index += 1;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async function* emitPage(page, exportedAt) {
|
|
884
|
+
const { contentHtml, assetUrls } = resolveContentHtml(page);
|
|
885
|
+
yield {
|
|
886
|
+
type: "page",
|
|
887
|
+
source: sourceMeta(page.id, page.url, exportedAt),
|
|
888
|
+
sourceId: page.id,
|
|
889
|
+
title: page.title,
|
|
890
|
+
slug: sanitizeSlug(page.slug),
|
|
891
|
+
contentHtml,
|
|
892
|
+
contentCss: page.contentCss,
|
|
893
|
+
isHomePage: page.isHomePage,
|
|
894
|
+
status: mapPublishStatus(page.status),
|
|
895
|
+
seoTitle: page.seoTitle,
|
|
896
|
+
seoDescription: page.seoDescription
|
|
897
|
+
};
|
|
898
|
+
yield* emitAssetsFromContent(contentHtml, assetUrls, exportedAt);
|
|
899
|
+
}
|
|
900
|
+
async function* emitPost(post, exportedAt) {
|
|
901
|
+
const { contentHtml, assetUrls } = resolveContentHtml(post);
|
|
902
|
+
let featuredAssetSourceId;
|
|
903
|
+
if (post.featuredImageUrl) {
|
|
904
|
+
featuredAssetSourceId = `featured-${post.id}`;
|
|
905
|
+
}
|
|
906
|
+
yield {
|
|
907
|
+
type: "post",
|
|
908
|
+
source: sourceMeta(post.id, post.url, exportedAt),
|
|
909
|
+
sourceId: post.id,
|
|
910
|
+
title: post.title,
|
|
911
|
+
slug: sanitizeSlug(post.slug),
|
|
912
|
+
excerpt: post.excerpt,
|
|
913
|
+
contentHtml,
|
|
914
|
+
publishedAt: post.publishedAt,
|
|
915
|
+
status: mapPublishStatus(post.status),
|
|
916
|
+
categorySlugs: post.categorySlugs,
|
|
917
|
+
tagSlugs: post.tagSlugs,
|
|
918
|
+
featuredAssetSourceId,
|
|
919
|
+
seoTitle: post.seoTitle,
|
|
920
|
+
seoDescription: post.seoDescription
|
|
921
|
+
};
|
|
922
|
+
if (post.featuredImageUrl) {
|
|
923
|
+
const filename = filenameFromUrl(post.featuredImageUrl, `${post.id}-featured.jpg`);
|
|
924
|
+
yield {
|
|
925
|
+
type: "asset",
|
|
926
|
+
source: sourceMeta(featuredAssetSourceId, post.featuredImageUrl, exportedAt),
|
|
927
|
+
sourceId: featuredAssetSourceId,
|
|
928
|
+
sourceUrl: post.featuredImageUrl,
|
|
929
|
+
filename,
|
|
930
|
+
mimeType: guessMime(filename)
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
yield* emitAssetsFromContent(contentHtml, assetUrls, exportedAt);
|
|
934
|
+
}
|
|
935
|
+
async function* enumerateSquarespaceEntities(options) {
|
|
936
|
+
const doc = await loadSquarespaceExport(options);
|
|
937
|
+
const exportedAt = doc.exportedAt;
|
|
938
|
+
for (const category of doc.categories ?? []) {
|
|
939
|
+
yield {
|
|
940
|
+
type: "category",
|
|
941
|
+
source: sourceMeta(category.id, void 0, exportedAt),
|
|
942
|
+
sourceId: category.id,
|
|
943
|
+
name: category.name,
|
|
944
|
+
slug: sanitizeSlug(category.slug)
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
for (const tag of doc.tags ?? []) {
|
|
948
|
+
yield {
|
|
949
|
+
type: "tag",
|
|
950
|
+
source: sourceMeta(tag.id, void 0, exportedAt),
|
|
951
|
+
sourceId: tag.id,
|
|
952
|
+
name: tag.name,
|
|
953
|
+
slug: sanitizeSlug(tag.slug)
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
for (const page of doc.pages) {
|
|
957
|
+
yield* emitPage(page, exportedAt);
|
|
958
|
+
}
|
|
959
|
+
for (const post of doc.posts ?? []) {
|
|
960
|
+
yield* emitPost(post, exportedAt);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function summarizeSquarespaceExport(doc) {
|
|
964
|
+
return {
|
|
965
|
+
pages: doc.pages.length,
|
|
966
|
+
posts: doc.posts?.length ?? 0,
|
|
967
|
+
categories: doc.categories?.length ?? 0,
|
|
968
|
+
tags: doc.tags?.length ?? 0
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
async function validateSquarespaceExportFile(filePath) {
|
|
972
|
+
const issues = [];
|
|
973
|
+
let doc;
|
|
974
|
+
try {
|
|
975
|
+
doc = await loadSquarespaceExport({ filePath });
|
|
976
|
+
} catch (error) {
|
|
977
|
+
return {
|
|
978
|
+
ok: false,
|
|
979
|
+
issues: [
|
|
980
|
+
{
|
|
981
|
+
code: "invalid_export",
|
|
982
|
+
message: error instanceof Error ? error.message : String(error)
|
|
983
|
+
}
|
|
984
|
+
],
|
|
985
|
+
summary: {}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
if (doc.pages.length === 0 && (doc.posts?.length ?? 0) === 0) {
|
|
989
|
+
issues.push({ code: "empty_export", message: "No pages or posts in export" });
|
|
990
|
+
}
|
|
991
|
+
const summary = summarizeSquarespaceExport(doc);
|
|
992
|
+
return {
|
|
993
|
+
ok: issues.length === 0,
|
|
994
|
+
issues,
|
|
995
|
+
summary: {
|
|
996
|
+
pages: summary.pages,
|
|
997
|
+
posts: summary.posts,
|
|
998
|
+
categories: summary.categories,
|
|
999
|
+
tags: summary.tags,
|
|
1000
|
+
portfolios: 0,
|
|
1001
|
+
assets: 0
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function findUnsupportedBlockMarkers(html) {
|
|
1006
|
+
const markers = [];
|
|
1007
|
+
const pattern = /data-artinstack-unsupported-block="([^"]+)"(?:\s+data-artinstack-block-id="([^"]*)")?/g;
|
|
1008
|
+
for (const match of html.matchAll(pattern)) {
|
|
1009
|
+
markers.push({
|
|
1010
|
+
blockType: match[1] ?? "unknown",
|
|
1011
|
+
blockId: match[2] || void 0
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
return markers;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/sinks/conflicts.ts
|
|
1018
|
+
function emptyConflictReport() {
|
|
1019
|
+
return {
|
|
1020
|
+
duplicatePostSlugs: [],
|
|
1021
|
+
duplicatePageSlugs: [],
|
|
1022
|
+
missingFeaturedImages: [],
|
|
1023
|
+
staleAssetUrls: [],
|
|
1024
|
+
invalidHtml: [],
|
|
1025
|
+
unresolvedInlineImages: [],
|
|
1026
|
+
unsupportedBlocks: [],
|
|
1027
|
+
redirectLoops: []
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function findDuplicateSlugs(items, entityType) {
|
|
1031
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
1032
|
+
for (const item of items) {
|
|
1033
|
+
const list = bySlug.get(item.slug) ?? [];
|
|
1034
|
+
list.push(item.sourceId);
|
|
1035
|
+
bySlug.set(item.slug, list);
|
|
1036
|
+
}
|
|
1037
|
+
const conflicts = [];
|
|
1038
|
+
for (const [slug, sourceIds] of bySlug) {
|
|
1039
|
+
if (sourceIds.length > 1) {
|
|
1040
|
+
conflicts.push({ entityType, slug, sourceIds });
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return conflicts;
|
|
1044
|
+
}
|
|
1045
|
+
function analyzeHtml(html) {
|
|
1046
|
+
const issues = [];
|
|
1047
|
+
if (/<script\b/i.test(html)) {
|
|
1048
|
+
issues.push("script_tag_present");
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const $ = cheerio4.load(html, { xml: false });
|
|
1052
|
+
$("p").each((_, el) => {
|
|
1053
|
+
const inner = $(el).html() ?? "";
|
|
1054
|
+
if (inner.includes("<p")) {
|
|
1055
|
+
issues.push("nested_paragraph");
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
} catch {
|
|
1059
|
+
issues.push("html_parse_error");
|
|
1060
|
+
}
|
|
1061
|
+
return [...new Set(issues)];
|
|
1062
|
+
}
|
|
1063
|
+
function mediaUrlSet(bundle) {
|
|
1064
|
+
const urls = /* @__PURE__ */ new Set();
|
|
1065
|
+
for (const asset of bundle.media) {
|
|
1066
|
+
const normalized = normalizeAssetUrl(asset.sourceUrl);
|
|
1067
|
+
if (normalized) urls.add(normalized);
|
|
1068
|
+
urls.add(asset.sourceUrl);
|
|
1069
|
+
}
|
|
1070
|
+
return urls;
|
|
1071
|
+
}
|
|
1072
|
+
function findUnresolvedInlineImages(sourceId, contentHtml, mediaUrls) {
|
|
1073
|
+
const conflicts = [];
|
|
1074
|
+
for (const raw of discoverRawImgSrcs(contentHtml)) {
|
|
1075
|
+
const normalized = normalizeAssetUrl(raw);
|
|
1076
|
+
if (!normalized) continue;
|
|
1077
|
+
if (!mediaUrls.has(normalized) && !mediaUrls.has(raw)) {
|
|
1078
|
+
conflicts.push({ postOrPageSourceId: sourceId, src: raw });
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return conflicts;
|
|
1082
|
+
}
|
|
1083
|
+
function findUnsupportedBlocks(entityType, sourceId, contentHtml) {
|
|
1084
|
+
return findUnsupportedBlockMarkers(contentHtml).map((marker) => ({
|
|
1085
|
+
entityType,
|
|
1086
|
+
sourceId,
|
|
1087
|
+
blockType: marker.blockType,
|
|
1088
|
+
blockId: marker.blockId
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
function analyzeConflicts(bundle, options) {
|
|
1092
|
+
const report = emptyConflictReport();
|
|
1093
|
+
const mediaUrls = mediaUrlSet(bundle);
|
|
1094
|
+
report.duplicatePostSlugs = findDuplicateSlugs(bundle.posts, "post");
|
|
1095
|
+
report.duplicatePageSlugs = findDuplicateSlugs(bundle.pages, "page");
|
|
1096
|
+
for (const post of bundle.posts) {
|
|
1097
|
+
if (post.sourceFeaturedMediaId && !post.featuredAssetSourceId) {
|
|
1098
|
+
report.missingFeaturedImages.push({
|
|
1099
|
+
postSourceId: post.sourceId,
|
|
1100
|
+
featuredMediaSourceId: post.sourceFeaturedMediaId,
|
|
1101
|
+
reason: "attachment_not_in_export"
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
const htmlIssues = analyzeHtml(post.contentHtml);
|
|
1105
|
+
if (htmlIssues.length) {
|
|
1106
|
+
report.invalidHtml.push({
|
|
1107
|
+
entityType: "post",
|
|
1108
|
+
sourceId: post.sourceId,
|
|
1109
|
+
issues: htmlIssues
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
report.unresolvedInlineImages.push(
|
|
1113
|
+
...findUnresolvedInlineImages(post.sourceId, post.contentHtml, mediaUrls)
|
|
1114
|
+
);
|
|
1115
|
+
report.unsupportedBlocks.push(
|
|
1116
|
+
...findUnsupportedBlocks("post", post.sourceId, post.contentHtml)
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
for (const page of bundle.pages) {
|
|
1120
|
+
const htmlIssues = analyzeHtml(page.contentHtml);
|
|
1121
|
+
if (htmlIssues.length) {
|
|
1122
|
+
report.invalidHtml.push({
|
|
1123
|
+
entityType: "page",
|
|
1124
|
+
sourceId: page.sourceId,
|
|
1125
|
+
issues: htmlIssues
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
report.unresolvedInlineImages.push(
|
|
1129
|
+
...findUnresolvedInlineImages(page.sourceId, page.contentHtml, mediaUrls)
|
|
1130
|
+
);
|
|
1131
|
+
report.unsupportedBlocks.push(
|
|
1132
|
+
...findUnsupportedBlocks("page", page.sourceId, page.contentHtml)
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
if (options?.staleAssetUrls) {
|
|
1136
|
+
report.staleAssetUrls = options.staleAssetUrls;
|
|
1137
|
+
}
|
|
1138
|
+
if (options?.redirectLoops) {
|
|
1139
|
+
report.redirectLoops = options.redirectLoops;
|
|
1140
|
+
}
|
|
1141
|
+
return report;
|
|
1142
|
+
}
|
|
1143
|
+
function hasBlockingConflicts(report) {
|
|
1144
|
+
return report.duplicatePageSlugs.length > 0 || report.redirectLoops.some((r) => r.blocked);
|
|
1145
|
+
}
|
|
1146
|
+
function hasWarnings(report) {
|
|
1147
|
+
return report.duplicatePostSlugs.length > 0 || report.missingFeaturedImages.length > 0 || report.staleAssetUrls.length > 0 || report.invalidHtml.length > 0 || report.unresolvedInlineImages.length > 0 || report.unsupportedBlocks.length > 0;
|
|
1148
|
+
}
|
|
1149
|
+
function buildRedirectMap(bundle) {
|
|
1150
|
+
const redirects = [];
|
|
1151
|
+
for (const post of bundle.posts) {
|
|
1152
|
+
const from = post.source.path;
|
|
1153
|
+
if (!from) continue;
|
|
1154
|
+
const to = `/blog/${post.slug}`;
|
|
1155
|
+
if (from.replace(/\/$/, "") === to.replace(/\/$/, "")) continue;
|
|
1156
|
+
redirects.push({ fromPath: from, toPath: to, statusCode: 301 });
|
|
1157
|
+
}
|
|
1158
|
+
for (const page of bundle.pages) {
|
|
1159
|
+
const from = page.source.path;
|
|
1160
|
+
if (!from) continue;
|
|
1161
|
+
const to = `/${page.slug}`;
|
|
1162
|
+
if (from.replace(/\/$/, "") === to.replace(/\/$/, "")) continue;
|
|
1163
|
+
redirects.push({ fromPath: from, toPath: to, statusCode: 301 });
|
|
1164
|
+
}
|
|
1165
|
+
return redirects;
|
|
1166
|
+
}
|
|
1167
|
+
function detectRedirectLoops(redirects) {
|
|
1168
|
+
const loops = [];
|
|
1169
|
+
for (const row of redirects) {
|
|
1170
|
+
if (row.fromPath.replace(/\/$/, "") === row.toPath.replace(/\/$/, "")) {
|
|
1171
|
+
loops.push({ fromPath: row.fromPath, toPath: row.toPath, blocked: true });
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return loops;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/sinks/run-migration.ts
|
|
1178
|
+
async function runMigration(options) {
|
|
1179
|
+
const bundle = await collectEntities(options.entities);
|
|
1180
|
+
return runMigrationFromBundle(bundle, options);
|
|
1181
|
+
}
|
|
1182
|
+
async function runMigrationFromBundle(bundle, options) {
|
|
1183
|
+
const { sink, platform, onEntityProcessed } = options;
|
|
1184
|
+
const checkpointEntities = [];
|
|
1185
|
+
const uploadedAssets = /* @__PURE__ */ new Map();
|
|
1186
|
+
let processed = 0;
|
|
1187
|
+
let failed = 0;
|
|
1188
|
+
let skipped = 0;
|
|
1189
|
+
const track = async (stage, key, action) => {
|
|
1190
|
+
if (!shouldProcessEntity(key, checkpointEntities)) {
|
|
1191
|
+
skipped += 1;
|
|
1192
|
+
onEntityProcessed?.(key, "skipped");
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const existingTargetId = await sink.findExisting?.(key);
|
|
1196
|
+
if (existingTargetId) {
|
|
1197
|
+
skipped += 1;
|
|
1198
|
+
onEntityProcessed?.(key, "skipped");
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
await sink.reportProgress?.({
|
|
1203
|
+
stage,
|
|
1204
|
+
progress: processed + failed + skipped,
|
|
1205
|
+
message: `${stage}:${key.entityType}:${key.sourceId}`
|
|
1206
|
+
});
|
|
1207
|
+
await action();
|
|
1208
|
+
processed += 1;
|
|
1209
|
+
onEntityProcessed?.(key, "done");
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
failed += 1;
|
|
1212
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1213
|
+
onEntityProcessed?.(key, "failed", message);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
for (const category of bundle.categories) {
|
|
1217
|
+
const key = entityKey(category, platform);
|
|
1218
|
+
await track("taxonomy", key, async () => {
|
|
1219
|
+
if (!sink.createCategory) return;
|
|
1220
|
+
await sink.createCategory(category);
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
for (const tag of bundle.tags) {
|
|
1224
|
+
const key = entityKey(tag, platform);
|
|
1225
|
+
await track("taxonomy", key, async () => {
|
|
1226
|
+
if (!sink.createTag) return;
|
|
1227
|
+
await sink.createTag(tag);
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
for (const asset of bundle.media) {
|
|
1231
|
+
const key = entityKey(asset, platform);
|
|
1232
|
+
await track("assets", key, async () => {
|
|
1233
|
+
const stream = options.resolveAssetStream ? await options.resolveAssetStream(asset) : null;
|
|
1234
|
+
const result = await sink.uploadAsset({
|
|
1235
|
+
asset,
|
|
1236
|
+
body: stream?.body ?? emptyBody(),
|
|
1237
|
+
contentLength: stream?.contentLength
|
|
1238
|
+
});
|
|
1239
|
+
uploadedAssets.set(asset.sourceId, result);
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
for (const portfolio of bundle.portfolios) {
|
|
1243
|
+
const key = entityKey(portfolio, platform);
|
|
1244
|
+
await track("portfolios", key, async () => {
|
|
1245
|
+
if (!sink.createPortfolio) {
|
|
1246
|
+
throw new Error("Sink does not support portfolios");
|
|
1247
|
+
}
|
|
1248
|
+
await sink.createPortfolio(portfolio);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
for (const post of bundle.posts) {
|
|
1252
|
+
const key = entityKey(post, platform);
|
|
1253
|
+
await track("content", key, async () => {
|
|
1254
|
+
await sink.createPost(
|
|
1255
|
+
prepareContentEntity(post, options, uploadedAssets, bundle)
|
|
1256
|
+
);
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
for (const page of bundle.pages) {
|
|
1260
|
+
const key = entityKey(page, platform);
|
|
1261
|
+
await track("content", key, async () => {
|
|
1262
|
+
await sink.createPage(
|
|
1263
|
+
prepareContentEntity(page, options, uploadedAssets, bundle)
|
|
1264
|
+
);
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
const portfolioLinks = buildPortfolioMediaLinks(bundle);
|
|
1268
|
+
for (const link of portfolioLinks) {
|
|
1269
|
+
const key = {
|
|
1270
|
+
platform,
|
|
1271
|
+
entityType: "asset",
|
|
1272
|
+
sourceId: `${link.portfolioSourceId}:${link.assetSourceId}`
|
|
1273
|
+
};
|
|
1274
|
+
await track("bindings", key, async () => {
|
|
1275
|
+
if (!sink.linkPortfolioMedia) return;
|
|
1276
|
+
await sink.linkPortfolioMedia(link);
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
const redirects = buildRedirectMap(bundle);
|
|
1280
|
+
for (const redirect of redirects) {
|
|
1281
|
+
const key = {
|
|
1282
|
+
platform,
|
|
1283
|
+
entityType: "page",
|
|
1284
|
+
sourceId: `redirect:${redirect.fromPath}`
|
|
1285
|
+
};
|
|
1286
|
+
await track("redirects", key, async () => {
|
|
1287
|
+
if (!sink.writeRedirect) return;
|
|
1288
|
+
await sink.writeRedirect(redirect);
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
return { processed, failed, skipped };
|
|
1292
|
+
}
|
|
1293
|
+
function prepareContentEntity(entity, options, uploadedAssets, bundle) {
|
|
1294
|
+
if (!options.rewriteInlineImages) return entity;
|
|
1295
|
+
const rewriteOptions = mergeRewriteOptions(bundle, options.rewriteInlineImages);
|
|
1296
|
+
const rewritten = rewriteInlineImages(entity.contentHtml, rewriteOptions, uploadedAssets);
|
|
1297
|
+
return {
|
|
1298
|
+
...entity,
|
|
1299
|
+
contentHtml: rewritten.html
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function mergeRewriteOptions(bundle, options) {
|
|
1303
|
+
const urlToSourceId = /* @__PURE__ */ new Map();
|
|
1304
|
+
for (const asset of bundle.media) {
|
|
1305
|
+
urlToSourceId.set(asset.sourceUrl, asset.sourceId);
|
|
1306
|
+
const normalized = normalizeAssetUrl(asset.sourceUrl);
|
|
1307
|
+
if (normalized) urlToSourceId.set(normalized, asset.sourceId);
|
|
1308
|
+
}
|
|
1309
|
+
return {
|
|
1310
|
+
resolveAsset: (src) => {
|
|
1311
|
+
const resolved = options.resolveAsset(src);
|
|
1312
|
+
if (resolved) return resolved;
|
|
1313
|
+
const normalized = normalizeAssetUrl(src);
|
|
1314
|
+
const sourceAssetId = (normalized ? urlToSourceId.get(normalized) : void 0) ?? urlToSourceId.get(src);
|
|
1315
|
+
if (!sourceAssetId) return void 0;
|
|
1316
|
+
return { originalSrc: src, sourceAssetId };
|
|
1317
|
+
},
|
|
1318
|
+
replaceWith: options.replaceWith
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function emptyBody() {
|
|
1322
|
+
return Readable.from([]);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/sinks/filesystem.ts
|
|
1326
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
1327
|
+
import { join } from "path";
|
|
1328
|
+
async function writeJson(path, data) {
|
|
1329
|
+
await writeFile(path, `${JSON.stringify(data, null, 2)}
|
|
1330
|
+
`, "utf8");
|
|
1331
|
+
}
|
|
1332
|
+
async function writeFilesystemExport(options) {
|
|
1333
|
+
await mkdir(options.outDir, { recursive: true });
|
|
1334
|
+
await writeJson(join(options.outDir, "posts.json"), options.bundle.posts);
|
|
1335
|
+
await writeJson(join(options.outDir, "pages.json"), options.bundle.pages);
|
|
1336
|
+
await writeJson(join(options.outDir, "media.json"), options.bundle.media);
|
|
1337
|
+
await writeJson(join(options.outDir, "portfolios.json"), options.bundle.portfolios);
|
|
1338
|
+
await writeJson(
|
|
1339
|
+
join(options.outDir, "portfolio-media.json"),
|
|
1340
|
+
buildPortfolioMediaLinks(options.bundle)
|
|
1341
|
+
);
|
|
1342
|
+
await writeJson(join(options.outDir, "categories.json"), options.bundle.categories);
|
|
1343
|
+
await writeJson(join(options.outDir, "tags.json"), options.bundle.tags);
|
|
1344
|
+
if (options.conflicts) {
|
|
1345
|
+
await writeJson(join(options.outDir, "conflicts.json"), options.conflicts);
|
|
1346
|
+
}
|
|
1347
|
+
if (options.report) {
|
|
1348
|
+
await writeJson(join(options.outDir, "migration-report.json"), options.report);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function bundleToCombinedJson(bundle) {
|
|
1352
|
+
return {
|
|
1353
|
+
posts: bundle.posts,
|
|
1354
|
+
pages: bundle.pages,
|
|
1355
|
+
media: bundle.media,
|
|
1356
|
+
portfolios: bundle.portfolios,
|
|
1357
|
+
portfolioMedia: buildPortfolioMediaLinks(bundle),
|
|
1358
|
+
categories: bundle.categories,
|
|
1359
|
+
tags: bundle.tags
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/sinks/filesystem-sink.ts
|
|
1364
|
+
var FilesystemMigrationSink = class {
|
|
1365
|
+
bundle = emptyBundle();
|
|
1366
|
+
portfolioMediaLinks = [];
|
|
1367
|
+
redirects = [];
|
|
1368
|
+
async createCategory(category) {
|
|
1369
|
+
this.bundle.categories.push(category);
|
|
1370
|
+
return { targetId: category.sourceId };
|
|
1371
|
+
}
|
|
1372
|
+
async createTag(tag) {
|
|
1373
|
+
this.bundle.tags.push(tag);
|
|
1374
|
+
return { targetId: tag.sourceId };
|
|
1375
|
+
}
|
|
1376
|
+
async uploadAsset(input) {
|
|
1377
|
+
this.bundle.media.push(input.asset);
|
|
1378
|
+
return {
|
|
1379
|
+
targetId: input.asset.sourceId,
|
|
1380
|
+
publicUrl: input.asset.sourceUrl
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
async createPortfolio(portfolio) {
|
|
1384
|
+
this.bundle.portfolios.push(portfolio);
|
|
1385
|
+
return { targetId: portfolio.sourceId };
|
|
1386
|
+
}
|
|
1387
|
+
async createPost(post) {
|
|
1388
|
+
this.bundle.posts.push(post);
|
|
1389
|
+
return {
|
|
1390
|
+
targetId: post.sourceId,
|
|
1391
|
+
publicPath: post.source.path ?? `/${post.slug}`
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
async createPage(page) {
|
|
1395
|
+
this.bundle.pages.push(page);
|
|
1396
|
+
return {
|
|
1397
|
+
targetId: page.sourceId,
|
|
1398
|
+
publicPath: page.source.path ?? `/${page.slug}`
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
async linkPortfolioMedia(link) {
|
|
1402
|
+
this.portfolioMediaLinks.push(link);
|
|
1403
|
+
}
|
|
1404
|
+
async writeRedirect(redirect) {
|
|
1405
|
+
this.redirects.push(redirect);
|
|
1406
|
+
}
|
|
1407
|
+
async flush(options) {
|
|
1408
|
+
await writeFilesystemExport({
|
|
1409
|
+
...options,
|
|
1410
|
+
bundle: this.bundle
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
function createFilesystemMigrationSink() {
|
|
1415
|
+
return new FilesystemMigrationSink();
|
|
1416
|
+
}
|
|
1417
|
+
function portfolioMediaMatchesBundle(sink) {
|
|
1418
|
+
const expected = buildPortfolioMediaLinks(sink.bundle);
|
|
1419
|
+
if (expected.length !== sink.portfolioMediaLinks.length) return false;
|
|
1420
|
+
return expected.every((link, index) => {
|
|
1421
|
+
const actual = sink.portfolioMediaLinks[index];
|
|
1422
|
+
return actual?.portfolioSourceId === link.portfolioSourceId && actual?.assetSourceId === link.assetSourceId && actual?.sort === link.sort;
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/sinks/migration-report.ts
|
|
1427
|
+
import { randomUUID } from "crypto";
|
|
1428
|
+
function buildMigrationReport(input) {
|
|
1429
|
+
const counts = bundleCounts(input.bundle);
|
|
1430
|
+
return {
|
|
1431
|
+
runId: input.runId ?? randomUUID(),
|
|
1432
|
+
platform: input.platform,
|
|
1433
|
+
mode: input.mode,
|
|
1434
|
+
startedAt: input.startedAt.toISOString(),
|
|
1435
|
+
finishedAt: (input.finishedAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1436
|
+
summary: {
|
|
1437
|
+
...counts,
|
|
1438
|
+
storageBytesEstimated: input.storageBytesEstimated
|
|
1439
|
+
},
|
|
1440
|
+
warnings: input.warnings ?? [],
|
|
1441
|
+
errors: input.errors ?? [],
|
|
1442
|
+
conflicts: input.conflicts,
|
|
1443
|
+
redirectMap: input.redirectMap
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/sinks/storage-estimate.ts
|
|
1448
|
+
var FALLBACK_ASSET_BYTES = 4 * 1024 * 1024;
|
|
1449
|
+
async function estimateStorage(options) {
|
|
1450
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
1451
|
+
const results = [];
|
|
1452
|
+
for (const asset of options.assets) {
|
|
1453
|
+
if (options.offline) {
|
|
1454
|
+
results.push({
|
|
1455
|
+
sourceId: asset.sourceId,
|
|
1456
|
+
url: asset.sourceUrl,
|
|
1457
|
+
bytes: FALLBACK_ASSET_BYTES,
|
|
1458
|
+
source: "fallback",
|
|
1459
|
+
error: "offline_mode"
|
|
1460
|
+
});
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
const response = await fetchFn(asset.sourceUrl, {
|
|
1465
|
+
method: "HEAD",
|
|
1466
|
+
signal: AbortSignal.timeout(8e3)
|
|
1467
|
+
});
|
|
1468
|
+
if (response.ok) {
|
|
1469
|
+
const length = response.headers.get("content-length");
|
|
1470
|
+
const bytes = length ? Number.parseInt(length, 10) : FALLBACK_ASSET_BYTES;
|
|
1471
|
+
results.push({
|
|
1472
|
+
sourceId: asset.sourceId,
|
|
1473
|
+
url: asset.sourceUrl,
|
|
1474
|
+
bytes: Number.isFinite(bytes) ? bytes : FALLBACK_ASSET_BYTES,
|
|
1475
|
+
source: "head"
|
|
1476
|
+
});
|
|
1477
|
+
} else {
|
|
1478
|
+
results.push({
|
|
1479
|
+
sourceId: asset.sourceId,
|
|
1480
|
+
url: asset.sourceUrl,
|
|
1481
|
+
bytes: FALLBACK_ASSET_BYTES,
|
|
1482
|
+
source: "fallback",
|
|
1483
|
+
error: `http_${response.status}`
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
results.push({
|
|
1488
|
+
sourceId: asset.sourceId,
|
|
1489
|
+
url: asset.sourceUrl,
|
|
1490
|
+
bytes: FALLBACK_ASSET_BYTES,
|
|
1491
|
+
source: "fallback",
|
|
1492
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const totalBytes = results.reduce((sum, r) => sum + r.bytes, 0);
|
|
1497
|
+
return { totalBytes, assets: results };
|
|
1498
|
+
}
|
|
1499
|
+
function staleUrlsFromEstimate(estimate) {
|
|
1500
|
+
return estimate.assets.filter((a) => a.source === "fallback" && a.error && a.error !== "offline_mode").map((a) => ({
|
|
1501
|
+
sourceId: a.sourceId,
|
|
1502
|
+
url: a.url,
|
|
1503
|
+
reason: a.error ?? "unknown"
|
|
1504
|
+
}));
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/sinks/dry-run.ts
|
|
1508
|
+
async function runDryRun(options) {
|
|
1509
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
1510
|
+
const bundle = await collectEntities(
|
|
1511
|
+
options.adapter.enumerateEntities({ input: options.input })
|
|
1512
|
+
);
|
|
1513
|
+
const estimate = await estimateStorage({
|
|
1514
|
+
assets: bundle.media,
|
|
1515
|
+
offline: options.offlineStorageEstimate,
|
|
1516
|
+
fetchFn: options.fetchFn
|
|
1517
|
+
});
|
|
1518
|
+
const redirectMap = buildRedirectMap(bundle);
|
|
1519
|
+
const redirectLoops = detectRedirectLoops(redirectMap);
|
|
1520
|
+
const staleAssetUrls = staleUrlsFromEstimate(estimate);
|
|
1521
|
+
const conflicts = analyzeConflicts(bundle, { staleAssetUrls, redirectLoops });
|
|
1522
|
+
const warnings = [];
|
|
1523
|
+
if (staleAssetUrls.length > 0) {
|
|
1524
|
+
warnings.push(`${staleAssetUrls.length} asset URL(s) unreachable; used 4 MB fallback each`);
|
|
1525
|
+
}
|
|
1526
|
+
if (conflicts.duplicatePostSlugs.length > 0) {
|
|
1527
|
+
warnings.push(
|
|
1528
|
+
`${conflicts.duplicatePostSlugs.length} duplicate post slug group(s); host may auto-suffix`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
const report = buildMigrationReport({
|
|
1532
|
+
platform: options.platform,
|
|
1533
|
+
mode: "dry-run",
|
|
1534
|
+
bundle,
|
|
1535
|
+
conflicts,
|
|
1536
|
+
redirectMap,
|
|
1537
|
+
startedAt,
|
|
1538
|
+
storageBytesEstimated: estimate.totalBytes,
|
|
1539
|
+
warnings
|
|
1540
|
+
});
|
|
1541
|
+
let exitCode = 0;
|
|
1542
|
+
if (hasBlockingConflicts(conflicts)) exitCode = 1;
|
|
1543
|
+
else if (hasWarnings(conflicts) || warnings.length > 0) exitCode = 2;
|
|
1544
|
+
return { bundle, conflicts, report, exitCode };
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
export {
|
|
1548
|
+
MIGRATION_WRITE_STAGES,
|
|
1549
|
+
discoverContentAssetUrls,
|
|
1550
|
+
rewriteInlineImages,
|
|
1551
|
+
sanitizeSlug,
|
|
1552
|
+
linkToPath,
|
|
1553
|
+
SQUARESPACE_JSON_FORMAT,
|
|
1554
|
+
buildJsonPrettyUrl,
|
|
1555
|
+
mapJsonPrettyWire,
|
|
1556
|
+
SquarespaceCollectionClient,
|
|
1557
|
+
enumerateSquarespaceEntities,
|
|
1558
|
+
summarizeSquarespaceExport,
|
|
1559
|
+
validateSquarespaceExportFile,
|
|
1560
|
+
emptyConflictReport,
|
|
1561
|
+
analyzeConflicts,
|
|
1562
|
+
hasBlockingConflicts,
|
|
1563
|
+
hasWarnings,
|
|
1564
|
+
buildRedirectMap,
|
|
1565
|
+
detectRedirectLoops,
|
|
1566
|
+
runMigration,
|
|
1567
|
+
runMigrationFromBundle,
|
|
1568
|
+
writeFilesystemExport,
|
|
1569
|
+
bundleToCombinedJson,
|
|
1570
|
+
FilesystemMigrationSink,
|
|
1571
|
+
createFilesystemMigrationSink,
|
|
1572
|
+
portfolioMediaMatchesBundle,
|
|
1573
|
+
buildMigrationReport,
|
|
1574
|
+
FALLBACK_ASSET_BYTES,
|
|
1575
|
+
estimateStorage,
|
|
1576
|
+
staleUrlsFromEstimate,
|
|
1577
|
+
runDryRun
|
|
1578
|
+
};
|
|
1579
|
+
//# sourceMappingURL=chunk-LKNIQQJO.js.map
|