@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parsers/wordpress/parse-wxr.ts","../src/parsers/wordpress/index.ts","../src/parsers/smugmug/api.ts","../src/parsers/smugmug/parse-node.ts","../src/parsers/smugmug/index.ts","../src/parsers/squarespace/index.ts","../src/parsers/index.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nimport { XMLParser } from \"fast-xml-parser\";\n\nimport { discoverContentAssetUrls } from \"../../lib/content-asset-urls.js\";\nimport { linkToPath, sanitizeSlug } from \"../../lib/utility.js\";\nimport type {\n NormalizedAsset,\n NormalizedCategory,\n NormalizedEntity,\n NormalizedPage,\n NormalizedPost,\n NormalizedTag,\n PublishStatus,\n SourceMetadata,\n} from \"../../normalizer/types.js\";\n\nconst PLATFORM = \"wordpress\" as const;\n\nexport interface WxrParseOptions {\n filePath: string;\n exportedAt?: string;\n}\n\ninterface WxrItem {\n title?: string;\n link?: string;\n encoded?: string;\n post_id?: string | number;\n post_date?: string;\n post_name?: string;\n status?: string;\n post_type?: string;\n attachment_url?: string;\n postmeta?: WxrPostMeta | WxrPostMeta[];\n category?: WxrCategory | WxrCategory[];\n}\n\ninterface WxrPostMeta {\n meta_key?: string;\n meta_value?: string | number;\n}\n\ninterface WxrCategory {\n \"@_domain\"?: string;\n \"@_nicename\"?: string;\n \"#text\"?: string;\n}\n\ninterface AttachmentIndexEntry {\n sourceUrl: string;\n filename: string;\n mimeType?: string;\n title?: string;\n}\n\nfunction asArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) return [];\n return Array.isArray(value) ? value : [value];\n}\n\nfunction textValue(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"string\" || typeof value === \"number\") return String(value);\n if (typeof value === \"object\" && value !== null && \"#text\" in value) {\n return String((value as { \"#text\": unknown })[\"#text\"] ?? \"\");\n }\n return String(value);\n}\n\nfunction mapPublishStatus(wpStatus: string | undefined): PublishStatus {\n switch ((wpStatus ?? \"\").toLowerCase()) {\n case \"publish\":\n return \"published\";\n case \"draft\":\n case \"pending\":\n return \"draft\";\n default:\n return \"archived\";\n }\n}\n\nfunction getContentEncoded(item: WxrItem): string {\n const content = (item as { content?: { encoded?: string } | string }).content;\n if (content !== undefined) {\n if (typeof content === \"string\") return content;\n return textValue(content.encoded);\n }\n return textValue(item.encoded);\n}\n\nfunction sourceMeta(id: string, link?: string, exportedAt?: string): SourceMetadata {\n return {\n platform: PLATFORM,\n id,\n url: link || undefined,\n path: linkToPath(link),\n exportedAt,\n };\n}\n\nfunction getExcerpt(item: WxrItem): string {\n const excerpt = (item as { excerpt?: { encoded?: string } | string }).excerpt;\n if (!excerpt) return \"\";\n if (typeof excerpt === \"string\") return excerpt;\n return textValue(excerpt.encoded);\n}\n\nfunction getPostMeta(item: WxrItem, key: string): string | undefined {\n for (const meta of asArray(item.postmeta)) {\n if (textValue(meta.meta_key) === key) {\n return textValue(meta.meta_value);\n }\n }\n return undefined;\n}\n\nfunction parseItems(xml: string): WxrItem[] {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n removeNSPrefix: true,\n trimValues: false,\n parseTagValue: false,\n });\n\n const doc = parser.parse(xml) as {\n rss?: { channel?: { item?: WxrItem | WxrItem[] } };\n };\n\n return asArray(doc.rss?.channel?.item);\n}\n\nfunction buildAttachmentIndex(items: WxrItem[]): Map<string, AttachmentIndexEntry> {\n const index = new Map<string, AttachmentIndexEntry>();\n\n for (const item of items) {\n if (textValue(item.post_type) !== \"attachment\") continue;\n const id = textValue(item.post_id);\n const url = textValue(item.attachment_url) || textValue(item.link);\n if (!id || !url) continue;\n\n const filename = basename(new URL(url, \"http://local.invalid\").pathname) || `attachment-${id}`;\n index.set(id, {\n sourceUrl: url,\n filename,\n mimeType: getPostMeta(item, \"_wp_attached_file\") ? undefined : guessMime(filename),\n title: textValue(item.title),\n });\n }\n\n return index;\n}\n\nfunction guessMime(filename: string): string | undefined {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const map: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n pdf: \"application/pdf\",\n };\n return ext ? map[ext] : undefined;\n}\n\nfunction collectTaxonomies(items: WxrItem[]): {\n categories: Map<string, NormalizedCategory>;\n tags: Map<string, NormalizedTag>;\n} {\n const categories = new Map<string, NormalizedCategory>();\n const tags = new Map<string, NormalizedTag>();\n\n for (const item of items) {\n const postType = textValue(item.post_type);\n if (postType !== \"post\" && postType !== \"page\") continue;\n\n for (const cat of asArray(item.category)) {\n const domain = cat[\"@_domain\"] ?? \"\";\n const nicename = sanitizeSlug(cat[\"@_nicename\"] ?? textValue(cat[\"#text\"]));\n const name = textValue(cat[\"#text\"]) || nicename;\n if (!nicename) continue;\n\n if (domain === \"category\") {\n if (!categories.has(nicename)) {\n categories.set(nicename, {\n type: \"category\",\n source: sourceMeta(`cat:${nicename}`),\n sourceId: `cat:${nicename}`,\n name,\n slug: nicename,\n });\n }\n } else if (domain === \"post_tag\") {\n if (!tags.has(nicename)) {\n tags.set(nicename, {\n type: \"tag\",\n source: sourceMeta(`tag:${nicename}`),\n sourceId: `tag:${nicename}`,\n name,\n slug: nicename,\n });\n }\n }\n }\n }\n\n return { categories, tags };\n}\n\nfunction collectInlineAssets(\n html: string,\n attachmentIndex: Map<string, AttachmentIndexEntry>,\n seenUrls: Set<string>,\n exportedAt?: string,\n): NormalizedAsset[] {\n const assets: NormalizedAsset[] = [];\n for (const src of discoverContentAssetUrls(html)) {\n if (seenUrls.has(src)) continue;\n seenUrls.add(src);\n\n let filename: string;\n try {\n filename = basename(new URL(src, \"http://local.invalid\").pathname) || \"inline-asset\";\n } catch {\n filename = \"inline-asset\";\n }\n\n assets.push({\n type: \"asset\",\n source: sourceMeta(`url:${src}`, src, exportedAt),\n sourceId: `url:${src}`,\n sourceUrl: src,\n filename,\n mimeType: guessMime(filename),\n });\n }\n\n // Resolve attachment-index URLs referenced in content if not already seen\n for (const [id, entry] of attachmentIndex) {\n if (seenUrls.has(entry.sourceUrl)) continue;\n // Only auto-include attachments referenced via wp-content in posts is handled by inline src\n void id;\n }\n\n return assets;\n}\n\nexport async function* enumerateWxrEntities(\n options: WxrParseOptions,\n): AsyncGenerator<NormalizedEntity> {\n const xml = await readFile(options.filePath, \"utf8\");\n const items = parseItems(xml);\n const attachmentIndex = buildAttachmentIndex(items);\n const { categories, tags } = collectTaxonomies(items);\n const seenAssetUrls = new Set<string>();\n const emittedAttachmentIds = new Set<string>();\n\n for (const category of categories.values()) {\n yield category;\n }\n for (const tag of tags.values()) {\n yield tag;\n }\n\n // Emit attachment assets\n for (const [id, entry] of attachmentIndex) {\n emittedAttachmentIds.add(id);\n seenAssetUrls.add(entry.sourceUrl);\n yield {\n type: \"asset\",\n source: sourceMeta(id, entry.sourceUrl, options.exportedAt),\n sourceId: id,\n sourceUrl: entry.sourceUrl,\n filename: entry.filename,\n mimeType: entry.mimeType,\n caption: entry.title,\n } satisfies NormalizedAsset;\n }\n\n for (const item of items) {\n const postType = textValue(item.post_type);\n if (postType !== \"post\" && postType !== \"page\") continue;\n\n const id = textValue(item.post_id);\n const link = textValue(item.link);\n const slug = sanitizeSlug(textValue(item.post_name) || textValue(item.title) || id);\n const rawHtml = getContentEncoded(item);\n\n for (const asset of collectInlineAssets(\n rawHtml,\n attachmentIndex,\n seenAssetUrls,\n options.exportedAt,\n )) {\n yield asset;\n }\n\n const categorySlugs: string[] = [];\n const tagSlugs: string[] = [];\n for (const cat of asArray(item.category)) {\n const domain = cat[\"@_domain\"] ?? \"\";\n const nicename = sanitizeSlug(cat[\"@_nicename\"] ?? textValue(cat[\"#text\"]));\n if (!nicename) continue;\n if (domain === \"category\") categorySlugs.push(nicename);\n if (domain === \"post_tag\") tagSlugs.push(nicename);\n }\n\n if (postType === \"post\") {\n const thumbnailId = getPostMeta(item, \"_thumbnail_id\");\n let featuredAssetSourceId: string | undefined;\n if (thumbnailId && attachmentIndex.has(thumbnailId)) {\n featuredAssetSourceId = thumbnailId;\n }\n\n const post: NormalizedPost = {\n type: \"post\",\n source: sourceMeta(id, link, options.exportedAt),\n sourceId: id,\n title: textValue(item.title) || slug,\n slug,\n excerpt: getExcerpt(item) || undefined,\n contentHtml: rawHtml,\n publishedAt: textValue(item.post_date) || undefined,\n status: mapPublishStatus(textValue(item.status)),\n categorySlugs: categorySlugs.length ? categorySlugs : undefined,\n tagSlugs: tagSlugs.length ? tagSlugs : undefined,\n sourceFeaturedMediaId: thumbnailId,\n featuredAssetSourceId,\n };\n yield post;\n } else {\n const isHomePage =\n getPostMeta(item, \"_wp_show_on_front\") === \"1\" ||\n getPostMeta(item, \"page_on_front\") === \"1\";\n\n const page: NormalizedPage = {\n type: \"page\",\n source: sourceMeta(id, link, options.exportedAt),\n sourceId: id,\n title: textValue(item.title) || slug,\n slug,\n contentHtml: rawHtml,\n isHomePage: isHomePage || undefined,\n status: mapPublishStatus(textValue(item.status)),\n };\n yield page;\n }\n }\n}\n\nexport async function validateWxrFile(filePath: string): Promise<{\n ok: boolean;\n issues: { code: string; message: string }[];\n summary: Record<string, number>;\n}> {\n const issues: { code: string; message: string }[] = [];\n let xml: string;\n try {\n xml = await readFile(filePath, \"utf8\");\n } catch {\n return {\n ok: false,\n issues: [{ code: \"file_not_found\", message: `Cannot read file: ${filePath}` }],\n summary: {},\n };\n }\n\n const looksLikeWxr =\n xml.includes(\"<rss\") &&\n (xml.includes(\"wp:wxr_version\") ||\n xml.includes(\"xmlns:wp=\") ||\n xml.includes(\"WordPress eXtended RSS\"));\n if (!looksLikeWxr) {\n issues.push({ code: \"invalid_wxr\", message: \"File does not appear to be WordPress WXR\" });\n }\n\n const items = parseItems(xml);\n const summary = {\n posts: items.filter((i) => textValue(i.post_type) === \"post\").length,\n pages: items.filter((i) => textValue(i.post_type) === \"page\").length,\n assets: items.filter((i) => textValue(i.post_type) === \"attachment\").length,\n portfolios: 0,\n categories: 0,\n tags: 0,\n };\n\n const { categories, tags } = collectTaxonomies(items);\n summary.categories = categories.size;\n summary.tags = tags.size;\n\n return { ok: issues.length === 0, issues, summary };\n}\n","import type { AdapterContext, MigrationAdapter, ValidationResult } from \"../../normalizer/types.js\";\nimport { enumerateWxrEntities, validateWxrFile } from \"./parse-wxr.js\";\n\nfunction resolvePath(input: unknown): string {\n if (typeof input === \"string\") return input;\n if (input && typeof input === \"object\" && \"path\" in input) {\n return String((input as { path: string }).path);\n }\n throw new Error(\"WordPress adapter requires input path (string or { path })\");\n}\n\nexport const wordpressAdapter: MigrationAdapter = {\n platform: \"wordpress\",\n\n async validateInput(input: unknown): Promise<ValidationResult> {\n const path = resolvePath(input);\n const result = await validateWxrFile(path);\n return {\n ok: result.ok,\n issues: result.issues,\n summary: result.summary,\n };\n },\n\n enumerateEntities(ctx: AdapterContext) {\n const path = resolvePath(ctx.input);\n return enumerateWxrEntities({ filePath: path });\n },\n};\n","import { createHmac, randomBytes } from \"node:crypto\";\n\nimport { z } from \"zod\";\n\nimport type { SmugMugFlatAlbum, SmugMugFlatExport, SmugMugFlatFolder, SmugMugFlatImage } from \"./types.js\";\n\n/** SmugMug API v2 base (OAuth 1.0a). No secrets — inject credentials at runtime. */\nexport const SMUGMUG_API_HOST = \"api.smugmug.com\";\nexport const SMUGMUG_API_BASE = `https://${SMUGMUG_API_HOST}/api/v2`;\n\nexport const SMUGMUG_OAUTH_ENDPOINTS = {\n requestToken: \"https://api.smugmug.com/services/oauth/1.0a/getRequestToken\",\n authorize: \"https://api.smugmug.com/services/oauth/1.0a/authorize\",\n accessToken: \"https://api.smugmug.com/services/oauth/1.0a/getAccessToken\",\n} as const;\n\nexport const smugMugCredentialsSchema = z.object({\n consumerKey: z.string().min(1),\n consumerSecret: z.string().min(1),\n accessToken: z.string().min(1),\n accessTokenSecret: z.string().min(1),\n});\n\nexport type SmugMugCredentials = z.infer<typeof smugMugCredentialsSchema>;\n\nexport const smugMugClientOptionsSchema = z.object({\n credentials: smugMugCredentialsSchema,\n pageSize: z.number().int().min(1).max(500).default(100),\n maxRetries: z.number().int().min(0).max(10).default(3),\n retryBaseDelayMs: z.number().int().min(0).default(500),\n maxRetryDelayMs: z.number().int().min(0).default(8000),\n requestIntervalMs: z.number().int().min(0).default(200),\n fetchImpl: z.custom<typeof fetch>().optional(),\n});\n\nexport type SmugMugClientOptions = z.input<typeof smugMugClientOptionsSchema>;\n\nconst ALBUM_IMAGES_CONFIG = {\n expand: {\n AlbumImage: {\n expand: {\n Image: {\n filter: [\"FileName\", \"Caption\", \"KeywordsArray\"],\n filteruri: [\"ImageMetadata\", \"ImageSizeDetails\"],\n expand: {\n ImageMetadata: {\n filter: [\"ISO\", \"Aperture\", \"ApertureValue\", \"ShutterSpeed\", \"ExposureTime\", \"FocalLength\"],\n },\n ImageSizeDetails: {\n filter: [\"OriginalImageUrl\"],\n },\n },\n },\n },\n },\n },\n};\n\ninterface SmugMugPages {\n Total?: number;\n Start?: number;\n Count?: number;\n NextPage?: string;\n}\n\ninterface SmugMugApiEnvelope<T> {\n Response: T & { Pages?: SmugMugPages; Uri?: string };\n Code: number;\n Message: string;\n}\n\ninterface SmugMugUserWire {\n NickName?: string;\n Uri: string;\n Uris: { Node: string };\n}\n\ninterface SmugMugNodeWire {\n NodeID: string;\n Type: \"Folder\" | \"Album\" | \"Page\" | string;\n Name: string;\n Description?: string;\n UrlName?: string;\n WebUri?: string;\n Uri: string;\n Uris?: { Album?: string; ChildNodes?: string };\n}\n\ninterface SmugMugImageMetadataWire {\n ISO?: number | string;\n Aperture?: number | string;\n ApertureValue?: number | string;\n ShutterSpeed?: string;\n ExposureTime?: string;\n FocalLength?: number | string;\n}\n\ninterface SmugMugImageWire {\n FileName?: string;\n Caption?: string;\n KeywordsArray?: string[];\n ImageMetadata?: SmugMugImageMetadataWire;\n ImageSizeDetails?: { OriginalImageUrl?: string };\n}\n\ninterface SmugMugAlbumImageWire {\n ImageKey: string;\n Caption?: string;\n FileName?: string;\n WebUri?: string;\n Image?: SmugMugImageWire;\n LargestImage?: { Url?: string };\n ImageMetadata?: SmugMugImageMetadataWire;\n}\n\n/** RFC 3986 encoding used by OAuth 1.0a parameter normalization. */\nexport function oauthPercentEncode(value: string): string {\n return encodeURIComponent(value).replace(/[!'()*]/g, (char) =>\n `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nfunction normalizeRequestUrl(url: URL): string {\n const protocol = url.protocol.replace(/:$/, \"\").toLowerCase();\n const host = url.hostname.toLowerCase();\n const defaultPort = protocol === \"http\" ? \"80\" : \"443\";\n const port = url.port && url.port !== defaultPort ? `:${url.port}` : \"\";\n return `${protocol}://${host}${port}${url.pathname}`;\n}\n\nfunction sortedParameterString(params: Record<string, string>): string {\n return Object.keys(params)\n .sort((a, b) => (a === b ? 0 : a < b ? -1 : 1))\n .map((key) => `${oauthPercentEncode(key)}=${oauthPercentEncode(params[key]!)}`)\n .join(\"&\");\n}\n\nfunction collectSignatureParams(\n url: URL,\n oauthParams: Record<string, string>,\n bodyParams?: Record<string, string>,\n): Record<string, string> {\n const params: Record<string, string> = { ...oauthParams };\n url.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n if (bodyParams) {\n for (const [key, value] of Object.entries(bodyParams)) {\n params[key] = value;\n }\n }\n return params;\n}\n\n/** Build OAuth 1.0a HMAC-SHA1 signature for a SmugMug API request. */\nexport function signSmugMugOAuthRequest(input: {\n method: string;\n url: string;\n credentials: SmugMugCredentials;\n oauthParams: Record<string, string>;\n bodyParams?: Record<string, string>;\n}): string {\n const url = new URL(input.url);\n const parameterString = sortedParameterString(\n collectSignatureParams(url, input.oauthParams, input.bodyParams),\n );\n const signatureBase = [\n input.method.toUpperCase(),\n oauthPercentEncode(normalizeRequestUrl(url)),\n oauthPercentEncode(parameterString),\n ].join(\"&\");\n const signingKey = `${oauthPercentEncode(input.credentials.consumerSecret)}&${oauthPercentEncode(input.credentials.accessTokenSecret)}`;\n return createHmac(\"sha1\", signingKey).update(signatureBase).digest(\"base64\");\n}\n\nfunction buildOAuthParams(credentials: SmugMugCredentials, nonce: string, timestamp: string) {\n return {\n oauth_consumer_key: credentials.consumerKey,\n oauth_token: credentials.accessToken,\n oauth_signature_method: \"HMAC-SHA1\",\n oauth_timestamp: timestamp,\n oauth_nonce: nonce,\n oauth_version: \"1.0\",\n };\n}\n\nexport function buildSmugMugAuthorizationHeader(input: {\n method: string;\n url: string;\n credentials: SmugMugCredentials;\n nonce?: string;\n timestamp?: string;\n bodyParams?: Record<string, string>;\n}): string {\n const nonce = input.nonce ?? randomBytes(16).toString(\"hex\");\n const timestamp = input.timestamp ?? String(Math.floor(Date.now() / 1000));\n const oauthParams = buildOAuthParams(input.credentials, nonce, timestamp);\n const signature = signSmugMugOAuthRequest({\n method: input.method,\n url: input.url,\n credentials: input.credentials,\n oauthParams,\n bodyParams: input.bodyParams,\n });\n const headerParams = { ...oauthParams, oauth_signature: signature };\n const headerValue = Object.keys(headerParams)\n .sort()\n .map((key) => `${oauthPercentEncode(key)}=\"${oauthPercentEncode(headerParams[key as keyof typeof headerParams]!)}\"`)\n .join(\", \");\n return `OAuth ${headerValue}`;\n}\n\nexport function readSmugMugCredentialsFromEnv(\n env: Record<string, string | undefined> = process.env,\n): SmugMugCredentials {\n return smugMugCredentialsSchema.parse({\n consumerKey: env.SMUGMUG_CONSUMER_KEY,\n consumerSecret: env.SMUGMUG_CONSUMER_SECRET,\n accessToken: env.SMUGMUG_ACCESS_TOKEN,\n accessTokenSecret: env.SMUGMUG_ACCESS_TOKEN_SECRET,\n });\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction albumKeyFromUri(uri: string): string {\n const match = uri.match(/\\/album\\/([^/?!]+)/i);\n if (!match?.[1]) {\n throw new Error(`Unable to parse album key from URI: ${uri}`);\n }\n return match[1];\n}\n\nfunction nodeIdFromUri(uri: string): string {\n const match = uri.match(/\\/node\\/([^/?!]+)/i);\n if (!match?.[1]) {\n throw new Error(`Unable to parse node id from URI: ${uri}`);\n }\n return match[1];\n}\n\nfunction mapAlbumImage(\n albumImage: SmugMugAlbumImageWire,\n portfolioSourceId: string,\n sort: number,\n): SmugMugFlatImage {\n const image = albumImage.Image;\n const metadata = image?.ImageMetadata ?? albumImage.ImageMetadata;\n const originalUrl =\n image?.ImageSizeDetails?.OriginalImageUrl ?? albumImage.LargestImage?.Url ?? albumImage.WebUri;\n const fileName = image?.FileName ?? albumImage.FileName;\n return {\n sourceId: albumImage.ImageKey,\n portfolioSourceId,\n sort,\n fileName,\n originalUrl,\n caption: albumImage.Caption ?? image?.Caption,\n keywords: image?.KeywordsArray?.length ? image.KeywordsArray : undefined,\n exif: metadata\n ? {\n iso: metadata.ISO,\n aperture: metadata.Aperture ?? metadata.ApertureValue,\n shutter: metadata.ShutterSpeed ?? metadata.ExposureTime,\n focalLength: metadata.FocalLength,\n }\n : undefined,\n };\n}\n\n/** Signed SmugMug API client — recursively discovers folders, albums, and images. */\nexport class SmugMugApiClient {\n readonly credentials: SmugMugCredentials;\n readonly pageSize: number;\n readonly maxRetries: number;\n readonly retryBaseDelayMs: number;\n readonly maxRetryDelayMs: number;\n readonly requestIntervalMs: number;\n readonly fetchImpl: typeof fetch;\n\n private lastRequestAt = 0;\n\n constructor(options: SmugMugClientOptions) {\n const parsed = smugMugClientOptionsSchema.parse(options);\n this.credentials = parsed.credentials;\n this.pageSize = parsed.pageSize;\n this.maxRetries = parsed.maxRetries;\n this.retryBaseDelayMs = parsed.retryBaseDelayMs;\n this.maxRetryDelayMs = parsed.maxRetryDelayMs;\n this.requestIntervalMs = parsed.requestIntervalMs;\n this.fetchImpl = parsed.fetchImpl ?? fetch;\n }\n\n /** Validate credentials against `GET /user/!authuser`. */\n async validateCredentials(): Promise<{ nick?: string; rootNodeUri: string }> {\n const user = await this.getAuthUser();\n return { nick: user.NickName, rootNodeUri: user.Uris.Node };\n }\n\n /** Crawl the authenticated user's node tree into flat export tables for `parse-node.ts`. */\n async crawlExport(): Promise<SmugMugFlatExport> {\n const user = await this.getAuthUser();\n const folders: SmugMugFlatFolder[] = [];\n const albums: SmugMugFlatAlbum[] = [];\n const images: SmugMugFlatImage[] = [];\n\n await this.walkNode(user.Uris.Node, undefined, folders, albums, images);\n\n return {\n exportVersion: 1,\n exportedAt: new Date().toISOString(),\n Folders: folders,\n Albums: albums,\n Images: images,\n };\n }\n\n private async getAuthUser(): Promise<SmugMugUserWire> {\n const envelope = await this.requestJson<SmugMugUserWire>(`${SMUGMUG_API_BASE}/user/!authuser`);\n return envelope.Response;\n }\n\n private async walkNode(\n nodeUri: string,\n parentFolderId: string | undefined,\n folders: SmugMugFlatFolder[],\n albums: SmugMugFlatAlbum[],\n images: SmugMugFlatImage[],\n ): Promise<void> {\n const childrenPath = `${nodeUri}!children`;\n for await (const child of this.paginateNodes(childrenPath)) {\n if (child.Type === \"Page\") continue;\n\n if (child.Type === \"Folder\") {\n folders.push({\n sourceId: child.NodeID,\n name: child.Name,\n parentSourceId: parentFolderId,\n slug: child.UrlName,\n description: child.Description,\n });\n await this.walkNode(child.Uri, child.NodeID, folders, albums, images);\n continue;\n }\n\n if (child.Type === \"Album\") {\n albums.push({\n sourceId: child.NodeID,\n name: child.Name,\n parentSourceId: parentFolderId,\n slug: child.UrlName,\n description: child.Description,\n url: child.WebUri,\n });\n const albumUri = child.Uris?.Album;\n if (albumUri) {\n await this.collectAlbumImages(albumUri, child.NodeID, images);\n }\n }\n }\n }\n\n private async collectAlbumImages(\n albumUri: string,\n portfolioSourceId: string,\n images: SmugMugFlatImage[],\n ): Promise<void> {\n const albumKey = albumKeyFromUri(albumUri);\n const configQuery = `_config=${encodeURIComponent(JSON.stringify(ALBUM_IMAGES_CONFIG))}`;\n const initialPath = `${SMUGMUG_API_BASE}/album/${albumKey}!images?${configQuery}`;\n\n let sort = 0;\n for await (const albumImage of this.paginateAlbumImages(initialPath)) {\n images.push(mapAlbumImage(albumImage, portfolioSourceId, sort));\n sort += 1;\n }\n }\n\n private async *paginateNodes(path: string): AsyncGenerator<SmugMugNodeWire> {\n for await (const page of this.paginate<{ Node?: SmugMugNodeWire[] }>(path)) {\n for (const node of page.Node ?? []) {\n yield node;\n }\n }\n }\n\n private async *paginateAlbumImages(path: string): AsyncGenerator<SmugMugAlbumImageWire> {\n for await (const page of this.paginate<{ AlbumImage?: SmugMugAlbumImageWire[] }>(path)) {\n for (const albumImage of page.AlbumImage ?? []) {\n yield albumImage;\n }\n }\n }\n\n private async *paginate<T extends Record<string, unknown>>(\n initialPath: string,\n ): AsyncGenerator<T> {\n let nextPath: string | undefined = appendPagination(initialPath, this.pageSize, 1);\n while (nextPath) {\n const envelope: SmugMugApiEnvelope<T> = await this.requestJson<T>(nextPath);\n yield envelope.Response;\n nextPath = envelope.Response.Pages?.NextPage;\n }\n }\n\n private async requestJson<T>(pathOrUrl: string): Promise<SmugMugApiEnvelope<T>> {\n const url = toAbsoluteUrl(pathOrUrl);\n const response = await this.requestWithRetry(url);\n const body = (await response.json()) as SmugMugApiEnvelope<T>;\n if (body.Code !== 200) {\n throw new Error(`SmugMug API error ${body.Code}: ${body.Message}`);\n }\n return body;\n }\n\n private async requestWithRetry(url: URL): Promise<Response> {\n let attempt = 0;\n while (true) {\n await this.throttle();\n const authorization = buildSmugMugAuthorizationHeader({\n method: \"GET\",\n url: url.toString(),\n credentials: this.credentials,\n });\n const response = await this.fetchImpl(url, {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n Authorization: authorization,\n },\n });\n\n if (response.ok) {\n return response;\n }\n\n const retryable = response.status === 429 || response.status >= 500;\n if (!retryable || attempt >= this.maxRetries) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(\n `SmugMug HTTP ${response.status}${detail ? `: ${detail.slice(0, 200)}` : \"\"}`,\n );\n }\n\n const retryAfter = Number.parseInt(response.headers.get(\"retry-after\") ?? \"\", 10);\n const delay = Number.isFinite(retryAfter)\n ? retryAfter * 1000\n : Math.min(this.maxRetryDelayMs, this.retryBaseDelayMs * 2 ** attempt);\n await sleep(delay);\n attempt += 1;\n }\n }\n\n private async throttle(): Promise<void> {\n if (this.requestIntervalMs <= 0) return;\n const elapsed = Date.now() - this.lastRequestAt;\n if (elapsed < this.requestIntervalMs) {\n await sleep(this.requestIntervalMs - elapsed);\n }\n this.lastRequestAt = Date.now();\n }\n}\n\nfunction toAbsoluteUrl(pathOrUrl: string): URL {\n if (pathOrUrl.startsWith(\"http://\") || pathOrUrl.startsWith(\"https://\")) {\n return new URL(pathOrUrl);\n }\n if (pathOrUrl.startsWith(\"/\")) {\n return new URL(`https://${SMUGMUG_API_HOST}${pathOrUrl}`);\n }\n return new URL(pathOrUrl);\n}\n\nfunction appendPagination(pathOrUrl: string, count: number, start: number): string {\n const url = toAbsoluteUrl(pathOrUrl);\n url.searchParams.set(\"count\", String(count));\n url.searchParams.set(\"start\", String(start));\n return url.toString();\n}\n\n/** @internal Exported for crawl tests — resolves root node id from user node URI. */\nexport function smugMugRootNodeIdFromUserNodeUri(nodeUri: string): string {\n return nodeIdFromUri(nodeUri);\n}\n","import { readFile } from \"node:fs/promises\";\n\nimport { sanitizeSlug } from \"../../lib/utility.js\";\nimport type {\n NormalizedAsset,\n NormalizedAssetExif,\n NormalizedEntity,\n NormalizedPortfolio,\n SourceMetadata,\n} from \"../../normalizer/types.js\";\nimport { SmugMugApiClient, type SmugMugClientOptions, type SmugMugCredentials } from \"./api.js\";\nimport type {\n SmugMugExportDocument,\n SmugMugFlatExport,\n SmugMugFlatImage,\n SmugMugMockAlbum,\n SmugMugMockExport,\n SmugMugMockFolder,\n} from \"./types.js\";\n\nconst PLATFORM = \"smugmug\" as const;\nconst UNRESOLVED_URL_PREFIX = \"unspecified://smugmug/\";\n\nexport interface SmugMugParseOptions {\n filePath?: string;\n data?: SmugMugExportDocument;\n /** Pre-constructed signed API client (live crawl). */\n client?: SmugMugApiClient;\n /** OAuth credentials — builds a client when `client` is omitted. */\n credentials?: SmugMugCredentials;\n /** Optional tuning for credential-backed client. */\n clientOptions?: Omit<SmugMugClientOptions, \"credentials\">;\n}\n\nfunction sourceMeta(id: string, url?: string, exportedAt?: string): SourceMetadata {\n return {\n platform: PLATFORM,\n id,\n url,\n exportedAt,\n };\n}\n\nfunction guessMime(filename: string): string | undefined {\n const ext = filename.split(\".\").pop()?.toLowerCase();\n const map: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n tif: \"image/tiff\",\n tiff: \"image/tiff\",\n };\n return ext ? map[ext] : undefined;\n}\n\nfunction parseExifNumber(value: number | string | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n const parsed = Number.parseFloat(String(value).replace(/[^0-9.]/g, \"\"));\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction normalizeExif(\n exif: SmugMugFlatImage[\"exif\"] | SmugMugMockAlbum[\"images\"][0][\"exif\"],\n): NormalizedAssetExif | undefined {\n if (!exif || Object.keys(exif).length === 0) return undefined;\n const normalized: NormalizedAssetExif = {\n iso: parseExifNumber(exif.iso),\n aperture: parseExifNumber(exif.aperture),\n shutter: exif.shutter,\n focalLength: parseExifNumber(exif.focalLength),\n };\n if (\n normalized.iso === undefined &&\n normalized.aperture === undefined &&\n !normalized.shutter &&\n normalized.focalLength === undefined\n ) {\n return undefined;\n }\n return normalized;\n}\n\nexport function isSmugMugFlatExport(value: unknown): value is SmugMugFlatExport {\n if (!value || typeof value !== \"object\") return false;\n const record = value as SmugMugFlatExport;\n const version = record.exportVersion;\n return (\n (version === 1 || version === \"1\") &&\n Array.isArray(record.Folders) &&\n Array.isArray(record.Albums) &&\n Array.isArray(record.Images)\n );\n}\n\nfunction isSmugMugNestedExport(value: unknown): value is SmugMugMockExport {\n if (!value || typeof value !== \"object\") return false;\n const record = value as SmugMugMockExport;\n const version = record.exportVersion;\n return (version === 1 || version === \"1\") && Array.isArray(record.folders);\n}\n\nexport async function loadSmugMugExport(options: SmugMugParseOptions): Promise<SmugMugExportDocument> {\n if (options.data) return options.data;\n if (!options.filePath) {\n throw new Error(\"SmugMug parser requires filePath or data\");\n }\n const raw: unknown = JSON.parse(await readFile(options.filePath, \"utf8\"));\n if (isSmugMugFlatExport(raw) || isSmugMugNestedExport(raw)) {\n return raw;\n }\n throw new Error(\n \"Invalid SmugMug export: expected exportVersion 1 with folders[] (nested) or Folders/Albums/Images (flat)\",\n );\n}\n\nfunction resolveAssetUrl(image: SmugMugFlatImage): string {\n if (image.originalUrl) return image.originalUrl;\n return `${UNRESOLVED_URL_PREFIX}${image.sourceId}`;\n}\n\nfunction resolveFilename(image: SmugMugFlatImage): string {\n if (image.fileName) return image.fileName;\n return `${image.sourceId}.jpg`;\n}\n\nfunction* emitNestedFolderPortfolio(\n folder: SmugMugMockFolder,\n exportedAt?: string,\n): Generator<NormalizedPortfolio> {\n yield {\n type: \"portfolio\",\n source: sourceMeta(folder.id, undefined, exportedAt),\n sourceId: folder.id,\n title: folder.name,\n slug: sanitizeSlug(folder.slug ?? folder.name),\n description: folder.description,\n };\n}\n\nfunction* emitNestedAlbumPortfolio(\n folder: SmugMugMockFolder,\n album: SmugMugMockAlbum,\n exportedAt?: string,\n): Generator<NormalizedPortfolio> {\n yield {\n type: \"portfolio\",\n source: sourceMeta(album.id, album.url, exportedAt),\n sourceId: album.id,\n title: album.name,\n slug: sanitizeSlug(album.slug ?? album.name),\n description: album.description,\n parentSourceId: folder.id,\n };\n}\n\nfunction* emitNestedAlbumAssets(\n album: SmugMugMockAlbum,\n exportedAt?: string,\n): Generator<NormalizedAsset> {\n for (let index = 0; index < album.images.length; index++) {\n const image = album.images[index]!;\n yield {\n type: \"asset\",\n source: sourceMeta(image.id, image.originalUrl, exportedAt),\n sourceId: image.id,\n sourceUrl: image.originalUrl,\n filename: image.fileName,\n mimeType: guessMime(image.fileName),\n caption: image.caption,\n keywords: image.keywords?.length ? image.keywords : undefined,\n exif: normalizeExif(image.exif),\n portfolioSourceId: album.id,\n sort: index,\n };\n }\n}\n\nasync function* enumerateNestedExport(\n doc: SmugMugMockExport,\n): AsyncGenerator<NormalizedEntity> {\n const exportedAt = doc.exportedAt;\n for (const folder of doc.folders) {\n yield* emitNestedFolderPortfolio(folder, exportedAt);\n for (const album of folder.albums) {\n yield* emitNestedAlbumPortfolio(folder, album, exportedAt);\n yield* emitNestedAlbumAssets(album, exportedAt);\n }\n }\n}\n\nasync function* enumerateFlatExport(doc: SmugMugFlatExport): AsyncGenerator<NormalizedEntity> {\n const exportedAt = doc.exportedAt;\n\n for (const folder of doc.Folders) {\n yield {\n type: \"portfolio\",\n source: sourceMeta(folder.sourceId, undefined, exportedAt),\n sourceId: folder.sourceId,\n title: folder.name,\n slug: sanitizeSlug(folder.slug ?? folder.name),\n description: folder.description,\n parentSourceId: folder.parentSourceId,\n } satisfies NormalizedPortfolio;\n }\n\n for (const album of doc.Albums) {\n yield {\n type: \"portfolio\",\n source: sourceMeta(album.sourceId, album.url, exportedAt),\n sourceId: album.sourceId,\n title: album.name,\n slug: sanitizeSlug(album.slug ?? album.name),\n description: album.description,\n parentSourceId: album.parentSourceId,\n } satisfies NormalizedPortfolio;\n }\n\n for (const image of doc.Images) {\n const filename = resolveFilename(image);\n yield {\n type: \"asset\",\n source: sourceMeta(image.sourceId, image.originalUrl, exportedAt),\n sourceId: image.sourceId,\n sourceUrl: resolveAssetUrl(image),\n filename,\n mimeType: guessMime(filename),\n caption: image.caption,\n keywords: image.keywords?.length ? image.keywords : undefined,\n exif: normalizeExif(image.exif),\n portfolioSourceId: image.portfolioSourceId,\n sort: image.sort ?? 0,\n } satisfies NormalizedAsset;\n }\n}\n\nasync function resolveSmugMugDocument(options: SmugMugParseOptions): Promise<SmugMugExportDocument> {\n if (options.data) return options.data;\n if (options.client) return options.client.crawlExport();\n if (options.credentials) {\n const client = new SmugMugApiClient({ credentials: options.credentials, ...options.clientOptions });\n return client.crawlExport();\n }\n return loadSmugMugExport(options);\n}\n\n/** Walk discovered SmugMug nodes — fixture JSON or live API crawl via injected credentials. */\nexport async function* enumerateSmugMugEntities(\n options: SmugMugParseOptions,\n): AsyncGenerator<NormalizedEntity> {\n const doc = await resolveSmugMugDocument(options);\n if (isSmugMugFlatExport(doc)) {\n yield* enumerateFlatExport(doc);\n return;\n }\n yield* enumerateNestedExport(doc);\n}\n\nexport function summarizeSmugMugExport(doc: SmugMugExportDocument): {\n folders: number;\n albums: number;\n assets: number;\n portfolios: number;\n} {\n if (isSmugMugFlatExport(doc)) {\n return {\n folders: doc.Folders.length,\n albums: doc.Albums.length,\n assets: doc.Images.length,\n portfolios: doc.Folders.length + doc.Albums.length,\n };\n }\n\n const folders = doc.folders.length;\n let albums = 0;\n let assets = 0;\n for (const folder of doc.folders) {\n albums += folder.albums.length;\n for (const album of folder.albums) {\n assets += album.images.length;\n }\n }\n return {\n folders,\n albums,\n assets,\n portfolios: folders + albums,\n };\n}\n\nexport async function validateSmugMugExportFile(filePath: string): Promise<{\n ok: boolean;\n issues: { code: string; message: string }[];\n summary: Record<string, number>;\n}> {\n const issues: { code: string; message: string }[] = [];\n let doc: SmugMugExportDocument;\n try {\n doc = await loadSmugMugExport({ filePath });\n } catch (error) {\n return {\n ok: false,\n issues: [\n {\n code: \"invalid_export\",\n message: error instanceof Error ? error.message : String(error),\n },\n ],\n summary: {},\n };\n }\n\n if (isSmugMugFlatExport(doc)) {\n if (doc.Folders.length === 0 && doc.Albums.length === 0) {\n issues.push({ code: \"empty_export\", message: \"No folders or albums in export\" });\n }\n } else if (doc.folders.length === 0) {\n issues.push({ code: \"empty_export\", message: \"No folders in export\" });\n }\n\n const summary = summarizeSmugMugExport(doc);\n return {\n ok: issues.length === 0,\n issues,\n summary: {\n portfolios: summary.portfolios,\n assets: summary.assets,\n categories: summary.folders,\n posts: 0,\n pages: 0,\n tags: 0,\n },\n };\n}\n\n/** @deprecated Use validateSmugMugExportFile */\nexport const validateSmugMugMockFile = validateSmugMugExportFile;\n","import type { AdapterContext, MigrationAdapter, ValidationResult } from \"../../normalizer/types.js\";\nimport type { SmugMugClientOptions } from \"./api.js\";\nimport {\n SmugMugApiClient,\n readSmugMugCredentialsFromEnv,\n smugMugCredentialsSchema,\n} from \"./api.js\";\nimport {\n enumerateSmugMugEntities,\n summarizeSmugMugExport,\n validateSmugMugExportFile,\n} from \"./parse-node.js\";\nimport type { SmugMugExportDocument } from \"./types.js\";\n\ninterface SmugMugParseInput {\n path?: string;\n data?: SmugMugExportDocument;\n credentials?: ReturnType<typeof smugMugCredentialsSchema.parse>;\n client?: SmugMugApiClient;\n clientOptions?: Omit<SmugMugClientOptions, \"credentials\">;\n /** When true, read SMUGMUG_* env vars for live API crawl (no file path required). */\n live?: boolean;\n}\n\nfunction resolveInput(input: unknown): SmugMugParseInput {\n if (typeof input === \"string\") return { path: input };\n if (input && typeof input === \"object\") {\n const record = input as SmugMugParseInput;\n if (record.client || record.credentials || record.live) return record;\n if (record.data) return { data: record.data };\n if (record.path) return { path: record.path };\n }\n throw new Error(\n \"SmugMug adapter requires input path (string or { path }), { data }, { credentials }, { client }, or { live: true }\",\n );\n}\n\nfunction resolveLiveCredentials(input: SmugMugParseInput) {\n if (input.credentials) return input.credentials;\n if (input.live) return readSmugMugCredentialsFromEnv();\n return undefined;\n}\n\nexport const smugmugAdapter: MigrationAdapter = {\n platform: \"smugmug\",\n\n async validateInput(input: unknown): Promise<ValidationResult> {\n try {\n const resolved = resolveInput(input);\n const credentials = resolveLiveCredentials(resolved);\n\n if (resolved.data) {\n const summary = summarizeSmugMugExport(resolved.data);\n return {\n ok: true,\n issues: [],\n summary: {\n portfolios: summary.portfolios,\n assets: summary.assets,\n categories: summary.folders,\n posts: 0,\n pages: 0,\n tags: 0,\n },\n };\n }\n\n if (resolved.client || credentials) {\n const client =\n resolved.client ??\n new SmugMugApiClient({ credentials: credentials!, ...resolved.clientOptions });\n await client.validateCredentials();\n const doc = await client.crawlExport();\n const summary = summarizeSmugMugExport(doc);\n return {\n ok: true,\n issues: [],\n summary: {\n portfolios: summary.portfolios,\n assets: summary.assets,\n categories: summary.folders,\n posts: 0,\n pages: 0,\n tags: 0,\n },\n };\n }\n\n const result = await validateSmugMugExportFile(resolved.path!);\n return {\n ok: result.ok,\n issues: result.issues,\n summary: result.summary,\n };\n } catch (error) {\n return {\n ok: false,\n issues: [\n {\n code: \"invalid_input\",\n message: error instanceof Error ? error.message : String(error),\n },\n ],\n };\n }\n },\n\n enumerateEntities(ctx: AdapterContext) {\n const resolved = resolveInput(ctx.input);\n const credentials = resolveLiveCredentials(resolved);\n return enumerateSmugMugEntities({\n filePath: resolved.path,\n data: resolved.data,\n client: resolved.client,\n credentials,\n clientOptions: resolved.clientOptions,\n });\n },\n};\n\nexport type { SmugMugExportDocument, SmugMugFlatExport, SmugMugMockExport } from \"./types.js\";\nexport {\n SmugMugApiClient,\n SMUGMUG_API_BASE,\n SMUGMUG_OAUTH_ENDPOINTS,\n buildSmugMugAuthorizationHeader,\n oauthPercentEncode,\n readSmugMugCredentialsFromEnv,\n signSmugMugOAuthRequest,\n smugMugCredentialsSchema,\n} from \"./api.js\";\nexport type { SmugMugClientOptions, SmugMugCredentials } from \"./api.js\";\nexport {\n enumerateSmugMugEntities,\n isSmugMugFlatExport,\n loadSmugMugExport,\n summarizeSmugMugExport,\n validateSmugMugExportFile,\n validateSmugMugMockFile,\n} from \"./parse-node.js\";\n","import type { AdapterContext, MigrationAdapter, ValidationResult } from \"../../normalizer/types.js\";\nimport {\n SquarespaceCollectionClient,\n type SquarespaceClientOptions,\n type SquarespaceCollectTarget,\n} from \"./collect.js\";\nimport {\n enumerateSquarespaceEntities,\n summarizeSquarespaceExport,\n validateSquarespaceExportFile,\n} from \"./parse-export.js\";\nimport type { SquarespaceExport } from \"./types.js\";\n\ninterface SquarespaceParseInput {\n path?: string;\n data?: SquarespaceExport;\n client?: SquarespaceCollectionClient;\n collectTargets?: SquarespaceCollectTarget[];\n clientOptions?: SquarespaceClientOptions;\n}\n\nfunction resolveInput(input: unknown): SquarespaceParseInput {\n if (typeof input === \"string\") return { path: input };\n if (input && typeof input === \"object\") {\n const record = input as SquarespaceParseInput;\n if (record.client || record.collectTargets) return record;\n if (record.data) return { data: record.data };\n if (record.path) return { path: record.path };\n }\n throw new Error(\n \"Squarespace adapter requires input path (string or { path }), { data }, { client, collectTargets }, or { collectTargets }\",\n );\n}\n\nexport const squarespaceAdapter: MigrationAdapter = {\n platform: \"squarespace\",\n\n async validateInput(input: unknown): Promise<ValidationResult> {\n try {\n const resolved = resolveInput(input);\n\n if (resolved.data) {\n const summary = summarizeSquarespaceExport(resolved.data);\n return {\n ok: true,\n issues: [],\n summary: {\n pages: summary.pages,\n posts: summary.posts,\n categories: summary.categories,\n tags: summary.tags,\n },\n };\n }\n\n if (resolved.client || resolved.collectTargets?.length) {\n if (!resolved.collectTargets?.length) {\n throw new Error(\"Squarespace live validation requires collectTargets\");\n }\n const client =\n resolved.client ?? new SquarespaceCollectionClient(resolved.clientOptions);\n const doc = await client.collectExport(resolved.collectTargets);\n const summary = summarizeSquarespaceExport(doc);\n return {\n ok: true,\n issues: [],\n summary: {\n pages: summary.pages,\n posts: summary.posts,\n categories: summary.categories,\n tags: summary.tags,\n },\n };\n }\n\n const result = await validateSquarespaceExportFile(resolved.path!);\n return {\n ok: result.ok,\n issues: result.issues,\n summary: result.summary,\n };\n } catch (error) {\n return {\n ok: false,\n issues: [\n {\n code: \"invalid_input\",\n message: error instanceof Error ? error.message : String(error),\n },\n ],\n };\n }\n },\n\n enumerateEntities(ctx: AdapterContext) {\n const resolved = resolveInput(ctx.input);\n return enumerateSquarespaceEntities({\n filePath: resolved.path,\n data: resolved.data,\n client: resolved.client,\n collectTargets: resolved.collectTargets,\n clientOptions: resolved.clientOptions,\n });\n },\n};\n\nexport type { SquarespaceExport } from \"./types.js\";\nexport {\n SQUARESPACE_JSON_FORMAT,\n SquarespaceCollectionClient,\n buildJsonPrettyUrl,\n extractBlocksFromBodyHtml,\n inferBlockTypeFromClassName,\n mapJsonPrettyWire,\n mergeSquarespaceExportPartials,\n squarespaceClientOptionsSchema,\n} from \"./collect.js\";\nexport type { SquarespaceClientOptions, SquarespaceCollectTarget } from \"./collect.js\";\nexport {\n SUPPORTED_BLOCK_TYPES,\n UNSUPPORTED_BLOCK_TYPES,\n enumerateSquarespaceEntities,\n findUnsupportedBlockMarkers,\n flattenSquarespaceBlock,\n flattenSquarespaceBlocks,\n isSquarespaceExport,\n loadSquarespaceExport,\n summarizeSquarespaceExport,\n validateSquarespaceExportFile,\n} from \"./parse-export.js\";\n","export { wordpressAdapter } from \"./wordpress/index.js\";\nexport {\n SmugMugApiClient,\n SMUGMUG_API_BASE,\n SMUGMUG_OAUTH_ENDPOINTS,\n buildSmugMugAuthorizationHeader,\n readSmugMugCredentialsFromEnv,\n signSmugMugOAuthRequest,\n smugmugAdapter,\n smugMugCredentialsSchema,\n} from \"./smugmug/index.js\";\nexport type { SmugMugClientOptions, SmugMugCredentials } from \"./smugmug/index.js\";\nexport { squarespaceAdapter } from \"./squarespace/index.js\";\nexport {\n SquarespaceCollectionClient,\n SQUARESPACE_JSON_FORMAT,\n buildJsonPrettyUrl,\n mapJsonPrettyWire,\n} from \"./squarespace/index.js\";\nexport type { SquarespaceClientOptions, SquarespaceCollectTarget } from \"./squarespace/index.js\";\n\nimport type { MigrationPlatform } from \"../normalizer/types.js\";\nimport { smugmugAdapter } from \"./smugmug/index.js\";\nimport { squarespaceAdapter } from \"./squarespace/index.js\";\nimport { wordpressAdapter } from \"./wordpress/index.js\";\n\nconst adapters = {\n wordpress: wordpressAdapter,\n smugmug: smugmugAdapter,\n squarespace: squarespaceAdapter,\n} as const;\n\nexport function getAdapter(platform: MigrationPlatform) {\n return adapters[platform];\n}\n"],"mappings":"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","names":["readFile","PLATFORM","sourceMeta","guessMime","readFile","resolveInput"]}
@@ -0,0 +1,102 @@
1
+ // src/normalizer/types.ts
2
+ function entityKey(entity, platform) {
3
+ return {
4
+ platform,
5
+ entityType: entity.type,
6
+ sourceId: entity.sourceId
7
+ };
8
+ }
9
+
10
+ // src/normalizer/idempotency.ts
11
+ function isTerminalState(state) {
12
+ return state === "done" || state === "skipped";
13
+ }
14
+ function shouldProcessEntity(key, entities) {
15
+ const existing = entities.find(
16
+ (e) => e.platform === key.platform && e.entityType === key.entityType && e.sourceId === key.sourceId
17
+ );
18
+ return !existing || !isTerminalState(existing.state);
19
+ }
20
+
21
+ // src/normalizer/bundle.ts
22
+ function emptyBundle() {
23
+ return {
24
+ posts: [],
25
+ pages: [],
26
+ media: [],
27
+ portfolios: [],
28
+ categories: [],
29
+ tags: []
30
+ };
31
+ }
32
+ async function collectEntities(entities) {
33
+ const bundle = emptyBundle();
34
+ for await (const entity of entities) {
35
+ switch (entity.type) {
36
+ case "post":
37
+ bundle.posts.push(entity);
38
+ break;
39
+ case "page":
40
+ bundle.pages.push(entity);
41
+ break;
42
+ case "asset":
43
+ bundle.media.push(entity);
44
+ break;
45
+ case "portfolio":
46
+ bundle.portfolios.push(entity);
47
+ break;
48
+ case "category":
49
+ bundle.categories.push(entity);
50
+ break;
51
+ case "tag":
52
+ bundle.tags.push(entity);
53
+ break;
54
+ default: {
55
+ const _exhaustive = entity;
56
+ throw new Error(`Unknown entity type: ${_exhaustive.type}`);
57
+ }
58
+ }
59
+ }
60
+ return bundle;
61
+ }
62
+ function bundleCounts(bundle) {
63
+ return {
64
+ posts: bundle.posts.length,
65
+ pages: bundle.pages.length,
66
+ assets: bundle.media.length,
67
+ portfolios: bundle.portfolios.length,
68
+ categories: bundle.categories.length,
69
+ tags: bundle.tags.length
70
+ };
71
+ }
72
+
73
+ // src/normalizer/portfolio-media.ts
74
+ function buildPortfolioMediaLinks(bundle) {
75
+ const links = [];
76
+ for (const asset of bundle.media) {
77
+ if (!asset.portfolioSourceId) continue;
78
+ links.push({
79
+ portfolioSourceId: asset.portfolioSourceId,
80
+ assetSourceId: asset.sourceId,
81
+ sort: asset.sort ?? 0
82
+ });
83
+ }
84
+ links.sort((a, b) => {
85
+ if (a.portfolioSourceId !== b.portfolioSourceId) {
86
+ return a.portfolioSourceId.localeCompare(b.portfolioSourceId);
87
+ }
88
+ return a.sort - b.sort || a.assetSourceId.localeCompare(b.assetSourceId);
89
+ });
90
+ return links;
91
+ }
92
+
93
+ export {
94
+ entityKey,
95
+ isTerminalState,
96
+ shouldProcessEntity,
97
+ emptyBundle,
98
+ collectEntities,
99
+ bundleCounts,
100
+ buildPortfolioMediaLinks
101
+ };
102
+ //# sourceMappingURL=chunk-JKDRTL24.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/normalizer/types.ts","../src/normalizer/idempotency.ts","../src/normalizer/bundle.ts","../src/normalizer/portfolio-media.ts"],"sourcesContent":["export type MigrationPlatform = \"wordpress\" | \"smugmug\" | \"squarespace\";\n\nexport type EntityType = \"post\" | \"page\" | \"asset\" | \"portfolio\" | \"category\" | \"tag\";\n\nexport type PublishStatus = \"draft\" | \"published\" | \"archived\";\n\nexport interface SourceMetadata {\n platform: MigrationPlatform;\n id: string;\n url?: string;\n path?: string;\n exportedAt?: string;\n}\n\n/** Canonical post DTO — raw HTML; sanitize at host sink. */\nexport interface NormalizedPost {\n type: \"post\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n excerpt?: string;\n contentHtml: string;\n publishedAt?: string;\n status: PublishStatus;\n categorySlugs?: string[];\n tagSlugs?: string[];\n /** WordPress attachment id before two-pass resolution. */\n sourceFeaturedMediaId?: string;\n featuredAssetSourceId?: string;\n seoTitle?: string;\n seoDescription?: string;\n}\n\n/** Canonical page DTO — raw HTML snapshot. */\nexport interface NormalizedPage {\n type: \"page\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n contentHtml: string;\n contentCss?: string;\n isHomePage?: boolean;\n status: PublishStatus;\n seoTitle?: string;\n seoDescription?: string;\n}\n\n/** EXIF fields preserved from SmugMug / camera metadata when present. */\nexport interface NormalizedAssetExif {\n iso?: number;\n aperture?: number;\n shutter?: string;\n focalLength?: number;\n}\n\n/** Remote asset to stream into the host sink. */\nexport interface NormalizedAsset {\n type: \"asset\";\n source: SourceMetadata;\n sourceId: string;\n sourceUrl: string;\n filename: string;\n mimeType?: string;\n caption?: string;\n altText?: string;\n keywords?: string[];\n exif?: NormalizedAssetExif;\n portfolioSourceId?: string;\n sort?: number;\n}\n\n/** M2M index: portfolio ↔ asset membership and sort order. */\nexport interface PortfolioMediaLink {\n portfolioSourceId: string;\n assetSourceId: string;\n sort: number;\n}\n\nexport interface NormalizedPortfolio {\n type: \"portfolio\";\n source: SourceMetadata;\n sourceId: string;\n title: string;\n slug: string;\n description?: string;\n parentSourceId?: string;\n}\n\nexport interface NormalizedCategory {\n type: \"category\";\n source: SourceMetadata;\n sourceId: string;\n name: string;\n slug: string;\n}\n\nexport interface NormalizedTag {\n type: \"tag\";\n source: SourceMetadata;\n sourceId: string;\n name: string;\n slug: string;\n}\n\nexport type NormalizedEntity =\n | NormalizedPost\n | NormalizedPage\n | NormalizedAsset\n | NormalizedPortfolio\n | NormalizedCategory\n | NormalizedTag;\n\nexport interface ValidationIssue {\n code: string;\n message: string;\n path?: string;\n}\n\nexport interface ValidationResult {\n ok: boolean;\n issues: ValidationIssue[];\n summary?: {\n posts?: number;\n pages?: number;\n assets?: number;\n portfolios?: number;\n categories?: number;\n tags?: number;\n };\n}\n\nexport interface AdapterContext {\n input: unknown;\n cursor?: MigrationCursor;\n}\n\nexport interface MigrationAdapter {\n platform: MigrationPlatform;\n validateInput(input: unknown): ValidationResult | Promise<ValidationResult>;\n enumerateEntities(ctx: AdapterContext): AsyncIterable<NormalizedEntity>;\n}\n\nexport interface MigrationCursor {\n lastEntityKey?: EntityKey;\n state?: Record<string, unknown>;\n}\n\nexport interface EntityKey {\n platform: MigrationPlatform;\n entityType: EntityType;\n sourceId: string;\n}\n\nexport function entityKey(entity: NormalizedEntity, platform: MigrationPlatform): EntityKey {\n return {\n platform,\n entityType: entity.type,\n sourceId: entity.sourceId,\n };\n}\n","import type { EntityKey, MigrationCursor } from \"./types.js\";\n\n/** Portable entity state for resume / idempotency (not Directus field names). */\nexport type EntityState = \"pending\" | \"done\" | \"failed\" | \"skipped\";\n\nexport interface TrackedEntity extends EntityKey {\n state: EntityState;\n targetId?: string;\n errorMessage?: string;\n}\n\nexport interface MigrationCheckpoint {\n jobId: string;\n cursor: MigrationCursor;\n entities: TrackedEntity[];\n updatedAt: string;\n}\n\nexport function isTerminalState(state: EntityState): boolean {\n return state === \"done\" || state === \"skipped\";\n}\n\nexport function shouldProcessEntity(\n key: EntityKey,\n entities: TrackedEntity[],\n): boolean {\n const existing = entities.find(\n (e) =>\n e.platform === key.platform &&\n e.entityType === key.entityType &&\n e.sourceId === key.sourceId,\n );\n return !existing || !isTerminalState(existing.state);\n}\n","import type {\n NormalizedAsset,\n NormalizedCategory,\n NormalizedEntity,\n NormalizedPage,\n NormalizedPortfolio,\n NormalizedPost,\n NormalizedTag,\n} from \"./types.js\";\n\nexport interface EntityBundle {\n posts: NormalizedPost[];\n pages: NormalizedPage[];\n media: NormalizedAsset[];\n portfolios: NormalizedPortfolio[];\n categories: NormalizedCategory[];\n tags: NormalizedTag[];\n}\n\nexport function emptyBundle(): EntityBundle {\n return {\n posts: [],\n pages: [],\n media: [],\n portfolios: [],\n categories: [],\n tags: [],\n };\n}\n\nexport async function collectEntities(\n entities: AsyncIterable<NormalizedEntity>,\n): Promise<EntityBundle> {\n const bundle = emptyBundle();\n\n for await (const entity of entities) {\n switch (entity.type) {\n case \"post\":\n bundle.posts.push(entity);\n break;\n case \"page\":\n bundle.pages.push(entity);\n break;\n case \"asset\":\n bundle.media.push(entity);\n break;\n case \"portfolio\":\n bundle.portfolios.push(entity);\n break;\n case \"category\":\n bundle.categories.push(entity);\n break;\n case \"tag\":\n bundle.tags.push(entity);\n break;\n default: {\n const _exhaustive: never = entity;\n throw new Error(`Unknown entity type: ${(_exhaustive as NormalizedEntity).type}`);\n }\n }\n }\n\n return bundle;\n}\n\nexport interface BundleCounts {\n posts: number;\n pages: number;\n assets: number;\n portfolios: number;\n categories: number;\n tags: number;\n}\n\nexport function bundleCounts(bundle: EntityBundle): BundleCounts {\n return {\n posts: bundle.posts.length,\n pages: bundle.pages.length,\n assets: bundle.media.length,\n portfolios: bundle.portfolios.length,\n categories: bundle.categories.length,\n tags: bundle.tags.length,\n };\n}\n","import type { EntityBundle } from \"./bundle.js\";\nimport type { PortfolioMediaLink } from \"./types.js\";\n\n/** Derive portfolio↔asset M2M rows from assets carrying `portfolioSourceId`. */\nexport function buildPortfolioMediaLinks(bundle: EntityBundle): PortfolioMediaLink[] {\n const links: PortfolioMediaLink[] = [];\n\n for (const asset of bundle.media) {\n if (!asset.portfolioSourceId) continue;\n links.push({\n portfolioSourceId: asset.portfolioSourceId,\n assetSourceId: asset.sourceId,\n sort: asset.sort ?? 0,\n });\n }\n\n links.sort((a, b) => {\n if (a.portfolioSourceId !== b.portfolioSourceId) {\n return a.portfolioSourceId.localeCompare(b.portfolioSourceId);\n }\n return a.sort - b.sort || a.assetSourceId.localeCompare(b.assetSourceId);\n });\n\n return links;\n}\n"],"mappings":";AA2JO,SAAS,UAAU,QAA0B,UAAwC;AAC1F,SAAO;AAAA,IACL;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,EACnB;AACF;;;AC/IO,SAAS,gBAAgB,OAA6B;AAC3D,SAAO,UAAU,UAAU,UAAU;AACvC;AAEO,SAAS,oBACd,KACA,UACS;AACT,QAAM,WAAW,SAAS;AAAA,IACxB,CAAC,MACC,EAAE,aAAa,IAAI,YACnB,EAAE,eAAe,IAAI,cACrB,EAAE,aAAa,IAAI;AAAA,EACvB;AACA,SAAO,CAAC,YAAY,CAAC,gBAAgB,SAAS,KAAK;AACrD;;;ACdO,SAAS,cAA4B;AAC1C,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,IACR,YAAY,CAAC;AAAA,IACb,YAAY,CAAC;AAAA,IACb,MAAM,CAAC;AAAA,EACT;AACF;AAEA,eAAsB,gBACpB,UACuB;AACvB,QAAM,SAAS,YAAY;AAE3B,mBAAiB,UAAU,UAAU;AACnC,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,MAAM,KAAK,MAAM;AACxB;AAAA,MACF,KAAK;AACH,eAAO,WAAW,KAAK,MAAM;AAC7B;AAAA,MACF,KAAK;AACH,eAAO,WAAW,KAAK,MAAM;AAC7B;AAAA,MACF,KAAK;AACH,eAAO,KAAK,KAAK,MAAM;AACvB;AAAA,MACF,SAAS;AACP,cAAM,cAAqB;AAC3B,cAAM,IAAI,MAAM,wBAAyB,YAAiC,IAAI,EAAE;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAWO,SAAS,aAAa,QAAoC;AAC/D,SAAO;AAAA,IACL,OAAO,OAAO,MAAM;AAAA,IACpB,OAAO,OAAO,MAAM;AAAA,IACpB,QAAQ,OAAO,MAAM;AAAA,IACrB,YAAY,OAAO,WAAW;AAAA,IAC9B,YAAY,OAAO,WAAW;AAAA,IAC9B,MAAM,OAAO,KAAK;AAAA,EACpB;AACF;;;AC/EO,SAAS,yBAAyB,QAA4C;AACnF,QAAM,QAA8B,CAAC;AAErC,aAAW,SAAS,OAAO,OAAO;AAChC,QAAI,CAAC,MAAM,kBAAmB;AAC9B,UAAM,KAAK;AAAA,MACT,mBAAmB,MAAM;AAAA,MACzB,eAAe,MAAM;AAAA,MACrB,MAAM,MAAM,QAAQ;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,QAAI,EAAE,sBAAsB,EAAE,mBAAmB;AAC/C,aAAO,EAAE,kBAAkB,cAAc,EAAE,iBAAiB;AAAA,IAC9D;AACA,WAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,cAAc,EAAE,aAAa;AAAA,EACzE,CAAC;AAED,SAAO;AACT;","names":[]}