From a6a2f56fbf716d359cd58035c7947f87a142d615 Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:36:04 +1000 Subject: [PATCH] fix: package og runtime assets during vite build (#2895) --- scripts/copy-og-assets.ts | 192 ++++++++++++++------------------------ server/og/ogAssets.ts | 1 + vite.config.ts | 12 +++ 3 files changed, 81 insertions(+), 124 deletions(-) diff --git a/scripts/copy-og-assets.ts b/scripts/copy-og-assets.ts index 86a8c5a5..54b4b1cb 100644 --- a/scripts/copy-og-assets.ts +++ b/scripts/copy-og-assets.ts @@ -1,5 +1,7 @@ import { copyFile, mkdir, stat } from "node:fs/promises"; import path from "node:path"; +import { pathToFileURL } from "node:url"; +import { OG_FONT_PATHS } from "../server/og/ogAssets"; async function resolveExistingPath(candidates: string[]) { for (const candidate of candidates) { @@ -21,131 +23,73 @@ function nodeModuleCandidates(relativePath: string) { ]; } -const resvgWasmSource = await resolveExistingPath( - nodeModuleCandidates("@resvg/resvg-wasm/index_bg.wasm"), -); -const bricolage800Source = await resolveExistingPath( - nodeModuleCandidates( - "@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2", - ), -); -const bricolage500Source = await resolveExistingPath( - nodeModuleCandidates( - "@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-500-normal.woff2", - ), -); -const ibmPlex500Source = await resolveExistingPath( - nodeModuleCandidates("@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2"), -); -const notoSansSc800Source = await resolveExistingPath( - nodeModuleCandidates( - "@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-800-normal.woff2", - ), -); -const notoSansSc500Source = await resolveExistingPath( - nodeModuleCandidates( - "@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-500-normal.woff2", - ), -); +export async function copyOgAssets() { + const resvgWasmSource = await resolveExistingPath( + nodeModuleCandidates("@resvg/resvg-wasm/index_bg.wasm"), + ); + const fontCopies = await Promise.all( + OG_FONT_PATHS.map(async (runtimePath) => { + const modulePath = runtimePath.replace(/^node_modules\//, ""); + const source = await resolveExistingPath(nodeModuleCandidates(modulePath)); + return { + source, + targets: [ + path.resolve(".output/server", runtimePath), + path.resolve(".vercel/output/functions/__server.func", runtimePath), + ], + }; + }), + ); -const copies = [ - { - source: path.resolve("public/clawd-logo.png"), - targets: [ - path.resolve(".output/server/clawd-logo.png"), - path.resolve(".output/server/public/clawd-logo.png"), - path.resolve(".vercel/output/functions/__server.func/clawd-logo.png"), - path.resolve(".vercel/output/functions/__server.func/public/clawd-logo.png"), - ], - }, - { - source: path.resolve("public/og-clawhub-watermark.png"), - targets: [ - path.resolve(".output/server/og-clawhub-watermark.png"), - path.resolve(".output/server/public/og-clawhub-watermark.png"), - path.resolve(".vercel/output/functions/__server.func/og-clawhub-watermark.png"), - path.resolve(".vercel/output/functions/__server.func/public/og-clawhub-watermark.png"), - ], - }, - { - source: path.resolve("public/clawd-mark.png"), - targets: [ - path.resolve(".output/server/clawd-mark.png"), - path.resolve(".output/server/public/clawd-mark.png"), - path.resolve(".vercel/output/functions/__server.func/clawd-mark.png"), - path.resolve(".vercel/output/functions/__server.func/public/clawd-mark.png"), - ], - }, - { - source: resvgWasmSource, - targets: [ - path.resolve(".output/server/node_modules/@resvg/resvg-wasm/index_bg.wasm"), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@resvg/resvg-wasm/index_bg.wasm", - ), - ], - }, - { - source: bricolage800Source, - targets: [ - path.resolve( - ".output/server/node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2", - ), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2", - ), - ], - }, - { - source: bricolage500Source, - targets: [ - path.resolve( - ".output/server/node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-500-normal.woff2", - ), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-500-normal.woff2", - ), - ], - }, - { - source: ibmPlex500Source, - targets: [ - path.resolve( - ".output/server/node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2", - ), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2", - ), - ], - }, - { - source: notoSansSc800Source, - targets: [ - path.resolve( - ".output/server/node_modules/@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-800-normal.woff2", - ), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-800-normal.woff2", - ), - ], - }, - { - source: notoSansSc500Source, - targets: [ - path.resolve( - ".output/server/node_modules/@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-500-normal.woff2", - ), - path.resolve( - ".vercel/output/functions/__server.func/node_modules/@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-500-normal.woff2", - ), - ], - }, -]; + const copies = [ + { + source: path.resolve("public/clawd-logo.png"), + targets: [ + path.resolve(".output/server/clawd-logo.png"), + path.resolve(".output/server/public/clawd-logo.png"), + path.resolve(".vercel/output/functions/__server.func/clawd-logo.png"), + path.resolve(".vercel/output/functions/__server.func/public/clawd-logo.png"), + ], + }, + { + source: path.resolve("public/og-clawhub-watermark.png"), + targets: [ + path.resolve(".output/server/og-clawhub-watermark.png"), + path.resolve(".output/server/public/og-clawhub-watermark.png"), + path.resolve(".vercel/output/functions/__server.func/og-clawhub-watermark.png"), + path.resolve(".vercel/output/functions/__server.func/public/og-clawhub-watermark.png"), + ], + }, + { + source: path.resolve("public/clawd-mark.png"), + targets: [ + path.resolve(".output/server/clawd-mark.png"), + path.resolve(".output/server/public/clawd-mark.png"), + path.resolve(".vercel/output/functions/__server.func/clawd-mark.png"), + path.resolve(".vercel/output/functions/__server.func/public/clawd-mark.png"), + ], + }, + { + source: resvgWasmSource, + targets: [ + path.resolve(".output/server/node_modules/@resvg/resvg-wasm/index_bg.wasm"), + path.resolve( + ".vercel/output/functions/__server.func/node_modules/@resvg/resvg-wasm/index_bg.wasm", + ), + ], + }, + ...fontCopies, + ]; -for (const { source, targets } of copies) { - for (const target of targets) { - const parent = path.dirname(target); - await mkdir(parent, { recursive: true }); - await copyFile(source, target); + for (const { source, targets } of copies) { + for (const target of targets) { + const parent = path.dirname(target); + await mkdir(parent, { recursive: true }); + await copyFile(source, target); + } } } + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + await copyOgAssets(); +} diff --git a/server/og/ogAssets.ts b/server/og/ogAssets.ts index f67b9a9d..d95c35ab 100644 --- a/server/og/ogAssets.ts +++ b/server/og/ogAssets.ts @@ -33,6 +33,7 @@ const PUBLISHER_FONT_PATHS = [ "node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2", ...SHARED_FONT_PATHS, ]; +export const OG_FONT_PATHS = Array.from(new Set([...DEFAULT_FONT_PATHS, ...PUBLISHER_FONT_PATHS])); function getServerRootUrl() { const nitroMain = (globalThis as unknown as GlobalNitroMain).__nitro_main__; diff --git a/vite.config.ts b/vite.config.ts index a978504a..e92eb23e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import { nitro } from "nitro/vite"; import { defineConfig, type Plugin } from "vite"; +import { copyOgAssets } from "./scripts/copy-og-assets"; const require = createRequire(import.meta.url); @@ -167,6 +168,16 @@ function patchArkSafariInOperator(): Plugin { }; } +function copyOgAssetsPlugin(): Plugin { + return { + name: "copy-og-assets", + apply: "build", + async closeBundle() { + await copyOgAssets(); + }, + }; +} + const config = defineConfig({ resolve: { dedupe: ["convex", "@convex-dev/auth", "react", "react-dom"], @@ -197,6 +208,7 @@ const config = defineConfig({ tailwindcss(), tanstackStart(), viteReact(), + copyOgAssetsPlugin(), ], build: { // Keep the shipped client bundle parseable in Safari/WebKit.