@ogpipe/next 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -0
- package/dist/bin/ogpipe.d.mts +1 -0
- package/dist/bin/ogpipe.d.ts +1 -0
- package/dist/bin/ogpipe.js +667 -0
- package/dist/bin/ogpipe.js.map +1 -0
- package/dist/bin/ogpipe.mjs +665 -0
- package/dist/bin/ogpipe.mjs.map +1 -0
- package/dist/client/index.d.mts +73 -0
- package/dist/client/index.d.ts +73 -0
- package/dist/client/index.js +101 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +76 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +168 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +139 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
- package/templates/blog.html +32 -0
- package/templates/changelog.html +32 -0
- package/templates/docs.html +25 -0
- package/templates/minimal.html +12 -0
- package/templates/product.html +29 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/next/config.ts","../../src/preview/server.ts","../../src/bin/ogpipe.ts","../../src/next/generator.ts"],"sourcesContent":["/**\n * @ogpipe/next/client — Framework-agnostic API client for OGPipe\n *\n * This is the thin HTTP client that communicates with the OGPipe rendering API.\n * Can be used standalone (Python, Go, curl equivalent) or via the Next.js integration.\n */\n\nexport interface OGPipeClientOptions {\n /** API key (og_live_xxx). Defaults to OGPIPE_API_KEY env var. */\n apiKey?: string;\n /** API base URL. Defaults to https://api.ogpipe.dev */\n baseUrl?: string;\n /** Request timeout in ms. Defaults to 30000. */\n timeout?: number;\n}\n\nexport interface RenderRequest {\n /** HTML content to render */\n html: string;\n /** Image width (default: 1200) */\n width?: number;\n /** Image height (default: 630) */\n height?: number;\n /** Output format (default: png) */\n format?: \"png\" | \"jpeg\" | \"webp\";\n}\n\nexport interface RenderResponse {\n /** CDN URL of the rendered image */\n url: string;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n /** Output format */\n format: string;\n /** Whether served from cache */\n cached: boolean;\n}\n\nexport interface RenderResult {\n success: true;\n data: RenderResponse;\n}\n\nexport interface RenderError {\n success: false;\n error: string;\n statusCode: number;\n}\n\nexport type RenderOutcome = RenderResult | RenderError;\n\n/**\n * OGPipe API client.\n *\n * Usage:\n * ```ts\n * import { OGPipeClient } from '@ogpipe/next/client'\n *\n * const client = new OGPipeClient({ apiKey: 'og_live_xxx' })\n * const result = await client.render({ html: '<div>Hello</div>' })\n * ```\n */\nexport class OGPipeClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n\n constructor(options: OGPipeClientOptions = {}) {\n this.apiKey = options.apiKey || process.env.OGPIPE_API_KEY || \"\";\n this.baseUrl = options.baseUrl || process.env.OGPIPE_BASE_URL || \"https://api.ogpipe.dev\";\n this.timeout = options.timeout || 30_000;\n\n if (!this.apiKey) {\n throw new Error(\n \"[OGPipe] Missing API key. Set OGPIPE_API_KEY environment variable or pass apiKey option.\"\n );\n }\n }\n\n /**\n * Render HTML to an image. Returns the CDN URL.\n */\n async render(request: RenderRequest): Promise<RenderOutcome> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}/images`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n html: request.html,\n width: request.width || 1200,\n height: request.height || 630,\n format: request.format || \"png\",\n }),\n signal: controller.signal,\n });\n\n const body = await res.json() as { error?: string };\n\n if (!res.ok) {\n return {\n success: false,\n error: body.error || `HTTP ${res.status}`,\n statusCode: res.status,\n };\n }\n\n return {\n success: true,\n data: body as RenderResponse,\n };\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n return { success: false, error: \"Request timed out\", statusCode: 408 };\n }\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n statusCode: 500,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Render HTML and return the raw image buffer (for writing to disk).\n */\n async renderToBuffer(request: RenderRequest): Promise<Buffer | null> {\n const result = await this.render(request);\n if (!result.success) return null;\n\n // Fetch the image from CDN URL\n const res = await fetch(result.data.url);\n if (!res.ok) return null;\n\n return Buffer.from(await res.arrayBuffer());\n }\n}\n","/**\n * OGPipe configuration schema.\n *\n * Developers define this in ogpipe.config.ts at their project root.\n * The CLI reads this to know which templates to use for which routes.\n */\n\nimport { readFileSync } from \"fs\";\nimport { resolve, dirname } from \"path\";\n\n// ─────────────────────────────────────────────\n// Config Types\n// ─────────────────────────────────────────────\n\nexport interface OGPipeTemplate {\n /** Inline HTML string with {{variable}} placeholders */\n html?: string;\n /** Path to an HTML template file (relative to config file) */\n file?: string;\n /** Image width (default: 1200) */\n width?: number;\n /** Image height (default: 630) */\n height?: number;\n /** Output format (default: png) */\n format?: \"png\" | \"jpeg\" | \"webp\";\n}\n\nexport interface RouteConfig {\n /** Template ID to use for this route */\n template: string;\n /** Function to extract variables from page metadata */\n vars?: (metadata: RouteMetadata) => Record<string, string>;\n}\n\nexport interface RouteMetadata {\n /** Page title from metadata export */\n title?: string;\n /** Page description from metadata export */\n description?: string;\n /** Route path (e.g., /blog/my-post) */\n path: string;\n /** Route params (e.g., { slug: 'my-post' }) */\n params?: Record<string, string>;\n /** Any additional metadata from the page */\n [key: string]: unknown;\n}\n\nexport interface OnDemandConfig {\n /** Cache duration in seconds (default: 86400 = 24h) */\n revalidate?: number;\n /** Fallback image path if API is unavailable */\n fallback?: string;\n}\n\nexport interface OGPipeConfig {\n /** API key. Defaults to OGPIPE_API_KEY env var. */\n apiKey?: string;\n /** API base URL. Defaults to https://api.ogpipe.dev */\n baseUrl?: string;\n /** Named templates */\n templates: Record<string, OGPipeTemplate>;\n /** Route-to-template mapping. Supports glob patterns. */\n routes: Record<string, RouteConfig>;\n /** On-demand rendering config (for dynamic routes) */\n onDemand?: OnDemandConfig;\n /** Output directory for generated images (default: public/og) */\n outDir?: string;\n}\n\n// ─────────────────────────────────────────────\n// Config Helper\n// ─────────────────────────────────────────────\n\n/**\n * Helper to define a type-safe OGPipe config.\n *\n * Usage in ogpipe.config.ts:\n * ```ts\n * import { defineConfig } from '@ogpipe/next'\n *\n * export default defineConfig({\n * templates: { ... },\n * routes: { ... },\n * })\n * ```\n */\nexport function defineConfig(config: OGPipeConfig): OGPipeConfig {\n return config;\n}\n\n// ─────────────────────────────────────────────\n// Template Resolution\n// ─────────────────────────────────────────────\n\n/**\n * Resolve a template to its final HTML string.\n * If the template uses a `file` path, reads the file from disk.\n */\nexport function resolveTemplateHtml(\n template: OGPipeTemplate,\n configDir: string\n): string {\n if (template.html) {\n return template.html;\n }\n\n if (template.file) {\n const filePath = resolve(configDir, template.file);\n try {\n return readFileSync(filePath, \"utf-8\");\n } catch (err) {\n throw new Error(`[OGPipe] Template file not found: ${filePath}`);\n }\n }\n\n throw new Error(\"[OGPipe] Template must have either 'html' or 'file' property.\");\n}\n\n/**\n * Inject variables into an HTML template string.\n * Replaces {{variable}} placeholders with values.\n */\nexport function injectVariables(\n html: string,\n variables: Record<string, string>\n): string {\n return html.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => {\n return variables[key] ?? \"\";\n });\n}\n\n/**\n * Match a route path against a glob pattern.\n * Supports:\n * /blog/[slug] → matches /blog/anything\n * /blog/* → matches /blog/anything\n * * → matches everything (default fallback)\n */\nexport function matchRoute(\n path: string,\n pattern: string\n): boolean {\n if (pattern === \"*\") return true;\n\n // Convert Next.js dynamic route pattern to regex\n const regexStr = pattern\n .replace(/\\[\\.\\.\\.[\\w]+\\]/g, \".*\") // [...slug] → .*\n .replace(/\\[[\\w]+\\]/g, \"[^/]+\") // [slug] → [^/]+\n .replace(/\\*/g, \"[^/]+\"); // * → [^/]+\n\n const regex = new RegExp(`^${regexStr}$`);\n return regex.test(path);\n}\n\n/**\n * Find the best matching route config for a given path.\n * More specific patterns take priority over wildcards.\n */\nexport function findRouteConfig(\n path: string,\n routes: Record<string, RouteConfig>\n): RouteConfig | null {\n // Sort routes by specificity (longer patterns first, * last)\n const sortedPatterns = Object.keys(routes).sort((a, b) => {\n if (a === \"*\") return 1;\n if (b === \"*\") return -1;\n return b.length - a.length;\n });\n\n for (const pattern of sortedPatterns) {\n if (matchRoute(path, pattern)) {\n return routes[pattern];\n }\n }\n\n return null;\n}\n","/**\n * OGPipe Local Dev Preview Server\n *\n * Boots a lightweight HTTP server on localhost:3010 that:\n * - Lists all routes from ogpipe.config.ts\n * - Renders OG images in real-time using the OGPipe API (or local Chromium fallback)\n * - Shows previews inside Twitter, LinkedIn, Slack, and Discord mockup frames\n * - Hot-reloads on template file changes via file watching\n *\n * Usage: npx ogpipe dev\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from \"http\";\nimport { readFileSync, watch, existsSync } from \"fs\";\nimport { resolve, extname } from \"path\";\nimport { OGPipeClient } from \"../client/index.js\";\nimport {\n OGPipeConfig,\n resolveTemplateHtml,\n injectVariables,\n} from \"../next/config.js\";\n\nconst DEFAULT_PORT = 3010;\n\ninterface PreviewRoute {\n path: string;\n template: string;\n vars: Record<string, string>;\n}\n\nexport async function startPreviewServer(options: {\n config: OGPipeConfig;\n configDir: string;\n port?: number;\n}) {\n const { config, configDir, port = DEFAULT_PORT } = options;\n\n // Collect preview routes from config\n const routes = buildPreviewRoutes(config);\n\n // Track connected SSE clients for hot-reload\n const clients: ServerResponse[] = [];\n\n // Watch template files for changes\n watchTemplateFiles(config, configDir, () => {\n // Notify all connected clients to reload\n for (const client of clients) {\n client.write(\"data: reload\\n\\n\");\n }\n });\n\n const server = createServer(async (req, res) => {\n const url = new URL(req.url || \"/\", `http://localhost:${port}`);\n const pathname = url.pathname;\n\n // SSE endpoint for hot-reload\n if (pathname === \"/__ogpipe/events\") {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n clients.push(res);\n req.on(\"close\", () => {\n const idx = clients.indexOf(res);\n if (idx >= 0) clients.splice(idx, 1);\n });\n return;\n }\n\n // Render a specific route's OG image\n if (pathname === \"/__ogpipe/render\") {\n const routePath = url.searchParams.get(\"route\") || \"/\";\n const templateId = url.searchParams.get(\"template\");\n await handleRender(req, res, config, configDir, routePath, templateId);\n return;\n }\n\n // API: list all routes\n if (pathname === \"/__ogpipe/routes\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(routes));\n return;\n }\n\n // Serve the preview dashboard UI\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(generateDashboardHtml(routes, port));\n });\n\n server.listen(port, () => {\n console.log(`\\n⚡ OGPipe Dev Preview`);\n console.log(` → http://localhost:${port}\\n`);\n console.log(` ${routes.length} routes configured`);\n console.log(` Watching templates for changes...\\n`);\n });\n}\n\n// ─────────────────────────────────────────────\n// Route Building\n// ─────────────────────────────────────────────\n\nfunction buildPreviewRoutes(config: OGPipeConfig): PreviewRoute[] {\n const routes: PreviewRoute[] = [];\n\n for (const [pattern, routeConfig] of Object.entries(config.routes)) {\n // Generate sample paths from patterns\n const samplePath = patternToSamplePath(pattern);\n const vars = typeof routeConfig.vars === \"function\"\n ? routeConfig.vars({ path: samplePath, title: \"Sample Title\", description: \"Sample description\", params: {} })\n : { title: \"Sample Title\", description: \"Sample description\" };\n\n routes.push({\n path: samplePath,\n template: routeConfig.template,\n vars,\n });\n }\n\n return routes;\n}\n\nfunction patternToSamplePath(pattern: string): string {\n if (pattern === \"*\") return \"/\";\n return pattern\n .replace(/\\[\\.\\.\\.(\\w+)\\]/g, \"example-$1\")\n .replace(/\\[(\\w+)\\]/g, \"example-$1\")\n .replace(/\\*/g, \"example\");\n}\n\n// ─────────────────────────────────────────────\n// Image Rendering\n// ─────────────────────────────────────────────\n\nasync function handleRender(\n req: IncomingMessage,\n res: ServerResponse,\n config: OGPipeConfig,\n configDir: string,\n routePath: string,\n templateId: string | null\n) {\n try {\n const tplId = templateId || Object.keys(config.templates)[0];\n const template = config.templates[tplId];\n\n if (!template) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Template '${tplId}' not found` }));\n return;\n }\n\n // Resolve and inject variables\n let html = resolveTemplateHtml(template, configDir);\n const vars = { title: \"Sample Blog Post Title\", description: \"A sample description for preview\", author: \"Developer\", date: \"June 2026\", site: \"mysite.dev\", category: \"Guide\" };\n html = injectVariables(html, vars);\n\n // Try rendering via API (if key available), otherwise return HTML for iframe\n const apiKey = config.apiKey || process.env.OGPIPE_API_KEY;\n\n if (apiKey) {\n const client = new OGPipeClient({ apiKey, baseUrl: config.baseUrl });\n const result = await client.render({ html, width: template.width || 1200, height: template.height || 630 });\n\n if (result.success) {\n // Proxy the image\n const imgRes = await fetch(result.data.url);\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n res.writeHead(200, {\n \"Content-Type\": `image/${result.data.format}`,\n \"Cache-Control\": \"no-cache\",\n });\n res.end(buffer);\n return;\n }\n }\n\n // Fallback: return the raw HTML for iframe-based preview\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : \"Render failed\" }));\n }\n}\n\n// ─────────────────────────────────────────────\n// File Watching\n// ─────────────────────────────────────────────\n\nfunction watchTemplateFiles(config: OGPipeConfig, configDir: string, onChange: () => void) {\n const filesToWatch: string[] = [];\n\n for (const template of Object.values(config.templates)) {\n if (template.file) {\n const fullPath = resolve(configDir, template.file);\n if (existsSync(fullPath)) {\n filesToWatch.push(fullPath);\n }\n }\n }\n\n for (const filePath of filesToWatch) {\n watch(filePath, { persistent: false }, (eventType) => {\n if (eventType === \"change\") {\n console.log(` ↻ Template changed: ${filePath}`);\n onChange();\n }\n });\n }\n}\n\n// ─────────────────────────────────────────────\n// Dashboard HTML\n// ─────────────────────────────────────────────\n\nfunction generateDashboardHtml(routes: PreviewRoute[], port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>OGPipe Dev Preview</title>\n <style>\n :root { --bg: #09090b; --surface: #18181b; --surface-2: #27272a; --border: #3f3f46; --text: #fafafa; --text-muted: #a1a1aa; --accent: #3b82f6; }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); padding: 32px; }\n h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; }\n .subtitle { color: var(--text-muted); font-size: 14px; margin-bottom: 32px; }\n .platforms { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }\n .platform-btn { padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); cursor: pointer; font-size: 13px; transition: all 0.15s; }\n .platform-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(59,130,246,0.1); }\n .preview-container { display: grid; grid-template-columns: 1fr; gap: 24px; }\n .preview-card { background: var(--surface); border: 1px solid var(--surface-2); border-radius: 12px; padding: 24px; }\n .preview-card h3 { font-size: 14px; color: var(--text-muted); margin-bottom: 12px; }\n .route-info { font-family: monospace; font-size: 12px; color: var(--accent); margin-bottom: 16px; }\n\n /* Platform mockups */\n .mockup { border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }\n .mockup-twitter { max-width: 506px; }\n .mockup-linkedin { max-width: 552px; }\n .mockup-slack { max-width: 400px; }\n .mockup-discord { max-width: 432px; }\n\n .mockup img, .mockup iframe { width: 100%; aspect-ratio: 1200/630; display: block; border: none; background: var(--surface-2); }\n\n .mockup-frame { padding: 12px; background: var(--surface-2); border-radius: 8px; }\n .mockup-meta { padding: 8px 12px; font-size: 12px; color: var(--text-muted); }\n .mockup-meta .title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 2px; }\n .mockup-meta .desc { font-size: 12px; color: var(--text-muted); }\n .mockup-meta .domain { font-size: 11px; color: var(--text-muted); margin-top: 4px; opacity: 0.7; }\n\n .routes-list { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; }\n .route-pill { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); cursor: pointer; font-size: 13px; font-family: monospace; }\n .route-pill.active { border-color: var(--accent); color: var(--accent); }\n\n .reload-badge { position: fixed; top: 16px; right: 16px; background: var(--accent); color: white; padding: 6px 12px; border-radius: 6px; font-size: 12px; display: none; animation: fadeIn 0.2s; }\n @keyframes fadeIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }\n </style>\n</head>\n<body>\n <div class=\"reload-badge\" id=\"reload-badge\">↻ Reloading...</div>\n <h1>⚡ OGPipe Dev Preview</h1>\n <p class=\"subtitle\">Live preview of your OG images across social platforms. Edit templates → see changes instantly.</p>\n\n <div class=\"routes-list\" id=\"routes-list\">\n ${routes.map((r, i) => `<button class=\"route-pill ${i === 0 ? \"active\" : \"\"}\" data-route=\"${r.path}\" data-template=\"${r.template}\">${r.path} (${r.template})</button>`).join(\"\\n \")}\n </div>\n\n <div class=\"platforms\">\n <button class=\"platform-btn active\" data-platform=\"twitter\">𝕏 / Twitter</button>\n <button class=\"platform-btn\" data-platform=\"linkedin\">LinkedIn</button>\n <button class=\"platform-btn\" data-platform=\"slack\">Slack</button>\n <button class=\"platform-btn\" data-platform=\"discord\">Discord</button>\n </div>\n\n <div class=\"preview-container\" id=\"preview-container\">\n <div class=\"preview-card\">\n <div class=\"mockup mockup-twitter\">\n <div class=\"mockup-frame\">\n <img id=\"og-preview\" src=\"/__ogpipe/render?route=/&template=${routes[0]?.template || \"default\"}\" alt=\"OG Image Preview\" />\n <div class=\"mockup-meta\">\n <div class=\"title\">Sample Blog Post Title</div>\n <div class=\"desc\">A sample description for preview</div>\n <div class=\"domain\">mysite.dev</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n const preview = document.getElementById('og-preview');\n const reloadBadge = document.getElementById('reload-badge');\n\n // Route selection\n document.querySelectorAll('.route-pill').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.route-pill').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n const route = btn.dataset.route;\n const template = btn.dataset.template;\n preview.src = '/__ogpipe/render?route=' + encodeURIComponent(route) + '&template=' + encodeURIComponent(template) + '&t=' + Date.now();\n });\n });\n\n // Platform selection (visual only for now — frame styling)\n document.querySelectorAll('.platform-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.platform-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n });\n });\n\n // SSE hot-reload\n const evtSource = new EventSource('/__ogpipe/events');\n evtSource.onmessage = (event) => {\n if (event.data === 'reload') {\n reloadBadge.style.display = 'block';\n // Cache-bust the image\n preview.src = preview.src.replace(/[&?]t=\\\\d+/, '') + '&t=' + Date.now();\n setTimeout(() => { reloadBadge.style.display = 'none'; }, 1500);\n }\n };\n </script>\n</body>\n</html>`;\n}\n","#!/usr/bin/env node\n\n/**\n * OGPipe CLI — generates OG images after `next build`.\n *\n * Usage:\n * npx ogpipe generate # Generate OG images for all static routes\n * npx ogpipe generate --dry # Show what would be generated without calling API\n * npx ogpipe dev # Start local preview server (hot-reload)\n */\n\nimport { resolve, dirname } from \"path\";\nimport { existsSync } from \"fs\";\nimport { generateOGImages } from \"../next/generator.js\";\nimport type { OGPipeConfig } from \"../next/config.js\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nasync function main() {\n switch (command) {\n case \"generate\":\n await runGenerate();\n break;\n case \"dev\":\n await runDev();\n break;\n case \"--help\":\n case \"-h\":\n case undefined:\n printHelp();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n}\n\nasync function runGenerate() {\n const isDry = args.includes(\"--dry\");\n const projectDir = process.cwd();\n\n console.log(\"\\n⚡ OGPipe — Generating OG images...\\n\");\n\n // Load config\n const config = await loadConfig(projectDir);\n if (!config) {\n console.error(\"❌ No ogpipe.config.ts found in project root.\");\n console.error(\" Create one with: import { defineConfig } from '@ogpipe/next'\");\n process.exit(1);\n }\n\n if (isDry) {\n console.log(\"🔍 Dry run — showing what would be generated:\\n\");\n // TODO: implement dry-run (list routes + templates without API calls)\n console.log(\" (dry run not yet implemented — run without --dry to generate)\");\n return;\n }\n\n // Check .next directory exists\n if (!existsSync(resolve(projectDir, \".next\"))) {\n console.error(\"❌ No .next directory found. Run 'next build' first.\");\n process.exit(1);\n }\n\n try {\n const configDir = projectDir; // config is at project root\n const report = await generateOGImages({\n projectDir,\n config,\n configDir,\n });\n\n // Print results\n console.log(`✅ Generated ${report.success.length} OG images in ${report.totalDurationMs}ms\\n`);\n\n for (const result of report.success) {\n console.log(` ${result.route} → ${result.outputPath} (${result.durationMs}ms)`);\n }\n\n if (report.failed.length > 0) {\n console.log(`\\n⚠️ ${report.failed.length} failed:\\n`);\n for (const failure of report.failed) {\n console.log(` ${failure.route}: ${failure.error}`);\n }\n }\n\n console.log(`\\n📁 Output: ${config.outDir || \"public/og/\"}`);\n console.log(`📋 Manifest: ${config.outDir || \"public/og\"}/og-manifest.json\\n`);\n } catch (err) {\n console.error(`\\n❌ Generation failed: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\nasync function runDev() {\n const projectDir = process.cwd();\n\n console.log(\"\\n⚡ OGPipe Dev Preview — starting...\\n\");\n\n // Load config\n const config = await loadConfig(projectDir);\n if (!config) {\n console.error(\"❌ No ogpipe.config.ts found in project root.\");\n console.error(\" Create one with: import { defineConfig } from '@ogpipe/next'\");\n process.exit(1);\n }\n\n const { startPreviewServer } = await import(\"../preview/server.js\");\n await startPreviewServer({\n config,\n configDir: projectDir,\n port: parseInt(process.env.OGPIPE_PORT || \"3010\"),\n });\n}\n\nasync function loadConfig(projectDir: string): Promise<OGPipeConfig | null> {\n // Try loading ogpipe.config.ts / ogpipe.config.js / ogpipe.config.mjs\n const configPaths = [\n resolve(projectDir, \"ogpipe.config.ts\"),\n resolve(projectDir, \"ogpipe.config.js\"),\n resolve(projectDir, \"ogpipe.config.mjs\"),\n ];\n\n for (const configPath of configPaths) {\n if (existsSync(configPath)) {\n try {\n // Dynamic import handles both TS and JS\n const module = await import(configPath);\n return module.default || module;\n } catch (err) {\n // If TS import fails, try with tsx/ts-node loader\n try {\n // Fallback: try requiring with ts-node\n const module = await import(`file://${configPath}`);\n return module.default || module;\n } catch {\n console.error(`❌ Failed to load config: ${configPath}`);\n console.error(` ${err instanceof Error ? err.message : err}`);\n return null;\n }\n }\n }\n }\n\n return null;\n}\n\nfunction printHelp() {\n console.log(`\n⚡ OGPipe CLI — Pixel-perfect OG images for Next.js\n\nCOMMANDS:\n generate Generate OG images for all static routes (run after next build)\n generate --dry Show what would be generated without calling the API\n dev Start local preview server with hot-reload\n\nSETUP:\n 1. Create ogpipe.config.ts in your project root\n 2. Add to package.json scripts: \"build\": \"next build && ogpipe generate\"\n 3. Set OGPIPE_API_KEY environment variable\n\nDOCS:\n https://ogpipe.dev/docs.html\n\nEXAMPLE CONFIG:\n import { defineConfig } from '@ogpipe/next'\n\n export default defineConfig({\n templates: {\n blog: { file: './og-templates/blog.html' },\n default: { html: '<div style=\"...\">{{title}}</div>' },\n },\n routes: {\n '/blog/[slug]': { template: 'blog', vars: (meta) => ({ title: meta.title }) },\n '*': { template: 'default' },\n },\n })\n`);\n}\n\nmain().catch((err) => {\n console.error(\"Fatal error:\", err);\n process.exit(1);\n});\n","/**\n * OG Image Generator — called by the CLI after `next build`.\n *\n * Reads prerender-manifest.json to discover all static routes,\n * matches them against the user's ogpipe.config.ts,\n * renders OG images via the OGPipe API,\n * and writes them to public/og/.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"fs\";\nimport { resolve, join, dirname } from \"path\";\nimport { OGPipeClient } from \"../client/index.js\";\nimport {\n OGPipeConfig,\n resolveTemplateHtml,\n injectVariables,\n findRouteConfig,\n RouteMetadata,\n} from \"./config.js\";\n\n// ─────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────\n\ninterface PrerenderManifest {\n version: number;\n routes: Record<string, { initialRevalidateSeconds: number | false }>;\n dynamicRoutes: Record<string, { routeRegex: string; fallback: string | false }>;\n}\n\ninterface GenerateResult {\n route: string;\n outputPath: string;\n imageUrl: string;\n durationMs: number;\n}\n\ninterface GenerateReport {\n success: GenerateResult[];\n failed: { route: string; error: string }[];\n totalDurationMs: number;\n}\n\n// ─────────────────────────────────────────────\n// Generator\n// ─────────────────────────────────────────────\n\nexport async function generateOGImages(options: {\n projectDir: string;\n config: OGPipeConfig;\n configDir: string;\n concurrency?: number;\n}): Promise<GenerateReport> {\n const { projectDir, config, configDir, concurrency = 5 } = options;\n const startTime = Date.now();\n\n // 1. Read prerender manifest\n const manifestPath = join(projectDir, \".next\", \"prerender-manifest.json\");\n if (!existsSync(manifestPath)) {\n throw new Error(\n `[OGPipe] Cannot find .next/prerender-manifest.json. Run 'next build' first.`\n );\n }\n\n const manifest: PrerenderManifest = JSON.parse(\n readFileSync(manifestPath, \"utf-8\")\n );\n\n // 2. Collect all static routes\n const routes = Object.keys(manifest.routes).filter(\n (route) => route !== \"/_not-found\" && !route.startsWith(\"/_\")\n );\n\n if (routes.length === 0) {\n return { success: [], failed: [], totalDurationMs: 0 };\n }\n\n // 3. Ensure output directory exists\n const outDir = resolve(projectDir, config.outDir || \"public/og\");\n mkdirSync(outDir, { recursive: true });\n\n // 4. Initialize API client\n const client = new OGPipeClient({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n });\n\n // 5. Generate images with concurrency control\n const results: GenerateResult[] = [];\n const failures: { route: string; error: string }[] = [];\n\n // Process in batches\n for (let i = 0; i < routes.length; i += concurrency) {\n const batch = routes.slice(i, i + concurrency);\n const batchPromises = batch.map((route) =>\n generateSingleImage({ route, config, configDir, client, outDir })\n );\n\n const batchResults = await Promise.allSettled(batchPromises);\n\n for (let j = 0; j < batchResults.length; j++) {\n const result = batchResults[j];\n const route = batch[j];\n\n if (result.status === \"fulfilled\" && result.value) {\n results.push(result.value);\n } else if (result.status === \"rejected\") {\n failures.push({ route, error: result.reason?.message || \"Unknown error\" });\n } else if (result.status === \"fulfilled\" && result.value === null) {\n // No matching route config — skip silently\n }\n }\n }\n\n // 6. Write og-manifest.json\n const ogManifest = Object.fromEntries(\n results.map((r) => [r.route, { path: r.outputPath, url: r.imageUrl }])\n );\n writeFileSync(\n join(outDir, \"og-manifest.json\"),\n JSON.stringify(ogManifest, null, 2)\n );\n\n return {\n success: results,\n failed: failures,\n totalDurationMs: Date.now() - startTime,\n };\n}\n\n// ─────────────────────────────────────────────\n// Single Image Generation\n// ─────────────────────────────────────────────\n\nasync function generateSingleImage(options: {\n route: string;\n config: OGPipeConfig;\n configDir: string;\n client: OGPipeClient;\n outDir: string;\n}): Promise<GenerateResult | null> {\n const { route, config, configDir, client, outDir } = options;\n const startTime = Date.now();\n\n // Find matching route config\n const routeConfig = findRouteConfig(route, config.routes);\n if (!routeConfig) return null;\n\n // Get template\n const template = config.templates[routeConfig.template];\n if (!template) {\n throw new Error(`[OGPipe] Template '${routeConfig.template}' not found for route ${route}`);\n }\n\n // Resolve HTML\n let html = resolveTemplateHtml(template, configDir);\n\n // Extract metadata and inject variables\n const metadata: RouteMetadata = {\n path: route,\n title: extractTitleFromRoute(route),\n description: \"\",\n params: extractParamsFromRoute(route),\n };\n\n const vars = routeConfig.vars\n ? routeConfig.vars(metadata)\n : { title: metadata.title || \"\", description: metadata.description || \"\" };\n\n html = injectVariables(html, vars);\n\n // Render via API\n const result = await client.render({\n html,\n width: template.width || 1200,\n height: template.height || 630,\n format: template.format || \"png\",\n });\n\n if (!result.success) {\n throw new Error(`API error for ${route}: ${result.error}`);\n }\n\n // Download and save locally\n const filename = routeToFilename(route, template.format || \"png\");\n const outputPath = join(outDir, filename);\n\n const imageBuffer = await client.renderToBuffer({\n html,\n width: template.width || 1200,\n height: template.height || 630,\n format: template.format || \"png\",\n });\n\n if (imageBuffer) {\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, imageBuffer);\n }\n\n return {\n route,\n outputPath: `/og/${filename}`,\n imageUrl: result.data.url,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ─────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────\n\n/**\n * Convert a route path to a safe filename.\n * /blog/my-post → blog/my-post.png\n * / → index.png\n */\nfunction routeToFilename(route: string, format: string): string {\n if (route === \"/\") return `index.${format}`;\n // Remove leading slash, replace remaining slashes with directory separator\n const clean = route.replace(/^\\//, \"\");\n return `${clean}.${format}`;\n}\n\n/**\n * Extract a human-readable title from a route path.\n * /blog/my-awesome-post → My Awesome Post\n */\nfunction extractTitleFromRoute(route: string): string {\n const segments = route.split(\"/\").filter(Boolean);\n const last = segments[segments.length - 1] || \"Home\";\n return last\n .replace(/[-_]/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Extract dynamic params from a route path.\n * /blog/my-post → { slug: 'my-post' } (heuristic)\n */\nfunction extractParamsFromRoute(route: string): Record<string, string> {\n const segments = route.split(\"/\").filter(Boolean);\n if (segments.length >= 2) {\n return { slug: segments[segments.length - 1] };\n }\n return {};\n}\n"],"mappings":"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","names":["existsSync","resolve","resolve","existsSync","readFileSync","resolve","dirname","existsSync","resolve","startPreviewServer"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ogpipe/next/client — Framework-agnostic API client for OGPipe
|
|
3
|
+
*
|
|
4
|
+
* This is the thin HTTP client that communicates with the OGPipe rendering API.
|
|
5
|
+
* Can be used standalone (Python, Go, curl equivalent) or via the Next.js integration.
|
|
6
|
+
*/
|
|
7
|
+
interface OGPipeClientOptions {
|
|
8
|
+
/** API key (og_live_xxx). Defaults to OGPIPE_API_KEY env var. */
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/** API base URL. Defaults to https://api.ogpipe.dev */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Request timeout in ms. Defaults to 30000. */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
interface RenderRequest {
|
|
16
|
+
/** HTML content to render */
|
|
17
|
+
html: string;
|
|
18
|
+
/** Image width (default: 1200) */
|
|
19
|
+
width?: number;
|
|
20
|
+
/** Image height (default: 630) */
|
|
21
|
+
height?: number;
|
|
22
|
+
/** Output format (default: png) */
|
|
23
|
+
format?: "png" | "jpeg" | "webp";
|
|
24
|
+
}
|
|
25
|
+
interface RenderResponse {
|
|
26
|
+
/** CDN URL of the rendered image */
|
|
27
|
+
url: string;
|
|
28
|
+
/** Image width */
|
|
29
|
+
width: number;
|
|
30
|
+
/** Image height */
|
|
31
|
+
height: number;
|
|
32
|
+
/** Output format */
|
|
33
|
+
format: string;
|
|
34
|
+
/** Whether served from cache */
|
|
35
|
+
cached: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface RenderResult {
|
|
38
|
+
success: true;
|
|
39
|
+
data: RenderResponse;
|
|
40
|
+
}
|
|
41
|
+
interface RenderError {
|
|
42
|
+
success: false;
|
|
43
|
+
error: string;
|
|
44
|
+
statusCode: number;
|
|
45
|
+
}
|
|
46
|
+
type RenderOutcome = RenderResult | RenderError;
|
|
47
|
+
/**
|
|
48
|
+
* OGPipe API client.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { OGPipeClient } from '@ogpipe/next/client'
|
|
53
|
+
*
|
|
54
|
+
* const client = new OGPipeClient({ apiKey: 'og_live_xxx' })
|
|
55
|
+
* const result = await client.render({ html: '<div>Hello</div>' })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare class OGPipeClient {
|
|
59
|
+
private apiKey;
|
|
60
|
+
private baseUrl;
|
|
61
|
+
private timeout;
|
|
62
|
+
constructor(options?: OGPipeClientOptions);
|
|
63
|
+
/**
|
|
64
|
+
* Render HTML to an image. Returns the CDN URL.
|
|
65
|
+
*/
|
|
66
|
+
render(request: RenderRequest): Promise<RenderOutcome>;
|
|
67
|
+
/**
|
|
68
|
+
* Render HTML and return the raw image buffer (for writing to disk).
|
|
69
|
+
*/
|
|
70
|
+
renderToBuffer(request: RenderRequest): Promise<Buffer | null>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { OGPipeClient, type OGPipeClientOptions, type RenderError, type RenderOutcome, type RenderRequest, type RenderResponse, type RenderResult };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ogpipe/next/client — Framework-agnostic API client for OGPipe
|
|
3
|
+
*
|
|
4
|
+
* This is the thin HTTP client that communicates with the OGPipe rendering API.
|
|
5
|
+
* Can be used standalone (Python, Go, curl equivalent) or via the Next.js integration.
|
|
6
|
+
*/
|
|
7
|
+
interface OGPipeClientOptions {
|
|
8
|
+
/** API key (og_live_xxx). Defaults to OGPIPE_API_KEY env var. */
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/** API base URL. Defaults to https://api.ogpipe.dev */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Request timeout in ms. Defaults to 30000. */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
interface RenderRequest {
|
|
16
|
+
/** HTML content to render */
|
|
17
|
+
html: string;
|
|
18
|
+
/** Image width (default: 1200) */
|
|
19
|
+
width?: number;
|
|
20
|
+
/** Image height (default: 630) */
|
|
21
|
+
height?: number;
|
|
22
|
+
/** Output format (default: png) */
|
|
23
|
+
format?: "png" | "jpeg" | "webp";
|
|
24
|
+
}
|
|
25
|
+
interface RenderResponse {
|
|
26
|
+
/** CDN URL of the rendered image */
|
|
27
|
+
url: string;
|
|
28
|
+
/** Image width */
|
|
29
|
+
width: number;
|
|
30
|
+
/** Image height */
|
|
31
|
+
height: number;
|
|
32
|
+
/** Output format */
|
|
33
|
+
format: string;
|
|
34
|
+
/** Whether served from cache */
|
|
35
|
+
cached: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface RenderResult {
|
|
38
|
+
success: true;
|
|
39
|
+
data: RenderResponse;
|
|
40
|
+
}
|
|
41
|
+
interface RenderError {
|
|
42
|
+
success: false;
|
|
43
|
+
error: string;
|
|
44
|
+
statusCode: number;
|
|
45
|
+
}
|
|
46
|
+
type RenderOutcome = RenderResult | RenderError;
|
|
47
|
+
/**
|
|
48
|
+
* OGPipe API client.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { OGPipeClient } from '@ogpipe/next/client'
|
|
53
|
+
*
|
|
54
|
+
* const client = new OGPipeClient({ apiKey: 'og_live_xxx' })
|
|
55
|
+
* const result = await client.render({ html: '<div>Hello</div>' })
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare class OGPipeClient {
|
|
59
|
+
private apiKey;
|
|
60
|
+
private baseUrl;
|
|
61
|
+
private timeout;
|
|
62
|
+
constructor(options?: OGPipeClientOptions);
|
|
63
|
+
/**
|
|
64
|
+
* Render HTML to an image. Returns the CDN URL.
|
|
65
|
+
*/
|
|
66
|
+
render(request: RenderRequest): Promise<RenderOutcome>;
|
|
67
|
+
/**
|
|
68
|
+
* Render HTML and return the raw image buffer (for writing to disk).
|
|
69
|
+
*/
|
|
70
|
+
renderToBuffer(request: RenderRequest): Promise<Buffer | null>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { OGPipeClient, type OGPipeClientOptions, type RenderError, type RenderOutcome, type RenderRequest, type RenderResponse, type RenderResult };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client/index.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
OGPipeClient: () => OGPipeClient
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(client_exports);
|
|
26
|
+
var OGPipeClient = class {
|
|
27
|
+
apiKey;
|
|
28
|
+
baseUrl;
|
|
29
|
+
timeout;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.apiKey = options.apiKey || process.env.OGPIPE_API_KEY || "";
|
|
32
|
+
this.baseUrl = options.baseUrl || process.env.OGPIPE_BASE_URL || "https://api.ogpipe.dev";
|
|
33
|
+
this.timeout = options.timeout || 3e4;
|
|
34
|
+
if (!this.apiKey) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"[OGPipe] Missing API key. Set OGPIPE_API_KEY environment variable or pass apiKey option."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Render HTML to an image. Returns the CDN URL.
|
|
42
|
+
*/
|
|
43
|
+
async render(request) {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${this.baseUrl}/images`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
51
|
+
"Content-Type": "application/json"
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
html: request.html,
|
|
55
|
+
width: request.width || 1200,
|
|
56
|
+
height: request.height || 630,
|
|
57
|
+
format: request.format || "png"
|
|
58
|
+
}),
|
|
59
|
+
signal: controller.signal
|
|
60
|
+
});
|
|
61
|
+
const body = await res.json();
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: body.error || `HTTP ${res.status}`,
|
|
66
|
+
statusCode: res.status
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
data: body
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
75
|
+
return { success: false, error: "Request timed out", statusCode: 408 };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
80
|
+
statusCode: 500
|
|
81
|
+
};
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Render HTML and return the raw image buffer (for writing to disk).
|
|
88
|
+
*/
|
|
89
|
+
async renderToBuffer(request) {
|
|
90
|
+
const result = await this.render(request);
|
|
91
|
+
if (!result.success) return null;
|
|
92
|
+
const res = await fetch(result.data.url);
|
|
93
|
+
if (!res.ok) return null;
|
|
94
|
+
return Buffer.from(await res.arrayBuffer());
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
98
|
+
0 && (module.exports = {
|
|
99
|
+
OGPipeClient
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * @ogpipe/next/client — Framework-agnostic API client for OGPipe\n *\n * This is the thin HTTP client that communicates with the OGPipe rendering API.\n * Can be used standalone (Python, Go, curl equivalent) or via the Next.js integration.\n */\n\nexport interface OGPipeClientOptions {\n /** API key (og_live_xxx). Defaults to OGPIPE_API_KEY env var. */\n apiKey?: string;\n /** API base URL. Defaults to https://api.ogpipe.dev */\n baseUrl?: string;\n /** Request timeout in ms. Defaults to 30000. */\n timeout?: number;\n}\n\nexport interface RenderRequest {\n /** HTML content to render */\n html: string;\n /** Image width (default: 1200) */\n width?: number;\n /** Image height (default: 630) */\n height?: number;\n /** Output format (default: png) */\n format?: \"png\" | \"jpeg\" | \"webp\";\n}\n\nexport interface RenderResponse {\n /** CDN URL of the rendered image */\n url: string;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n /** Output format */\n format: string;\n /** Whether served from cache */\n cached: boolean;\n}\n\nexport interface RenderResult {\n success: true;\n data: RenderResponse;\n}\n\nexport interface RenderError {\n success: false;\n error: string;\n statusCode: number;\n}\n\nexport type RenderOutcome = RenderResult | RenderError;\n\n/**\n * OGPipe API client.\n *\n * Usage:\n * ```ts\n * import { OGPipeClient } from '@ogpipe/next/client'\n *\n * const client = new OGPipeClient({ apiKey: 'og_live_xxx' })\n * const result = await client.render({ html: '<div>Hello</div>' })\n * ```\n */\nexport class OGPipeClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n\n constructor(options: OGPipeClientOptions = {}) {\n this.apiKey = options.apiKey || process.env.OGPIPE_API_KEY || \"\";\n this.baseUrl = options.baseUrl || process.env.OGPIPE_BASE_URL || \"https://api.ogpipe.dev\";\n this.timeout = options.timeout || 30_000;\n\n if (!this.apiKey) {\n throw new Error(\n \"[OGPipe] Missing API key. Set OGPIPE_API_KEY environment variable or pass apiKey option.\"\n );\n }\n }\n\n /**\n * Render HTML to an image. Returns the CDN URL.\n */\n async render(request: RenderRequest): Promise<RenderOutcome> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}/images`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n html: request.html,\n width: request.width || 1200,\n height: request.height || 630,\n format: request.format || \"png\",\n }),\n signal: controller.signal,\n });\n\n const body = await res.json() as { error?: string };\n\n if (!res.ok) {\n return {\n success: false,\n error: body.error || `HTTP ${res.status}`,\n statusCode: res.status,\n };\n }\n\n return {\n success: true,\n data: body as RenderResponse,\n };\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n return { success: false, error: \"Request timed out\", statusCode: 408 };\n }\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n statusCode: 500,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Render HTML and return the raw image buffer (for writing to disk).\n */\n async renderToBuffer(request: RenderRequest): Promise<Buffer | null> {\n const result = await this.render(request);\n if (!result.success) return null;\n\n // Fetch the image from CDN URL\n const res = await fetch(result.data.url);\n if (!res.ok) return null;\n\n return Buffer.from(await res.arrayBuffer());\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgEO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,UAA+B,CAAC,GAAG;AAC7C,SAAK,SAAS,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;AAC9D,SAAK,UAAU,QAAQ,WAAW,QAAQ,IAAI,mBAAmB;AACjE,SAAK,UAAU,QAAQ,WAAW;AAElC,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,SAAgD;AAC3D,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW;AAAA,QAChD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,KAAK,MAAM;AAAA,UACpC,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,MAAM,QAAQ;AAAA,UACd,OAAO,QAAQ,SAAS;AAAA,UACxB,QAAQ,QAAQ,UAAU;AAAA,UAC1B,QAAQ,QAAQ,UAAU;AAAA,QAC5B,CAAC;AAAA,QACD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,YAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,UAAI,CAAC,IAAI,IAAI;AACX,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,KAAK,SAAS,QAAQ,IAAI,MAAM;AAAA,UACvC,YAAY,IAAI;AAAA,QAClB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,eAAO,EAAE,SAAS,OAAO,OAAO,qBAAqB,YAAY,IAAI;AAAA,MACvE;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC5C,YAAY;AAAA,MACd;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,SAAgD;AACnE,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO;AACxC,QAAI,CAAC,OAAO,QAAS,QAAO;AAG5B,UAAM,MAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACvC,QAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,WAAO,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC;AAAA,EAC5C;AACF;","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/client/index.ts
|
|
2
|
+
var OGPipeClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
timeout;
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.apiKey = options.apiKey || process.env.OGPIPE_API_KEY || "";
|
|
8
|
+
this.baseUrl = options.baseUrl || process.env.OGPIPE_BASE_URL || "https://api.ogpipe.dev";
|
|
9
|
+
this.timeout = options.timeout || 3e4;
|
|
10
|
+
if (!this.apiKey) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"[OGPipe] Missing API key. Set OGPIPE_API_KEY environment variable or pass apiKey option."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Render HTML to an image. Returns the CDN URL.
|
|
18
|
+
*/
|
|
19
|
+
async render(request) {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(`${this.baseUrl}/images`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
27
|
+
"Content-Type": "application/json"
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
html: request.html,
|
|
31
|
+
width: request.width || 1200,
|
|
32
|
+
height: request.height || 630,
|
|
33
|
+
format: request.format || "png"
|
|
34
|
+
}),
|
|
35
|
+
signal: controller.signal
|
|
36
|
+
});
|
|
37
|
+
const body = await res.json();
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: body.error || `HTTP ${res.status}`,
|
|
42
|
+
statusCode: res.status
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
data: body
|
|
48
|
+
};
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
51
|
+
return { success: false, error: "Request timed out", statusCode: 408 };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
56
|
+
statusCode: 500
|
|
57
|
+
};
|
|
58
|
+
} finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Render HTML and return the raw image buffer (for writing to disk).
|
|
64
|
+
*/
|
|
65
|
+
async renderToBuffer(request) {
|
|
66
|
+
const result = await this.render(request);
|
|
67
|
+
if (!result.success) return null;
|
|
68
|
+
const res = await fetch(result.data.url);
|
|
69
|
+
if (!res.ok) return null;
|
|
70
|
+
return Buffer.from(await res.arrayBuffer());
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
export {
|
|
74
|
+
OGPipeClient
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * @ogpipe/next/client — Framework-agnostic API client for OGPipe\n *\n * This is the thin HTTP client that communicates with the OGPipe rendering API.\n * Can be used standalone (Python, Go, curl equivalent) or via the Next.js integration.\n */\n\nexport interface OGPipeClientOptions {\n /** API key (og_live_xxx). Defaults to OGPIPE_API_KEY env var. */\n apiKey?: string;\n /** API base URL. Defaults to https://api.ogpipe.dev */\n baseUrl?: string;\n /** Request timeout in ms. Defaults to 30000. */\n timeout?: number;\n}\n\nexport interface RenderRequest {\n /** HTML content to render */\n html: string;\n /** Image width (default: 1200) */\n width?: number;\n /** Image height (default: 630) */\n height?: number;\n /** Output format (default: png) */\n format?: \"png\" | \"jpeg\" | \"webp\";\n}\n\nexport interface RenderResponse {\n /** CDN URL of the rendered image */\n url: string;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n /** Output format */\n format: string;\n /** Whether served from cache */\n cached: boolean;\n}\n\nexport interface RenderResult {\n success: true;\n data: RenderResponse;\n}\n\nexport interface RenderError {\n success: false;\n error: string;\n statusCode: number;\n}\n\nexport type RenderOutcome = RenderResult | RenderError;\n\n/**\n * OGPipe API client.\n *\n * Usage:\n * ```ts\n * import { OGPipeClient } from '@ogpipe/next/client'\n *\n * const client = new OGPipeClient({ apiKey: 'og_live_xxx' })\n * const result = await client.render({ html: '<div>Hello</div>' })\n * ```\n */\nexport class OGPipeClient {\n private apiKey: string;\n private baseUrl: string;\n private timeout: number;\n\n constructor(options: OGPipeClientOptions = {}) {\n this.apiKey = options.apiKey || process.env.OGPIPE_API_KEY || \"\";\n this.baseUrl = options.baseUrl || process.env.OGPIPE_BASE_URL || \"https://api.ogpipe.dev\";\n this.timeout = options.timeout || 30_000;\n\n if (!this.apiKey) {\n throw new Error(\n \"[OGPipe] Missing API key. Set OGPIPE_API_KEY environment variable or pass apiKey option.\"\n );\n }\n }\n\n /**\n * Render HTML to an image. Returns the CDN URL.\n */\n async render(request: RenderRequest): Promise<RenderOutcome> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}/images`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n html: request.html,\n width: request.width || 1200,\n height: request.height || 630,\n format: request.format || \"png\",\n }),\n signal: controller.signal,\n });\n\n const body = await res.json() as { error?: string };\n\n if (!res.ok) {\n return {\n success: false,\n error: body.error || `HTTP ${res.status}`,\n statusCode: res.status,\n };\n }\n\n return {\n success: true,\n data: body as RenderResponse,\n };\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n return { success: false, error: \"Request timed out\", statusCode: 408 };\n }\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n statusCode: 500,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Render HTML and return the raw image buffer (for writing to disk).\n */\n async renderToBuffer(request: RenderRequest): Promise<Buffer | null> {\n const result = await this.render(request);\n if (!result.success) return null;\n\n // Fetch the image from CDN URL\n const res = await fetch(result.data.url);\n if (!res.ok) return null;\n\n return Buffer.from(await res.arrayBuffer());\n }\n}\n"],"mappings":";AAgEO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,UAA+B,CAAC,GAAG;AAC7C,SAAK,SAAS,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;AAC9D,SAAK,UAAU,QAAQ,WAAW,QAAQ,IAAI,mBAAmB;AACjE,SAAK,UAAU,QAAQ,WAAW;AAElC,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,SAAgD;AAC3D,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW;AAAA,QAChD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,KAAK,MAAM;AAAA,UACpC,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,MAAM,QAAQ;AAAA,UACd,OAAO,QAAQ,SAAS;AAAA,UACxB,QAAQ,QAAQ,UAAU;AAAA,UAC1B,QAAQ,QAAQ,UAAU;AAAA,QAC5B,CAAC;AAAA,QACD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,YAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,UAAI,CAAC,IAAI,IAAI;AACX,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,KAAK,SAAS,QAAQ,IAAI,MAAM;AAAA,UACvC,YAAY,IAAI;AAAA,QAClB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,eAAO,EAAE,SAAS,OAAO,OAAO,qBAAqB,YAAY,IAAI;AAAA,MACvE;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC5C,YAAY;AAAA,MACd;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,SAAgD;AACnE,UAAM,SAAS,MAAM,KAAK,OAAO,OAAO;AACxC,QAAI,CAAC,OAAO,QAAS,QAAO;AAG5B,UAAM,MAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACvC,QAAI,CAAC,IAAI,GAAI,QAAO;AAEpB,WAAO,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC;AAAA,EAC5C;AACF;","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export { OGPipeClient, OGPipeClientOptions, RenderError, RenderOutcome, RenderRequest, RenderResponse, RenderResult } from './client/index.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OGPipe configuration schema.
|
|
5
|
+
*
|
|
6
|
+
* Developers define this in ogpipe.config.ts at their project root.
|
|
7
|
+
* The CLI reads this to know which templates to use for which routes.
|
|
8
|
+
*/
|
|
9
|
+
interface OGPipeTemplate {
|
|
10
|
+
/** Inline HTML string with {{variable}} placeholders */
|
|
11
|
+
html?: string;
|
|
12
|
+
/** Path to an HTML template file (relative to config file) */
|
|
13
|
+
file?: string;
|
|
14
|
+
/** Image width (default: 1200) */
|
|
15
|
+
width?: number;
|
|
16
|
+
/** Image height (default: 630) */
|
|
17
|
+
height?: number;
|
|
18
|
+
/** Output format (default: png) */
|
|
19
|
+
format?: "png" | "jpeg" | "webp";
|
|
20
|
+
}
|
|
21
|
+
interface RouteConfig {
|
|
22
|
+
/** Template ID to use for this route */
|
|
23
|
+
template: string;
|
|
24
|
+
/** Function to extract variables from page metadata */
|
|
25
|
+
vars?: (metadata: RouteMetadata) => Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
interface RouteMetadata {
|
|
28
|
+
/** Page title from metadata export */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Page description from metadata export */
|
|
31
|
+
description?: string;
|
|
32
|
+
/** Route path (e.g., /blog/my-post) */
|
|
33
|
+
path: string;
|
|
34
|
+
/** Route params (e.g., { slug: 'my-post' }) */
|
|
35
|
+
params?: Record<string, string>;
|
|
36
|
+
/** Any additional metadata from the page */
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface OnDemandConfig {
|
|
40
|
+
/** Cache duration in seconds (default: 86400 = 24h) */
|
|
41
|
+
revalidate?: number;
|
|
42
|
+
/** Fallback image path if API is unavailable */
|
|
43
|
+
fallback?: string;
|
|
44
|
+
}
|
|
45
|
+
interface OGPipeConfig {
|
|
46
|
+
/** API key. Defaults to OGPIPE_API_KEY env var. */
|
|
47
|
+
apiKey?: string;
|
|
48
|
+
/** API base URL. Defaults to https://api.ogpipe.dev */
|
|
49
|
+
baseUrl?: string;
|
|
50
|
+
/** Named templates */
|
|
51
|
+
templates: Record<string, OGPipeTemplate>;
|
|
52
|
+
/** Route-to-template mapping. Supports glob patterns. */
|
|
53
|
+
routes: Record<string, RouteConfig>;
|
|
54
|
+
/** On-demand rendering config (for dynamic routes) */
|
|
55
|
+
onDemand?: OnDemandConfig;
|
|
56
|
+
/** Output directory for generated images (default: public/og) */
|
|
57
|
+
outDir?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Helper to define a type-safe OGPipe config.
|
|
61
|
+
*
|
|
62
|
+
* Usage in ogpipe.config.ts:
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { defineConfig } from '@ogpipe/next'
|
|
65
|
+
*
|
|
66
|
+
* export default defineConfig({
|
|
67
|
+
* templates: { ... },
|
|
68
|
+
* routes: { ... },
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function defineConfig(config: OGPipeConfig): OGPipeConfig;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* On-demand OG Image handler for Next.js App Router.
|
|
76
|
+
*
|
|
77
|
+
* Use this for dynamic routes where build-time generation isn't possible.
|
|
78
|
+
* The handler calls the OGPipe API on first request, then CDN-caches the result.
|
|
79
|
+
*
|
|
80
|
+
* Usage in app/blog/[slug]/opengraph-image.ts:
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { OGImageHandler } from '@ogpipe/next'
|
|
83
|
+
*
|
|
84
|
+
* export default OGImageHandler({
|
|
85
|
+
* template: 'blog',
|
|
86
|
+
* revalidate: 86400,
|
|
87
|
+
* fallback: '/og-fallback.png',
|
|
88
|
+
* })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
interface OGImageHandlerOptions {
|
|
92
|
+
/** Template ID (must exist in ogpipe.config.ts templates) */
|
|
93
|
+
template?: string;
|
|
94
|
+
/** Inline HTML (alternative to template) */
|
|
95
|
+
html?: string;
|
|
96
|
+
/** Variables to inject into the template */
|
|
97
|
+
vars?: Record<string, string> | ((params: Record<string, string>) => Record<string, string>);
|
|
98
|
+
/** Cache duration in seconds (default: 86400 = 24h) */
|
|
99
|
+
revalidate?: number;
|
|
100
|
+
/** Fallback image path if API is unavailable */
|
|
101
|
+
fallback?: string;
|
|
102
|
+
/** Image width (default: 1200) */
|
|
103
|
+
width?: number;
|
|
104
|
+
/** Image height (default: 630) */
|
|
105
|
+
height?: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create an on-demand OG image route handler.
|
|
109
|
+
*
|
|
110
|
+
* Returns a Next.js-compatible route handler that:
|
|
111
|
+
* 1. Resolves the template HTML with variables
|
|
112
|
+
* 2. Calls OGPipe API to render
|
|
113
|
+
* 3. Returns the image with cache headers
|
|
114
|
+
* 4. Falls back to a static image if API is down
|
|
115
|
+
*/
|
|
116
|
+
declare function OGImageHandler(options: OGImageHandlerOptions): (request: Request, context: {
|
|
117
|
+
params?: Record<string, string | string[]>;
|
|
118
|
+
}) => Promise<Response>;
|
|
119
|
+
|
|
120
|
+
export { OGImageHandler, type OGImageHandlerOptions, type OGPipeConfig, type OGPipeTemplate, type OnDemandConfig, type RouteConfig, type RouteMetadata, defineConfig };
|