@artinstack/migrator 0.1.0 → 0.1.2
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 +66 -9
- package/dist/{bundle-BfZqiKV_.d.ts → bundle-DfM_jKbq.d.ts} +2 -2
- package/dist/chunk-2PNSVE5Y.js +67 -0
- package/dist/chunk-2PNSVE5Y.js.map +1 -0
- package/dist/chunk-3YJFSTYR.js +147 -0
- package/dist/chunk-3YJFSTYR.js.map +1 -0
- package/dist/{chunk-LKNIQQJO.js → chunk-HH7666MQ.js} +13 -65
- package/dist/chunk-HH7666MQ.js.map +1 -0
- package/dist/{chunk-JKDRTL24.js → chunk-HI7JHWZU.js} +1 -1
- package/dist/chunk-HI7JHWZU.js.map +1 -0
- package/dist/chunk-VXEHAQKK.js +2290 -0
- package/dist/chunk-VXEHAQKK.js.map +1 -0
- package/dist/cli/index.js +23 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-DQNzrygx.d.ts → index-D88mjcF5.d.ts} +1 -1
- package/dist/index.d.ts +259 -5
- package/dist/index.js +127 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/index.js +15 -0
- package/dist/normalizer/index.d.ts +837 -3
- package/dist/normalizer/index.js +36 -3
- package/dist/sinks/index.d.ts +2 -2
- package/dist/sinks/index.js +3 -2
- package/package.json +5 -1
- package/dist/chunk-2RWAXT6O.js +0 -1
- package/dist/chunk-FXXKLYO5.js +0 -1076
- package/dist/chunk-FXXKLYO5.js.map +0 -1
- package/dist/chunk-JKDRTL24.js.map +0 -1
- package/dist/chunk-LKNIQQJO.js.map +0 -1
- /package/dist/{chunk-2RWAXT6O.js.map → lib/index.js.map} +0 -0
package/dist/chunk-FXXKLYO5.js
DELETED
|
@@ -1,1076 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SquarespaceCollectionClient,
|
|
3
|
-
discoverContentAssetUrls,
|
|
4
|
-
enumerateSquarespaceEntities,
|
|
5
|
-
linkToPath,
|
|
6
|
-
sanitizeSlug,
|
|
7
|
-
summarizeSquarespaceExport,
|
|
8
|
-
validateSquarespaceExportFile
|
|
9
|
-
} from "./chunk-LKNIQQJO.js";
|
|
10
|
-
|
|
11
|
-
// src/parsers/wordpress/parse-wxr.ts
|
|
12
|
-
import { readFile } from "fs/promises";
|
|
13
|
-
import { basename } from "path";
|
|
14
|
-
import { XMLParser } from "fast-xml-parser";
|
|
15
|
-
var PLATFORM = "wordpress";
|
|
16
|
-
function asArray(value) {
|
|
17
|
-
if (value === void 0) return [];
|
|
18
|
-
return Array.isArray(value) ? value : [value];
|
|
19
|
-
}
|
|
20
|
-
function textValue(value) {
|
|
21
|
-
if (value === void 0 || value === null) return "";
|
|
22
|
-
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
23
|
-
if (typeof value === "object" && value !== null && "#text" in value) {
|
|
24
|
-
return String(value["#text"] ?? "");
|
|
25
|
-
}
|
|
26
|
-
return String(value);
|
|
27
|
-
}
|
|
28
|
-
function mapPublishStatus(wpStatus) {
|
|
29
|
-
switch ((wpStatus ?? "").toLowerCase()) {
|
|
30
|
-
case "publish":
|
|
31
|
-
return "published";
|
|
32
|
-
case "draft":
|
|
33
|
-
case "pending":
|
|
34
|
-
return "draft";
|
|
35
|
-
default:
|
|
36
|
-
return "archived";
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function getContentEncoded(item) {
|
|
40
|
-
const content = item.content;
|
|
41
|
-
if (content !== void 0) {
|
|
42
|
-
if (typeof content === "string") return content;
|
|
43
|
-
return textValue(content.encoded);
|
|
44
|
-
}
|
|
45
|
-
return textValue(item.encoded);
|
|
46
|
-
}
|
|
47
|
-
function sourceMeta(id, link, exportedAt) {
|
|
48
|
-
return {
|
|
49
|
-
platform: PLATFORM,
|
|
50
|
-
id,
|
|
51
|
-
url: link || void 0,
|
|
52
|
-
path: linkToPath(link),
|
|
53
|
-
exportedAt
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function getExcerpt(item) {
|
|
57
|
-
const excerpt = item.excerpt;
|
|
58
|
-
if (!excerpt) return "";
|
|
59
|
-
if (typeof excerpt === "string") return excerpt;
|
|
60
|
-
return textValue(excerpt.encoded);
|
|
61
|
-
}
|
|
62
|
-
function getPostMeta(item, key) {
|
|
63
|
-
for (const meta of asArray(item.postmeta)) {
|
|
64
|
-
if (textValue(meta.meta_key) === key) {
|
|
65
|
-
return textValue(meta.meta_value);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
function parseItems(xml) {
|
|
71
|
-
const parser = new XMLParser({
|
|
72
|
-
ignoreAttributes: false,
|
|
73
|
-
attributeNamePrefix: "@_",
|
|
74
|
-
removeNSPrefix: true,
|
|
75
|
-
trimValues: false,
|
|
76
|
-
parseTagValue: false
|
|
77
|
-
});
|
|
78
|
-
const doc = parser.parse(xml);
|
|
79
|
-
return asArray(doc.rss?.channel?.item);
|
|
80
|
-
}
|
|
81
|
-
function buildAttachmentIndex(items) {
|
|
82
|
-
const index = /* @__PURE__ */ new Map();
|
|
83
|
-
for (const item of items) {
|
|
84
|
-
if (textValue(item.post_type) !== "attachment") continue;
|
|
85
|
-
const id = textValue(item.post_id);
|
|
86
|
-
const url = textValue(item.attachment_url) || textValue(item.link);
|
|
87
|
-
if (!id || !url) continue;
|
|
88
|
-
const filename = basename(new URL(url, "http://local.invalid").pathname) || `attachment-${id}`;
|
|
89
|
-
index.set(id, {
|
|
90
|
-
sourceUrl: url,
|
|
91
|
-
filename,
|
|
92
|
-
mimeType: getPostMeta(item, "_wp_attached_file") ? void 0 : guessMime(filename),
|
|
93
|
-
title: textValue(item.title)
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
return index;
|
|
97
|
-
}
|
|
98
|
-
function guessMime(filename) {
|
|
99
|
-
const ext = filename.split(".").pop()?.toLowerCase();
|
|
100
|
-
const map = {
|
|
101
|
-
jpg: "image/jpeg",
|
|
102
|
-
jpeg: "image/jpeg",
|
|
103
|
-
png: "image/png",
|
|
104
|
-
gif: "image/gif",
|
|
105
|
-
webp: "image/webp",
|
|
106
|
-
pdf: "application/pdf"
|
|
107
|
-
};
|
|
108
|
-
return ext ? map[ext] : void 0;
|
|
109
|
-
}
|
|
110
|
-
function collectTaxonomies(items) {
|
|
111
|
-
const categories = /* @__PURE__ */ new Map();
|
|
112
|
-
const tags = /* @__PURE__ */ new Map();
|
|
113
|
-
for (const item of items) {
|
|
114
|
-
const postType = textValue(item.post_type);
|
|
115
|
-
if (postType !== "post" && postType !== "page") continue;
|
|
116
|
-
for (const cat of asArray(item.category)) {
|
|
117
|
-
const domain = cat["@_domain"] ?? "";
|
|
118
|
-
const nicename = sanitizeSlug(cat["@_nicename"] ?? textValue(cat["#text"]));
|
|
119
|
-
const name = textValue(cat["#text"]) || nicename;
|
|
120
|
-
if (!nicename) continue;
|
|
121
|
-
if (domain === "category") {
|
|
122
|
-
if (!categories.has(nicename)) {
|
|
123
|
-
categories.set(nicename, {
|
|
124
|
-
type: "category",
|
|
125
|
-
source: sourceMeta(`cat:${nicename}`),
|
|
126
|
-
sourceId: `cat:${nicename}`,
|
|
127
|
-
name,
|
|
128
|
-
slug: nicename
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
} else if (domain === "post_tag") {
|
|
132
|
-
if (!tags.has(nicename)) {
|
|
133
|
-
tags.set(nicename, {
|
|
134
|
-
type: "tag",
|
|
135
|
-
source: sourceMeta(`tag:${nicename}`),
|
|
136
|
-
sourceId: `tag:${nicename}`,
|
|
137
|
-
name,
|
|
138
|
-
slug: nicename
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return { categories, tags };
|
|
145
|
-
}
|
|
146
|
-
function collectInlineAssets(html, attachmentIndex, seenUrls, exportedAt) {
|
|
147
|
-
const assets = [];
|
|
148
|
-
for (const src of discoverContentAssetUrls(html)) {
|
|
149
|
-
if (seenUrls.has(src)) continue;
|
|
150
|
-
seenUrls.add(src);
|
|
151
|
-
let filename;
|
|
152
|
-
try {
|
|
153
|
-
filename = basename(new URL(src, "http://local.invalid").pathname) || "inline-asset";
|
|
154
|
-
} catch {
|
|
155
|
-
filename = "inline-asset";
|
|
156
|
-
}
|
|
157
|
-
assets.push({
|
|
158
|
-
type: "asset",
|
|
159
|
-
source: sourceMeta(`url:${src}`, src, exportedAt),
|
|
160
|
-
sourceId: `url:${src}`,
|
|
161
|
-
sourceUrl: src,
|
|
162
|
-
filename,
|
|
163
|
-
mimeType: guessMime(filename)
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
for (const [id, entry] of attachmentIndex) {
|
|
167
|
-
if (seenUrls.has(entry.sourceUrl)) continue;
|
|
168
|
-
void id;
|
|
169
|
-
}
|
|
170
|
-
return assets;
|
|
171
|
-
}
|
|
172
|
-
async function* enumerateWxrEntities(options) {
|
|
173
|
-
const xml = await readFile(options.filePath, "utf8");
|
|
174
|
-
const items = parseItems(xml);
|
|
175
|
-
const attachmentIndex = buildAttachmentIndex(items);
|
|
176
|
-
const { categories, tags } = collectTaxonomies(items);
|
|
177
|
-
const seenAssetUrls = /* @__PURE__ */ new Set();
|
|
178
|
-
const emittedAttachmentIds = /* @__PURE__ */ new Set();
|
|
179
|
-
for (const category of categories.values()) {
|
|
180
|
-
yield category;
|
|
181
|
-
}
|
|
182
|
-
for (const tag of tags.values()) {
|
|
183
|
-
yield tag;
|
|
184
|
-
}
|
|
185
|
-
for (const [id, entry] of attachmentIndex) {
|
|
186
|
-
emittedAttachmentIds.add(id);
|
|
187
|
-
seenAssetUrls.add(entry.sourceUrl);
|
|
188
|
-
yield {
|
|
189
|
-
type: "asset",
|
|
190
|
-
source: sourceMeta(id, entry.sourceUrl, options.exportedAt),
|
|
191
|
-
sourceId: id,
|
|
192
|
-
sourceUrl: entry.sourceUrl,
|
|
193
|
-
filename: entry.filename,
|
|
194
|
-
mimeType: entry.mimeType,
|
|
195
|
-
caption: entry.title
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
for (const item of items) {
|
|
199
|
-
const postType = textValue(item.post_type);
|
|
200
|
-
if (postType !== "post" && postType !== "page") continue;
|
|
201
|
-
const id = textValue(item.post_id);
|
|
202
|
-
const link = textValue(item.link);
|
|
203
|
-
const slug = sanitizeSlug(textValue(item.post_name) || textValue(item.title) || id);
|
|
204
|
-
const rawHtml = getContentEncoded(item);
|
|
205
|
-
for (const asset of collectInlineAssets(
|
|
206
|
-
rawHtml,
|
|
207
|
-
attachmentIndex,
|
|
208
|
-
seenAssetUrls,
|
|
209
|
-
options.exportedAt
|
|
210
|
-
)) {
|
|
211
|
-
yield asset;
|
|
212
|
-
}
|
|
213
|
-
const categorySlugs = [];
|
|
214
|
-
const tagSlugs = [];
|
|
215
|
-
for (const cat of asArray(item.category)) {
|
|
216
|
-
const domain = cat["@_domain"] ?? "";
|
|
217
|
-
const nicename = sanitizeSlug(cat["@_nicename"] ?? textValue(cat["#text"]));
|
|
218
|
-
if (!nicename) continue;
|
|
219
|
-
if (domain === "category") categorySlugs.push(nicename);
|
|
220
|
-
if (domain === "post_tag") tagSlugs.push(nicename);
|
|
221
|
-
}
|
|
222
|
-
if (postType === "post") {
|
|
223
|
-
const thumbnailId = getPostMeta(item, "_thumbnail_id");
|
|
224
|
-
let featuredAssetSourceId;
|
|
225
|
-
if (thumbnailId && attachmentIndex.has(thumbnailId)) {
|
|
226
|
-
featuredAssetSourceId = thumbnailId;
|
|
227
|
-
}
|
|
228
|
-
const post = {
|
|
229
|
-
type: "post",
|
|
230
|
-
source: sourceMeta(id, link, options.exportedAt),
|
|
231
|
-
sourceId: id,
|
|
232
|
-
title: textValue(item.title) || slug,
|
|
233
|
-
slug,
|
|
234
|
-
excerpt: getExcerpt(item) || void 0,
|
|
235
|
-
contentHtml: rawHtml,
|
|
236
|
-
publishedAt: textValue(item.post_date) || void 0,
|
|
237
|
-
status: mapPublishStatus(textValue(item.status)),
|
|
238
|
-
categorySlugs: categorySlugs.length ? categorySlugs : void 0,
|
|
239
|
-
tagSlugs: tagSlugs.length ? tagSlugs : void 0,
|
|
240
|
-
sourceFeaturedMediaId: thumbnailId,
|
|
241
|
-
featuredAssetSourceId
|
|
242
|
-
};
|
|
243
|
-
yield post;
|
|
244
|
-
} else {
|
|
245
|
-
const isHomePage = getPostMeta(item, "_wp_show_on_front") === "1" || getPostMeta(item, "page_on_front") === "1";
|
|
246
|
-
const page = {
|
|
247
|
-
type: "page",
|
|
248
|
-
source: sourceMeta(id, link, options.exportedAt),
|
|
249
|
-
sourceId: id,
|
|
250
|
-
title: textValue(item.title) || slug,
|
|
251
|
-
slug,
|
|
252
|
-
contentHtml: rawHtml,
|
|
253
|
-
isHomePage: isHomePage || void 0,
|
|
254
|
-
status: mapPublishStatus(textValue(item.status))
|
|
255
|
-
};
|
|
256
|
-
yield page;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
async function validateWxrFile(filePath) {
|
|
261
|
-
const issues = [];
|
|
262
|
-
let xml;
|
|
263
|
-
try {
|
|
264
|
-
xml = await readFile(filePath, "utf8");
|
|
265
|
-
} catch {
|
|
266
|
-
return {
|
|
267
|
-
ok: false,
|
|
268
|
-
issues: [{ code: "file_not_found", message: `Cannot read file: ${filePath}` }],
|
|
269
|
-
summary: {}
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
const looksLikeWxr = xml.includes("<rss") && (xml.includes("wp:wxr_version") || xml.includes("xmlns:wp=") || xml.includes("WordPress eXtended RSS"));
|
|
273
|
-
if (!looksLikeWxr) {
|
|
274
|
-
issues.push({ code: "invalid_wxr", message: "File does not appear to be WordPress WXR" });
|
|
275
|
-
}
|
|
276
|
-
const items = parseItems(xml);
|
|
277
|
-
const summary = {
|
|
278
|
-
posts: items.filter((i) => textValue(i.post_type) === "post").length,
|
|
279
|
-
pages: items.filter((i) => textValue(i.post_type) === "page").length,
|
|
280
|
-
assets: items.filter((i) => textValue(i.post_type) === "attachment").length,
|
|
281
|
-
portfolios: 0,
|
|
282
|
-
categories: 0,
|
|
283
|
-
tags: 0
|
|
284
|
-
};
|
|
285
|
-
const { categories, tags } = collectTaxonomies(items);
|
|
286
|
-
summary.categories = categories.size;
|
|
287
|
-
summary.tags = tags.size;
|
|
288
|
-
return { ok: issues.length === 0, issues, summary };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/parsers/wordpress/index.ts
|
|
292
|
-
function resolvePath(input) {
|
|
293
|
-
if (typeof input === "string") return input;
|
|
294
|
-
if (input && typeof input === "object" && "path" in input) {
|
|
295
|
-
return String(input.path);
|
|
296
|
-
}
|
|
297
|
-
throw new Error("WordPress adapter requires input path (string or { path })");
|
|
298
|
-
}
|
|
299
|
-
var wordpressAdapter = {
|
|
300
|
-
platform: "wordpress",
|
|
301
|
-
async validateInput(input) {
|
|
302
|
-
const path = resolvePath(input);
|
|
303
|
-
const result = await validateWxrFile(path);
|
|
304
|
-
return {
|
|
305
|
-
ok: result.ok,
|
|
306
|
-
issues: result.issues,
|
|
307
|
-
summary: result.summary
|
|
308
|
-
};
|
|
309
|
-
},
|
|
310
|
-
enumerateEntities(ctx) {
|
|
311
|
-
const path = resolvePath(ctx.input);
|
|
312
|
-
return enumerateWxrEntities({ filePath: path });
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// src/parsers/smugmug/api.ts
|
|
317
|
-
import { createHmac, randomBytes } from "crypto";
|
|
318
|
-
import { z } from "zod";
|
|
319
|
-
var SMUGMUG_API_HOST = "api.smugmug.com";
|
|
320
|
-
var SMUGMUG_API_BASE = `https://${SMUGMUG_API_HOST}/api/v2`;
|
|
321
|
-
var SMUGMUG_OAUTH_ENDPOINTS = {
|
|
322
|
-
requestToken: "https://api.smugmug.com/services/oauth/1.0a/getRequestToken",
|
|
323
|
-
authorize: "https://api.smugmug.com/services/oauth/1.0a/authorize",
|
|
324
|
-
accessToken: "https://api.smugmug.com/services/oauth/1.0a/getAccessToken"
|
|
325
|
-
};
|
|
326
|
-
var smugMugCredentialsSchema = z.object({
|
|
327
|
-
consumerKey: z.string().min(1),
|
|
328
|
-
consumerSecret: z.string().min(1),
|
|
329
|
-
accessToken: z.string().min(1),
|
|
330
|
-
accessTokenSecret: z.string().min(1)
|
|
331
|
-
});
|
|
332
|
-
var smugMugClientOptionsSchema = z.object({
|
|
333
|
-
credentials: smugMugCredentialsSchema,
|
|
334
|
-
pageSize: z.number().int().min(1).max(500).default(100),
|
|
335
|
-
maxRetries: z.number().int().min(0).max(10).default(3),
|
|
336
|
-
retryBaseDelayMs: z.number().int().min(0).default(500),
|
|
337
|
-
maxRetryDelayMs: z.number().int().min(0).default(8e3),
|
|
338
|
-
requestIntervalMs: z.number().int().min(0).default(200),
|
|
339
|
-
fetchImpl: z.custom().optional()
|
|
340
|
-
});
|
|
341
|
-
var ALBUM_IMAGES_CONFIG = {
|
|
342
|
-
expand: {
|
|
343
|
-
AlbumImage: {
|
|
344
|
-
expand: {
|
|
345
|
-
Image: {
|
|
346
|
-
filter: ["FileName", "Caption", "KeywordsArray"],
|
|
347
|
-
filteruri: ["ImageMetadata", "ImageSizeDetails"],
|
|
348
|
-
expand: {
|
|
349
|
-
ImageMetadata: {
|
|
350
|
-
filter: ["ISO", "Aperture", "ApertureValue", "ShutterSpeed", "ExposureTime", "FocalLength"]
|
|
351
|
-
},
|
|
352
|
-
ImageSizeDetails: {
|
|
353
|
-
filter: ["OriginalImageUrl"]
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
function oauthPercentEncode(value) {
|
|
362
|
-
return encodeURIComponent(value).replace(
|
|
363
|
-
/[!'()*]/g,
|
|
364
|
-
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
function normalizeRequestUrl(url) {
|
|
368
|
-
const protocol = url.protocol.replace(/:$/, "").toLowerCase();
|
|
369
|
-
const host = url.hostname.toLowerCase();
|
|
370
|
-
const defaultPort = protocol === "http" ? "80" : "443";
|
|
371
|
-
const port = url.port && url.port !== defaultPort ? `:${url.port}` : "";
|
|
372
|
-
return `${protocol}://${host}${port}${url.pathname}`;
|
|
373
|
-
}
|
|
374
|
-
function sortedParameterString(params) {
|
|
375
|
-
return Object.keys(params).sort((a, b) => a === b ? 0 : a < b ? -1 : 1).map((key) => `${oauthPercentEncode(key)}=${oauthPercentEncode(params[key])}`).join("&");
|
|
376
|
-
}
|
|
377
|
-
function collectSignatureParams(url, oauthParams, bodyParams) {
|
|
378
|
-
const params = { ...oauthParams };
|
|
379
|
-
url.searchParams.forEach((value, key) => {
|
|
380
|
-
params[key] = value;
|
|
381
|
-
});
|
|
382
|
-
if (bodyParams) {
|
|
383
|
-
for (const [key, value] of Object.entries(bodyParams)) {
|
|
384
|
-
params[key] = value;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return params;
|
|
388
|
-
}
|
|
389
|
-
function signSmugMugOAuthRequest(input) {
|
|
390
|
-
const url = new URL(input.url);
|
|
391
|
-
const parameterString = sortedParameterString(
|
|
392
|
-
collectSignatureParams(url, input.oauthParams, input.bodyParams)
|
|
393
|
-
);
|
|
394
|
-
const signatureBase = [
|
|
395
|
-
input.method.toUpperCase(),
|
|
396
|
-
oauthPercentEncode(normalizeRequestUrl(url)),
|
|
397
|
-
oauthPercentEncode(parameterString)
|
|
398
|
-
].join("&");
|
|
399
|
-
const signingKey = `${oauthPercentEncode(input.credentials.consumerSecret)}&${oauthPercentEncode(input.credentials.accessTokenSecret)}`;
|
|
400
|
-
return createHmac("sha1", signingKey).update(signatureBase).digest("base64");
|
|
401
|
-
}
|
|
402
|
-
function buildOAuthParams(credentials, nonce, timestamp) {
|
|
403
|
-
return {
|
|
404
|
-
oauth_consumer_key: credentials.consumerKey,
|
|
405
|
-
oauth_token: credentials.accessToken,
|
|
406
|
-
oauth_signature_method: "HMAC-SHA1",
|
|
407
|
-
oauth_timestamp: timestamp,
|
|
408
|
-
oauth_nonce: nonce,
|
|
409
|
-
oauth_version: "1.0"
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
function buildSmugMugAuthorizationHeader(input) {
|
|
413
|
-
const nonce = input.nonce ?? randomBytes(16).toString("hex");
|
|
414
|
-
const timestamp = input.timestamp ?? String(Math.floor(Date.now() / 1e3));
|
|
415
|
-
const oauthParams = buildOAuthParams(input.credentials, nonce, timestamp);
|
|
416
|
-
const signature = signSmugMugOAuthRequest({
|
|
417
|
-
method: input.method,
|
|
418
|
-
url: input.url,
|
|
419
|
-
credentials: input.credentials,
|
|
420
|
-
oauthParams,
|
|
421
|
-
bodyParams: input.bodyParams
|
|
422
|
-
});
|
|
423
|
-
const headerParams = { ...oauthParams, oauth_signature: signature };
|
|
424
|
-
const headerValue = Object.keys(headerParams).sort().map((key) => `${oauthPercentEncode(key)}="${oauthPercentEncode(headerParams[key])}"`).join(", ");
|
|
425
|
-
return `OAuth ${headerValue}`;
|
|
426
|
-
}
|
|
427
|
-
function readSmugMugCredentialsFromEnv(env = process.env) {
|
|
428
|
-
return smugMugCredentialsSchema.parse({
|
|
429
|
-
consumerKey: env.SMUGMUG_CONSUMER_KEY,
|
|
430
|
-
consumerSecret: env.SMUGMUG_CONSUMER_SECRET,
|
|
431
|
-
accessToken: env.SMUGMUG_ACCESS_TOKEN,
|
|
432
|
-
accessTokenSecret: env.SMUGMUG_ACCESS_TOKEN_SECRET
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
function sleep(ms) {
|
|
436
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
437
|
-
}
|
|
438
|
-
function albumKeyFromUri(uri) {
|
|
439
|
-
const match = uri.match(/\/album\/([^/?!]+)/i);
|
|
440
|
-
if (!match?.[1]) {
|
|
441
|
-
throw new Error(`Unable to parse album key from URI: ${uri}`);
|
|
442
|
-
}
|
|
443
|
-
return match[1];
|
|
444
|
-
}
|
|
445
|
-
function mapAlbumImage(albumImage, portfolioSourceId, sort) {
|
|
446
|
-
const image = albumImage.Image;
|
|
447
|
-
const metadata = image?.ImageMetadata ?? albumImage.ImageMetadata;
|
|
448
|
-
const originalUrl = image?.ImageSizeDetails?.OriginalImageUrl ?? albumImage.LargestImage?.Url ?? albumImage.WebUri;
|
|
449
|
-
const fileName = image?.FileName ?? albumImage.FileName;
|
|
450
|
-
return {
|
|
451
|
-
sourceId: albumImage.ImageKey,
|
|
452
|
-
portfolioSourceId,
|
|
453
|
-
sort,
|
|
454
|
-
fileName,
|
|
455
|
-
originalUrl,
|
|
456
|
-
caption: albumImage.Caption ?? image?.Caption,
|
|
457
|
-
keywords: image?.KeywordsArray?.length ? image.KeywordsArray : void 0,
|
|
458
|
-
exif: metadata ? {
|
|
459
|
-
iso: metadata.ISO,
|
|
460
|
-
aperture: metadata.Aperture ?? metadata.ApertureValue,
|
|
461
|
-
shutter: metadata.ShutterSpeed ?? metadata.ExposureTime,
|
|
462
|
-
focalLength: metadata.FocalLength
|
|
463
|
-
} : void 0
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
var SmugMugApiClient = class {
|
|
467
|
-
credentials;
|
|
468
|
-
pageSize;
|
|
469
|
-
maxRetries;
|
|
470
|
-
retryBaseDelayMs;
|
|
471
|
-
maxRetryDelayMs;
|
|
472
|
-
requestIntervalMs;
|
|
473
|
-
fetchImpl;
|
|
474
|
-
lastRequestAt = 0;
|
|
475
|
-
constructor(options) {
|
|
476
|
-
const parsed = smugMugClientOptionsSchema.parse(options);
|
|
477
|
-
this.credentials = parsed.credentials;
|
|
478
|
-
this.pageSize = parsed.pageSize;
|
|
479
|
-
this.maxRetries = parsed.maxRetries;
|
|
480
|
-
this.retryBaseDelayMs = parsed.retryBaseDelayMs;
|
|
481
|
-
this.maxRetryDelayMs = parsed.maxRetryDelayMs;
|
|
482
|
-
this.requestIntervalMs = parsed.requestIntervalMs;
|
|
483
|
-
this.fetchImpl = parsed.fetchImpl ?? fetch;
|
|
484
|
-
}
|
|
485
|
-
/** Validate credentials against `GET /user/!authuser`. */
|
|
486
|
-
async validateCredentials() {
|
|
487
|
-
const user = await this.getAuthUser();
|
|
488
|
-
return { nick: user.NickName, rootNodeUri: user.Uris.Node };
|
|
489
|
-
}
|
|
490
|
-
/** Crawl the authenticated user's node tree into flat export tables for `parse-node.ts`. */
|
|
491
|
-
async crawlExport() {
|
|
492
|
-
const user = await this.getAuthUser();
|
|
493
|
-
const folders = [];
|
|
494
|
-
const albums = [];
|
|
495
|
-
const images = [];
|
|
496
|
-
await this.walkNode(user.Uris.Node, void 0, folders, albums, images);
|
|
497
|
-
return {
|
|
498
|
-
exportVersion: 1,
|
|
499
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
500
|
-
Folders: folders,
|
|
501
|
-
Albums: albums,
|
|
502
|
-
Images: images
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
async getAuthUser() {
|
|
506
|
-
const envelope = await this.requestJson(`${SMUGMUG_API_BASE}/user/!authuser`);
|
|
507
|
-
return envelope.Response;
|
|
508
|
-
}
|
|
509
|
-
async walkNode(nodeUri, parentFolderId, folders, albums, images) {
|
|
510
|
-
const childrenPath = `${nodeUri}!children`;
|
|
511
|
-
for await (const child of this.paginateNodes(childrenPath)) {
|
|
512
|
-
if (child.Type === "Page") continue;
|
|
513
|
-
if (child.Type === "Folder") {
|
|
514
|
-
folders.push({
|
|
515
|
-
sourceId: child.NodeID,
|
|
516
|
-
name: child.Name,
|
|
517
|
-
parentSourceId: parentFolderId,
|
|
518
|
-
slug: child.UrlName,
|
|
519
|
-
description: child.Description
|
|
520
|
-
});
|
|
521
|
-
await this.walkNode(child.Uri, child.NodeID, folders, albums, images);
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
if (child.Type === "Album") {
|
|
525
|
-
albums.push({
|
|
526
|
-
sourceId: child.NodeID,
|
|
527
|
-
name: child.Name,
|
|
528
|
-
parentSourceId: parentFolderId,
|
|
529
|
-
slug: child.UrlName,
|
|
530
|
-
description: child.Description,
|
|
531
|
-
url: child.WebUri
|
|
532
|
-
});
|
|
533
|
-
const albumUri = child.Uris?.Album;
|
|
534
|
-
if (albumUri) {
|
|
535
|
-
await this.collectAlbumImages(albumUri, child.NodeID, images);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
async collectAlbumImages(albumUri, portfolioSourceId, images) {
|
|
541
|
-
const albumKey = albumKeyFromUri(albumUri);
|
|
542
|
-
const configQuery = `_config=${encodeURIComponent(JSON.stringify(ALBUM_IMAGES_CONFIG))}`;
|
|
543
|
-
const initialPath = `${SMUGMUG_API_BASE}/album/${albumKey}!images?${configQuery}`;
|
|
544
|
-
let sort = 0;
|
|
545
|
-
for await (const albumImage of this.paginateAlbumImages(initialPath)) {
|
|
546
|
-
images.push(mapAlbumImage(albumImage, portfolioSourceId, sort));
|
|
547
|
-
sort += 1;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
async *paginateNodes(path) {
|
|
551
|
-
for await (const page of this.paginate(path)) {
|
|
552
|
-
for (const node of page.Node ?? []) {
|
|
553
|
-
yield node;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
async *paginateAlbumImages(path) {
|
|
558
|
-
for await (const page of this.paginate(path)) {
|
|
559
|
-
for (const albumImage of page.AlbumImage ?? []) {
|
|
560
|
-
yield albumImage;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
async *paginate(initialPath) {
|
|
565
|
-
let nextPath = appendPagination(initialPath, this.pageSize, 1);
|
|
566
|
-
while (nextPath) {
|
|
567
|
-
const envelope = await this.requestJson(nextPath);
|
|
568
|
-
yield envelope.Response;
|
|
569
|
-
nextPath = envelope.Response.Pages?.NextPage;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
async requestJson(pathOrUrl) {
|
|
573
|
-
const url = toAbsoluteUrl(pathOrUrl);
|
|
574
|
-
const response = await this.requestWithRetry(url);
|
|
575
|
-
const body = await response.json();
|
|
576
|
-
if (body.Code !== 200) {
|
|
577
|
-
throw new Error(`SmugMug API error ${body.Code}: ${body.Message}`);
|
|
578
|
-
}
|
|
579
|
-
return body;
|
|
580
|
-
}
|
|
581
|
-
async requestWithRetry(url) {
|
|
582
|
-
let attempt = 0;
|
|
583
|
-
while (true) {
|
|
584
|
-
await this.throttle();
|
|
585
|
-
const authorization = buildSmugMugAuthorizationHeader({
|
|
586
|
-
method: "GET",
|
|
587
|
-
url: url.toString(),
|
|
588
|
-
credentials: this.credentials
|
|
589
|
-
});
|
|
590
|
-
const response = await this.fetchImpl(url, {
|
|
591
|
-
method: "GET",
|
|
592
|
-
headers: {
|
|
593
|
-
Accept: "application/json",
|
|
594
|
-
Authorization: authorization
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
if (response.ok) {
|
|
598
|
-
return response;
|
|
599
|
-
}
|
|
600
|
-
const retryable = response.status === 429 || response.status >= 500;
|
|
601
|
-
if (!retryable || attempt >= this.maxRetries) {
|
|
602
|
-
const detail = await response.text().catch(() => "");
|
|
603
|
-
throw new Error(
|
|
604
|
-
`SmugMug HTTP ${response.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
const retryAfter = Number.parseInt(response.headers.get("retry-after") ?? "", 10);
|
|
608
|
-
const delay = Number.isFinite(retryAfter) ? retryAfter * 1e3 : Math.min(this.maxRetryDelayMs, this.retryBaseDelayMs * 2 ** attempt);
|
|
609
|
-
await sleep(delay);
|
|
610
|
-
attempt += 1;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
async throttle() {
|
|
614
|
-
if (this.requestIntervalMs <= 0) return;
|
|
615
|
-
const elapsed = Date.now() - this.lastRequestAt;
|
|
616
|
-
if (elapsed < this.requestIntervalMs) {
|
|
617
|
-
await sleep(this.requestIntervalMs - elapsed);
|
|
618
|
-
}
|
|
619
|
-
this.lastRequestAt = Date.now();
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
function toAbsoluteUrl(pathOrUrl) {
|
|
623
|
-
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
624
|
-
return new URL(pathOrUrl);
|
|
625
|
-
}
|
|
626
|
-
if (pathOrUrl.startsWith("/")) {
|
|
627
|
-
return new URL(`https://${SMUGMUG_API_HOST}${pathOrUrl}`);
|
|
628
|
-
}
|
|
629
|
-
return new URL(pathOrUrl);
|
|
630
|
-
}
|
|
631
|
-
function appendPagination(pathOrUrl, count, start) {
|
|
632
|
-
const url = toAbsoluteUrl(pathOrUrl);
|
|
633
|
-
url.searchParams.set("count", String(count));
|
|
634
|
-
url.searchParams.set("start", String(start));
|
|
635
|
-
return url.toString();
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// src/parsers/smugmug/parse-node.ts
|
|
639
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
640
|
-
var PLATFORM2 = "smugmug";
|
|
641
|
-
var UNRESOLVED_URL_PREFIX = "unspecified://smugmug/";
|
|
642
|
-
function sourceMeta2(id, url, exportedAt) {
|
|
643
|
-
return {
|
|
644
|
-
platform: PLATFORM2,
|
|
645
|
-
id,
|
|
646
|
-
url,
|
|
647
|
-
exportedAt
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
function guessMime2(filename) {
|
|
651
|
-
const ext = filename.split(".").pop()?.toLowerCase();
|
|
652
|
-
const map = {
|
|
653
|
-
jpg: "image/jpeg",
|
|
654
|
-
jpeg: "image/jpeg",
|
|
655
|
-
png: "image/png",
|
|
656
|
-
gif: "image/gif",
|
|
657
|
-
webp: "image/webp",
|
|
658
|
-
tif: "image/tiff",
|
|
659
|
-
tiff: "image/tiff"
|
|
660
|
-
};
|
|
661
|
-
return ext ? map[ext] : void 0;
|
|
662
|
-
}
|
|
663
|
-
function parseExifNumber(value) {
|
|
664
|
-
if (value === void 0) return void 0;
|
|
665
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
666
|
-
const parsed = Number.parseFloat(String(value).replace(/[^0-9.]/g, ""));
|
|
667
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
668
|
-
}
|
|
669
|
-
function normalizeExif(exif) {
|
|
670
|
-
if (!exif || Object.keys(exif).length === 0) return void 0;
|
|
671
|
-
const normalized = {
|
|
672
|
-
iso: parseExifNumber(exif.iso),
|
|
673
|
-
aperture: parseExifNumber(exif.aperture),
|
|
674
|
-
shutter: exif.shutter,
|
|
675
|
-
focalLength: parseExifNumber(exif.focalLength)
|
|
676
|
-
};
|
|
677
|
-
if (normalized.iso === void 0 && normalized.aperture === void 0 && !normalized.shutter && normalized.focalLength === void 0) {
|
|
678
|
-
return void 0;
|
|
679
|
-
}
|
|
680
|
-
return normalized;
|
|
681
|
-
}
|
|
682
|
-
function isSmugMugFlatExport(value) {
|
|
683
|
-
if (!value || typeof value !== "object") return false;
|
|
684
|
-
const record = value;
|
|
685
|
-
const version = record.exportVersion;
|
|
686
|
-
return (version === 1 || version === "1") && Array.isArray(record.Folders) && Array.isArray(record.Albums) && Array.isArray(record.Images);
|
|
687
|
-
}
|
|
688
|
-
function isSmugMugNestedExport(value) {
|
|
689
|
-
if (!value || typeof value !== "object") return false;
|
|
690
|
-
const record = value;
|
|
691
|
-
const version = record.exportVersion;
|
|
692
|
-
return (version === 1 || version === "1") && Array.isArray(record.folders);
|
|
693
|
-
}
|
|
694
|
-
async function loadSmugMugExport(options) {
|
|
695
|
-
if (options.data) return options.data;
|
|
696
|
-
if (!options.filePath) {
|
|
697
|
-
throw new Error("SmugMug parser requires filePath or data");
|
|
698
|
-
}
|
|
699
|
-
const raw = JSON.parse(await readFile2(options.filePath, "utf8"));
|
|
700
|
-
if (isSmugMugFlatExport(raw) || isSmugMugNestedExport(raw)) {
|
|
701
|
-
return raw;
|
|
702
|
-
}
|
|
703
|
-
throw new Error(
|
|
704
|
-
"Invalid SmugMug export: expected exportVersion 1 with folders[] (nested) or Folders/Albums/Images (flat)"
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
function resolveAssetUrl(image) {
|
|
708
|
-
if (image.originalUrl) return image.originalUrl;
|
|
709
|
-
return `${UNRESOLVED_URL_PREFIX}${image.sourceId}`;
|
|
710
|
-
}
|
|
711
|
-
function resolveFilename(image) {
|
|
712
|
-
if (image.fileName) return image.fileName;
|
|
713
|
-
return `${image.sourceId}.jpg`;
|
|
714
|
-
}
|
|
715
|
-
function* emitNestedFolderPortfolio(folder, exportedAt) {
|
|
716
|
-
yield {
|
|
717
|
-
type: "portfolio",
|
|
718
|
-
source: sourceMeta2(folder.id, void 0, exportedAt),
|
|
719
|
-
sourceId: folder.id,
|
|
720
|
-
title: folder.name,
|
|
721
|
-
slug: sanitizeSlug(folder.slug ?? folder.name),
|
|
722
|
-
description: folder.description
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
function* emitNestedAlbumPortfolio(folder, album, exportedAt) {
|
|
726
|
-
yield {
|
|
727
|
-
type: "portfolio",
|
|
728
|
-
source: sourceMeta2(album.id, album.url, exportedAt),
|
|
729
|
-
sourceId: album.id,
|
|
730
|
-
title: album.name,
|
|
731
|
-
slug: sanitizeSlug(album.slug ?? album.name),
|
|
732
|
-
description: album.description,
|
|
733
|
-
parentSourceId: folder.id
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
function* emitNestedAlbumAssets(album, exportedAt) {
|
|
737
|
-
for (let index = 0; index < album.images.length; index++) {
|
|
738
|
-
const image = album.images[index];
|
|
739
|
-
yield {
|
|
740
|
-
type: "asset",
|
|
741
|
-
source: sourceMeta2(image.id, image.originalUrl, exportedAt),
|
|
742
|
-
sourceId: image.id,
|
|
743
|
-
sourceUrl: image.originalUrl,
|
|
744
|
-
filename: image.fileName,
|
|
745
|
-
mimeType: guessMime2(image.fileName),
|
|
746
|
-
caption: image.caption,
|
|
747
|
-
keywords: image.keywords?.length ? image.keywords : void 0,
|
|
748
|
-
exif: normalizeExif(image.exif),
|
|
749
|
-
portfolioSourceId: album.id,
|
|
750
|
-
sort: index
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
async function* enumerateNestedExport(doc) {
|
|
755
|
-
const exportedAt = doc.exportedAt;
|
|
756
|
-
for (const folder of doc.folders) {
|
|
757
|
-
yield* emitNestedFolderPortfolio(folder, exportedAt);
|
|
758
|
-
for (const album of folder.albums) {
|
|
759
|
-
yield* emitNestedAlbumPortfolio(folder, album, exportedAt);
|
|
760
|
-
yield* emitNestedAlbumAssets(album, exportedAt);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
async function* enumerateFlatExport(doc) {
|
|
765
|
-
const exportedAt = doc.exportedAt;
|
|
766
|
-
for (const folder of doc.Folders) {
|
|
767
|
-
yield {
|
|
768
|
-
type: "portfolio",
|
|
769
|
-
source: sourceMeta2(folder.sourceId, void 0, exportedAt),
|
|
770
|
-
sourceId: folder.sourceId,
|
|
771
|
-
title: folder.name,
|
|
772
|
-
slug: sanitizeSlug(folder.slug ?? folder.name),
|
|
773
|
-
description: folder.description,
|
|
774
|
-
parentSourceId: folder.parentSourceId
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
for (const album of doc.Albums) {
|
|
778
|
-
yield {
|
|
779
|
-
type: "portfolio",
|
|
780
|
-
source: sourceMeta2(album.sourceId, album.url, exportedAt),
|
|
781
|
-
sourceId: album.sourceId,
|
|
782
|
-
title: album.name,
|
|
783
|
-
slug: sanitizeSlug(album.slug ?? album.name),
|
|
784
|
-
description: album.description,
|
|
785
|
-
parentSourceId: album.parentSourceId
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
for (const image of doc.Images) {
|
|
789
|
-
const filename = resolveFilename(image);
|
|
790
|
-
yield {
|
|
791
|
-
type: "asset",
|
|
792
|
-
source: sourceMeta2(image.sourceId, image.originalUrl, exportedAt),
|
|
793
|
-
sourceId: image.sourceId,
|
|
794
|
-
sourceUrl: resolveAssetUrl(image),
|
|
795
|
-
filename,
|
|
796
|
-
mimeType: guessMime2(filename),
|
|
797
|
-
caption: image.caption,
|
|
798
|
-
keywords: image.keywords?.length ? image.keywords : void 0,
|
|
799
|
-
exif: normalizeExif(image.exif),
|
|
800
|
-
portfolioSourceId: image.portfolioSourceId,
|
|
801
|
-
sort: image.sort ?? 0
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
async function resolveSmugMugDocument(options) {
|
|
806
|
-
if (options.data) return options.data;
|
|
807
|
-
if (options.client) return options.client.crawlExport();
|
|
808
|
-
if (options.credentials) {
|
|
809
|
-
const client = new SmugMugApiClient({ credentials: options.credentials, ...options.clientOptions });
|
|
810
|
-
return client.crawlExport();
|
|
811
|
-
}
|
|
812
|
-
return loadSmugMugExport(options);
|
|
813
|
-
}
|
|
814
|
-
async function* enumerateSmugMugEntities(options) {
|
|
815
|
-
const doc = await resolveSmugMugDocument(options);
|
|
816
|
-
if (isSmugMugFlatExport(doc)) {
|
|
817
|
-
yield* enumerateFlatExport(doc);
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
yield* enumerateNestedExport(doc);
|
|
821
|
-
}
|
|
822
|
-
function summarizeSmugMugExport(doc) {
|
|
823
|
-
if (isSmugMugFlatExport(doc)) {
|
|
824
|
-
return {
|
|
825
|
-
folders: doc.Folders.length,
|
|
826
|
-
albums: doc.Albums.length,
|
|
827
|
-
assets: doc.Images.length,
|
|
828
|
-
portfolios: doc.Folders.length + doc.Albums.length
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
const folders = doc.folders.length;
|
|
832
|
-
let albums = 0;
|
|
833
|
-
let assets = 0;
|
|
834
|
-
for (const folder of doc.folders) {
|
|
835
|
-
albums += folder.albums.length;
|
|
836
|
-
for (const album of folder.albums) {
|
|
837
|
-
assets += album.images.length;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
folders,
|
|
842
|
-
albums,
|
|
843
|
-
assets,
|
|
844
|
-
portfolios: folders + albums
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
async function validateSmugMugExportFile(filePath) {
|
|
848
|
-
const issues = [];
|
|
849
|
-
let doc;
|
|
850
|
-
try {
|
|
851
|
-
doc = await loadSmugMugExport({ filePath });
|
|
852
|
-
} catch (error) {
|
|
853
|
-
return {
|
|
854
|
-
ok: false,
|
|
855
|
-
issues: [
|
|
856
|
-
{
|
|
857
|
-
code: "invalid_export",
|
|
858
|
-
message: error instanceof Error ? error.message : String(error)
|
|
859
|
-
}
|
|
860
|
-
],
|
|
861
|
-
summary: {}
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
if (isSmugMugFlatExport(doc)) {
|
|
865
|
-
if (doc.Folders.length === 0 && doc.Albums.length === 0) {
|
|
866
|
-
issues.push({ code: "empty_export", message: "No folders or albums in export" });
|
|
867
|
-
}
|
|
868
|
-
} else if (doc.folders.length === 0) {
|
|
869
|
-
issues.push({ code: "empty_export", message: "No folders in export" });
|
|
870
|
-
}
|
|
871
|
-
const summary = summarizeSmugMugExport(doc);
|
|
872
|
-
return {
|
|
873
|
-
ok: issues.length === 0,
|
|
874
|
-
issues,
|
|
875
|
-
summary: {
|
|
876
|
-
portfolios: summary.portfolios,
|
|
877
|
-
assets: summary.assets,
|
|
878
|
-
categories: summary.folders,
|
|
879
|
-
posts: 0,
|
|
880
|
-
pages: 0,
|
|
881
|
-
tags: 0
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// src/parsers/smugmug/index.ts
|
|
887
|
-
function resolveInput(input) {
|
|
888
|
-
if (typeof input === "string") return { path: input };
|
|
889
|
-
if (input && typeof input === "object") {
|
|
890
|
-
const record = input;
|
|
891
|
-
if (record.client || record.credentials || record.live) return record;
|
|
892
|
-
if (record.data) return { data: record.data };
|
|
893
|
-
if (record.path) return { path: record.path };
|
|
894
|
-
}
|
|
895
|
-
throw new Error(
|
|
896
|
-
"SmugMug adapter requires input path (string or { path }), { data }, { credentials }, { client }, or { live: true }"
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
function resolveLiveCredentials(input) {
|
|
900
|
-
if (input.credentials) return input.credentials;
|
|
901
|
-
if (input.live) return readSmugMugCredentialsFromEnv();
|
|
902
|
-
return void 0;
|
|
903
|
-
}
|
|
904
|
-
var smugmugAdapter = {
|
|
905
|
-
platform: "smugmug",
|
|
906
|
-
async validateInput(input) {
|
|
907
|
-
try {
|
|
908
|
-
const resolved = resolveInput(input);
|
|
909
|
-
const credentials = resolveLiveCredentials(resolved);
|
|
910
|
-
if (resolved.data) {
|
|
911
|
-
const summary = summarizeSmugMugExport(resolved.data);
|
|
912
|
-
return {
|
|
913
|
-
ok: true,
|
|
914
|
-
issues: [],
|
|
915
|
-
summary: {
|
|
916
|
-
portfolios: summary.portfolios,
|
|
917
|
-
assets: summary.assets,
|
|
918
|
-
categories: summary.folders,
|
|
919
|
-
posts: 0,
|
|
920
|
-
pages: 0,
|
|
921
|
-
tags: 0
|
|
922
|
-
}
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
if (resolved.client || credentials) {
|
|
926
|
-
const client = resolved.client ?? new SmugMugApiClient({ credentials, ...resolved.clientOptions });
|
|
927
|
-
await client.validateCredentials();
|
|
928
|
-
const doc = await client.crawlExport();
|
|
929
|
-
const summary = summarizeSmugMugExport(doc);
|
|
930
|
-
return {
|
|
931
|
-
ok: true,
|
|
932
|
-
issues: [],
|
|
933
|
-
summary: {
|
|
934
|
-
portfolios: summary.portfolios,
|
|
935
|
-
assets: summary.assets,
|
|
936
|
-
categories: summary.folders,
|
|
937
|
-
posts: 0,
|
|
938
|
-
pages: 0,
|
|
939
|
-
tags: 0
|
|
940
|
-
}
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
const result = await validateSmugMugExportFile(resolved.path);
|
|
944
|
-
return {
|
|
945
|
-
ok: result.ok,
|
|
946
|
-
issues: result.issues,
|
|
947
|
-
summary: result.summary
|
|
948
|
-
};
|
|
949
|
-
} catch (error) {
|
|
950
|
-
return {
|
|
951
|
-
ok: false,
|
|
952
|
-
issues: [
|
|
953
|
-
{
|
|
954
|
-
code: "invalid_input",
|
|
955
|
-
message: error instanceof Error ? error.message : String(error)
|
|
956
|
-
}
|
|
957
|
-
]
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
enumerateEntities(ctx) {
|
|
962
|
-
const resolved = resolveInput(ctx.input);
|
|
963
|
-
const credentials = resolveLiveCredentials(resolved);
|
|
964
|
-
return enumerateSmugMugEntities({
|
|
965
|
-
filePath: resolved.path,
|
|
966
|
-
data: resolved.data,
|
|
967
|
-
client: resolved.client,
|
|
968
|
-
credentials,
|
|
969
|
-
clientOptions: resolved.clientOptions
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
// src/parsers/squarespace/index.ts
|
|
975
|
-
function resolveInput2(input) {
|
|
976
|
-
if (typeof input === "string") return { path: input };
|
|
977
|
-
if (input && typeof input === "object") {
|
|
978
|
-
const record = input;
|
|
979
|
-
if (record.client || record.collectTargets) return record;
|
|
980
|
-
if (record.data) return { data: record.data };
|
|
981
|
-
if (record.path) return { path: record.path };
|
|
982
|
-
}
|
|
983
|
-
throw new Error(
|
|
984
|
-
"Squarespace adapter requires input path (string or { path }), { data }, { client, collectTargets }, or { collectTargets }"
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
var squarespaceAdapter = {
|
|
988
|
-
platform: "squarespace",
|
|
989
|
-
async validateInput(input) {
|
|
990
|
-
try {
|
|
991
|
-
const resolved = resolveInput2(input);
|
|
992
|
-
if (resolved.data) {
|
|
993
|
-
const summary = summarizeSquarespaceExport(resolved.data);
|
|
994
|
-
return {
|
|
995
|
-
ok: true,
|
|
996
|
-
issues: [],
|
|
997
|
-
summary: {
|
|
998
|
-
pages: summary.pages,
|
|
999
|
-
posts: summary.posts,
|
|
1000
|
-
categories: summary.categories,
|
|
1001
|
-
tags: summary.tags
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
if (resolved.client || resolved.collectTargets?.length) {
|
|
1006
|
-
if (!resolved.collectTargets?.length) {
|
|
1007
|
-
throw new Error("Squarespace live validation requires collectTargets");
|
|
1008
|
-
}
|
|
1009
|
-
const client = resolved.client ?? new SquarespaceCollectionClient(resolved.clientOptions);
|
|
1010
|
-
const doc = await client.collectExport(resolved.collectTargets);
|
|
1011
|
-
const summary = summarizeSquarespaceExport(doc);
|
|
1012
|
-
return {
|
|
1013
|
-
ok: true,
|
|
1014
|
-
issues: [],
|
|
1015
|
-
summary: {
|
|
1016
|
-
pages: summary.pages,
|
|
1017
|
-
posts: summary.posts,
|
|
1018
|
-
categories: summary.categories,
|
|
1019
|
-
tags: summary.tags
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
const result = await validateSquarespaceExportFile(resolved.path);
|
|
1024
|
-
return {
|
|
1025
|
-
ok: result.ok,
|
|
1026
|
-
issues: result.issues,
|
|
1027
|
-
summary: result.summary
|
|
1028
|
-
};
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
return {
|
|
1031
|
-
ok: false,
|
|
1032
|
-
issues: [
|
|
1033
|
-
{
|
|
1034
|
-
code: "invalid_input",
|
|
1035
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1036
|
-
}
|
|
1037
|
-
]
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
},
|
|
1041
|
-
enumerateEntities(ctx) {
|
|
1042
|
-
const resolved = resolveInput2(ctx.input);
|
|
1043
|
-
return enumerateSquarespaceEntities({
|
|
1044
|
-
filePath: resolved.path,
|
|
1045
|
-
data: resolved.data,
|
|
1046
|
-
client: resolved.client,
|
|
1047
|
-
collectTargets: resolved.collectTargets,
|
|
1048
|
-
clientOptions: resolved.clientOptions
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1053
|
-
// src/parsers/index.ts
|
|
1054
|
-
var adapters = {
|
|
1055
|
-
wordpress: wordpressAdapter,
|
|
1056
|
-
smugmug: smugmugAdapter,
|
|
1057
|
-
squarespace: squarespaceAdapter
|
|
1058
|
-
};
|
|
1059
|
-
function getAdapter(platform) {
|
|
1060
|
-
return adapters[platform];
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
export {
|
|
1064
|
-
wordpressAdapter,
|
|
1065
|
-
SMUGMUG_API_BASE,
|
|
1066
|
-
SMUGMUG_OAUTH_ENDPOINTS,
|
|
1067
|
-
smugMugCredentialsSchema,
|
|
1068
|
-
signSmugMugOAuthRequest,
|
|
1069
|
-
buildSmugMugAuthorizationHeader,
|
|
1070
|
-
readSmugMugCredentialsFromEnv,
|
|
1071
|
-
SmugMugApiClient,
|
|
1072
|
-
smugmugAdapter,
|
|
1073
|
-
squarespaceAdapter,
|
|
1074
|
-
getAdapter
|
|
1075
|
-
};
|
|
1076
|
-
//# sourceMappingURL=chunk-FXXKLYO5.js.map
|