A CLI for publishing standard.site documents to ATProto

feat: add blob images

+26 -37
+26 -37
packages/cli/src/commands/publish-lite.ts
··· 36 36 ) 37 37 } 38 38 39 - async function resolveLocalImagePath( 39 + function getImageCandidates( 40 40 src: string, 41 41 postFilePath: string, 42 42 contentDir: string, 43 43 imagesDir?: string, 44 - ): Promise<string | null> { 44 + ): string[] { 45 45 const candidates = [ 46 46 path.resolve(path.dirname(postFilePath), src), 47 47 path.resolve(contentDir, src), 48 48 ] 49 49 if (imagesDir) { 50 50 candidates.push(path.resolve(imagesDir, src)) 51 - // Try stripping a leading directory that matches imagesDir basename 52 51 const baseName = path.basename(imagesDir) 53 52 const idx = src.indexOf(baseName) 54 53 if (idx !== -1) { ··· 56 55 candidates.push(path.resolve(imagesDir, after)) 57 56 } 58 57 } 59 - 60 - for (const candidate of candidates) { 61 - try { 62 - const stat = await fs.stat(candidate) 63 - if (stat.isFile() && stat.size > 0) return candidate 64 - } catch {} 65 - } 66 - return null 58 + return candidates 67 59 } 68 60 69 61 async function uploadBlob( 70 62 agent: Agent, 71 - filePath: string, 63 + candidates: string[], 72 64 ): Promise<BlobObject | undefined> { 73 - if (!(await fileExists(filePath))) return undefined 65 + for (const filePath of candidates) { 66 + if (!(await fileExists(filePath))) continue 74 67 75 - try { 76 - const imageBuffer = await fs.readFile(filePath) 77 - const mimeType = mimeTypes.lookup(filePath) || "application/octet-stream" 78 - const response = await agent.com.atproto.repo.uploadBlob( 79 - new Uint8Array(imageBuffer), 80 - { encoding: mimeType }, 81 - ) 82 - return { 83 - $type: "blob", 84 - ref: { $link: response.data.blob.ref.toString() }, 85 - mimeType, 86 - size: imageBuffer.byteLength, 87 - } 88 - } catch (error) { 89 - console.error(`Error uploading blob ${filePath}:`, error) 90 - return undefined 68 + try { 69 + const imageBuffer = await fs.readFile(filePath) 70 + if (imageBuffer.byteLength === 0) continue 71 + const mimeType = mimeTypes.lookup(filePath) || "application/octet-stream" 72 + const response = await agent.com.atproto.repo.uploadBlob( 73 + new Uint8Array(imageBuffer), 74 + { encoding: mimeType }, 75 + ) 76 + return { 77 + $type: "blob", 78 + ref: { $link: response.data.blob.ref.toString() }, 79 + mimeType, 80 + size: imageBuffer.byteLength, 81 + } 82 + } catch {} 91 83 } 84 + return undefined 92 85 } 93 86 94 87 async function processImages( ··· 111 104 const src = match[2]! 112 105 if (!isLocalPath(src)) continue 113 106 114 - const resolved = await resolveLocalImagePath( 115 - src, postFilePath, contentDir, imagesDir, 116 - ) 117 - if (!resolved) continue 118 - 119 - let blob = uploadCache.get(resolved) 107 + let blob = uploadCache.get(src) 120 108 if (!blob) { 121 - blob = await uploadBlob(agent, resolved) 109 + const candidates = getImageCandidates(src, postFilePath, contentDir, imagesDir) 110 + blob = await uploadBlob(agent, candidates) 122 111 if (!blob) continue 123 - uploadCache.set(resolved, blob) 112 + uploadCache.set(src, blob) 124 113 } 125 114 126 115 images.push({ image: blob, alt: alt || undefined })