snatching amp's walkthrough for my own purposes mwhahaha traverse.dunkirk.sh/diagram/6121f05c-a5ef-4ecf-8ffc-02534c5e767c
at main 309 lines 9.0 kB view raw
1import satori from "satori"; 2import { initWasm, Resvg } from "@resvg/resvg-wasm"; 3import { join } from "path"; 4 5// Load Inter font files (woff, not woff2 — satori doesn't support woff2) 6const fontsDir = join(import.meta.dir, "../fonts"); 7const [interRegular, interBold] = await Promise.all([ 8 Bun.file(join(fontsDir, "inter-latin-400-normal.woff")).arrayBuffer(), 9 Bun.file(join(fontsDir, "inter-latin-700-normal.woff")).arrayBuffer(), 10]); 11 12// Initialize resvg-wasm 13// Try bundled WASM first, then fall back to node_modules 14const possiblePaths = [ 15 // Bundled WASM file (most reliable) 16 join(import.meta.dir, "../vendor/index_bg.wasm"), 17 // Development: node_modules is a sibling of src 18 join(import.meta.dir, "../node_modules/@resvg/resvg-wasm/index_bg.wasm"), 19 // Installed: in parent node_modules 20 join(import.meta.dir, "../../@resvg/resvg-wasm/index_bg.wasm"), 21 join(import.meta.dir, "../../../@resvg/resvg-wasm/index_bg.wasm"), 22]; 23 24let wasmPath: string | undefined; 25for (const path of possiblePaths) { 26 if (await Bun.file(path).exists()) { 27 wasmPath = path; 28 break; 29 } 30} 31 32if (!wasmPath) { 33 console.error("Failed to find @resvg/resvg-wasm WASM file. Tried:"); 34 for (const path of possiblePaths) { 35 console.error(` - ${path}`); 36 } 37 console.error(`\nCurrent directory: ${import.meta.dir}`); 38 throw new Error("Could not find @resvg/resvg-wasm WASM file"); 39} 40 41await initWasm(Bun.file(wasmPath).arrayBuffer()); 42 43// Cache generated images by diagram ID 44const cache = new Map<string, Buffer>(); 45 46export async function generateIndexOgImage( 47 mode: "local" | "server", 48 diagramCount: number, 49): Promise<Buffer> { 50 const cacheKey = `__index_${mode}_${diagramCount}`; 51 const cached = cache.get(cacheKey); 52 if (cached) return cached; 53 54 const subtitle = mode === "server" 55 ? `${diagramCount} diagram${diagramCount !== 1 ? "s" : ""} shared` 56 : `${diagramCount} diagram${diagramCount !== 1 ? "s" : ""}`; 57 58 const tagline = mode === "server" 59 ? "Interactive code walkthrough diagrams, shareable with anyone." 60 : "Interactive code walkthrough diagrams"; 61 62 const svg = await satori( 63 { 64 type: "div", 65 props: { 66 style: { 67 width: "100%", 68 height: "100%", 69 display: "flex", 70 flexDirection: "column", 71 justifyContent: "center", 72 alignItems: "center", 73 padding: "60px", 74 backgroundColor: "#0a0a0a", 75 color: "#e5e5e5", 76 fontFamily: "Inter", 77 }, 78 children: [ 79 { 80 type: "div", 81 props: { 82 style: { 83 display: "flex", 84 flexDirection: "column", 85 alignItems: "center", 86 gap: "20px", 87 }, 88 children: [ 89 { 90 type: "div", 91 props: { 92 style: { 93 fontSize: "80px", 94 fontWeight: 700, 95 color: "#e5e5e5", 96 }, 97 children: "Traverse", 98 }, 99 }, 100 { 101 type: "div", 102 props: { 103 style: { 104 fontSize: "32px", 105 color: "#a3a3a3", 106 textAlign: "center", 107 }, 108 children: tagline, 109 }, 110 }, 111 { 112 type: "div", 113 props: { 114 style: { 115 display: "flex", 116 alignItems: "center", 117 gap: "8px", 118 marginTop: "16px", 119 fontSize: "24px", 120 color: "#a3a3a3", 121 backgroundColor: "#1c1c1e", 122 padding: "10px 24px", 123 borderRadius: "8px", 124 border: "1px solid #262626", 125 }, 126 children: subtitle, 127 }, 128 }, 129 ], 130 }, 131 }, 132 ], 133 }, 134 }, 135 { 136 width: 1200, 137 height: 630, 138 fonts: [ 139 { name: "Inter", data: interRegular, weight: 400, style: "normal" as const }, 140 { name: "Inter", data: interBold, weight: 700, style: "normal" as const }, 141 ], 142 }, 143 ); 144 145 const resvg = new Resvg(svg, { 146 fitTo: { mode: "width", value: 1200 }, 147 }); 148 const png = Buffer.from(resvg.render().asPng()); 149 150 cache.set(cacheKey, png); 151 return png; 152} 153 154export async function generateOgImage( 155 id: string, 156 summary: string, 157 nodeNames: string[], 158): Promise<Buffer> { 159 const cached = cache.get(id); 160 if (cached) return cached; 161 162 const nodeCount = nodeNames.length; 163 const displayNodes = nodeNames.slice(0, 8); 164 const extra = nodeCount > 8 ? nodeCount - 8 : 0; 165 166 const svg = await satori( 167 { 168 type: "div", 169 props: { 170 style: { 171 width: "100%", 172 height: "100%", 173 display: "flex", 174 flexDirection: "column", 175 justifyContent: "space-between", 176 padding: "60px", 177 backgroundColor: "#0a0a0a", 178 color: "#e5e5e5", 179 fontFamily: "Inter", 180 }, 181 children: [ 182 // Top: Traverse label + node count 183 { 184 type: "div", 185 props: { 186 style: { 187 display: "flex", 188 alignItems: "center", 189 justifyContent: "space-between", 190 }, 191 children: [ 192 { 193 type: "div", 194 props: { 195 style: { 196 fontSize: "20px", 197 fontWeight: 700, 198 color: "#a3a3a3", 199 letterSpacing: "0.05em", 200 textTransform: "uppercase" as const, 201 }, 202 children: "Traverse", 203 }, 204 }, 205 { 206 type: "div", 207 props: { 208 style: { 209 fontSize: "16px", 210 color: "#666", 211 }, 212 children: `${nodeCount} node${nodeCount !== 1 ? "s" : ""}`, 213 }, 214 }, 215 ], 216 }, 217 }, 218 // Middle: Summary headline 219 { 220 type: "div", 221 props: { 222 style: { 223 display: "flex", 224 flexDirection: "column", 225 gap: "24px", 226 flex: 1, 227 justifyContent: "center", 228 }, 229 children: [ 230 { 231 type: "div", 232 props: { 233 style: { 234 fontSize: summary.length > 60 ? "36px" : "44px", 235 fontWeight: 700, 236 lineHeight: 1.2, 237 color: "#e5e5e5", 238 overflow: "hidden", 239 textOverflow: "ellipsis", 240 }, 241 children: summary, 242 }, 243 }, 244 ], 245 }, 246 }, 247 // Bottom: Node pills 248 { 249 type: "div", 250 props: { 251 style: { 252 display: "flex", 253 flexWrap: "wrap", 254 gap: "8px", 255 }, 256 children: [ 257 ...displayNodes.map((name) => ({ 258 type: "div", 259 props: { 260 style: { 261 fontSize: "14px", 262 color: "#a3a3a3", 263 backgroundColor: "#1c1c1e", 264 padding: "4px 12px", 265 borderRadius: "6px", 266 border: "1px solid #262626", 267 }, 268 children: name, 269 }, 270 })), 271 ...(extra > 0 272 ? [ 273 { 274 type: "div", 275 props: { 276 style: { 277 fontSize: "14px", 278 color: "#666", 279 padding: "4px 8px", 280 }, 281 children: `+${extra} more`, 282 }, 283 }, 284 ] 285 : []), 286 ], 287 }, 288 }, 289 ], 290 }, 291 }, 292 { 293 width: 1200, 294 height: 630, 295 fonts: [ 296 { name: "Inter", data: interRegular, weight: 400, style: "normal" as const }, 297 { name: "Inter", data: interBold, weight: 700, style: "normal" as const }, 298 ], 299 }, 300 ); 301 302 const resvg = new Resvg(svg, { 303 fitTo: { mode: "width", value: 1200 }, 304 }); 305 const png = Buffer.from(resvg.render().asPng()); 306 307 cache.set(id, png); 308 return png; 309}