-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add open graph images + multi langs + image snapshots tests (#431)
* temp * fix * remove unused * move tests * add tests * fixes * fixes * fixes * fixes * fixes * like this * prettier * lint * ci: job to deploy opengraph service * style: run prettier * add a way to deploy manually * run temp * Revert "run temp" This reverts commit aa13fa5. * increase memory * use the worker * only deploy if service has changed --------- Co-authored-by: Saihajpreet Singh <[email protected]>
- Loading branch information
Showing
32 changed files
with
3,135 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: OpenGraph Service | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
paths: | ||
- 'packages/og-image/**' | ||
|
||
workflow_dispatch: | ||
inputs: | ||
commit: | ||
required: false | ||
description: 'Commit ID' | ||
|
||
jobs: | ||
deploy: | ||
name: Deploy to Cloudflare Workers | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: checkout | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
ref: ${{ env.COMMIT }} | ||
|
||
- uses: the-guild-org/shared-config/setup@main | ||
name: setup env | ||
with: | ||
nodeVersion: 18 | ||
packageManager: pnpm | ||
|
||
- name: Deploy | ||
working-directory: ./packages/og-image | ||
run: pnpm run deploy | ||
env: | ||
CLOUDFLARE_API_TOKEN: ${{ secrets.GUILD_CLOUDFLARE_API_TOKEN }} | ||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.GUILD_CF_ACCOUNT_ID }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,5 @@ build/ | |
.eslintcache | ||
dist/ | ||
.turbo/ | ||
|
||
.git | ||
.gitignore | ||
packages/og-image/vender/*.wasm | ||
.wrangler/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+501 KB
...e_snapshots__/handler-test-ts-tests-handler-test-ts-handler-no-title-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+514 KB
...andler-test-ts-handler-should-align-title-and-have-container-padding-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+492 KB
...tests-handler-test-ts-handler-should-align-title-without-whitespaces-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+509 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ar-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+507 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-hi-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+511 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ja-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+497 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ko-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+513 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ru-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+511 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ua-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+516 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-ur-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+504 KB
...r-test-ts-tests-handler-test-ts-handler-show-individual-languages-zh-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { handler } from '../src/handler' | ||
|
||
vi.mock('../vender/index_bg.wasm', async () => { | ||
const fs = await import('node:fs/promises') | ||
const wasm = await fs.readFile(require.resolve('@resvg/resvg-wasm/index_bg.wasm')) | ||
return { | ||
default: wasm, | ||
} | ||
}) | ||
|
||
describe('handler()', () => { | ||
it('no title', async () => { | ||
const response = await handler({ | ||
url: 'http://localhost:3000', | ||
} as Request) | ||
const result = Buffer.from(await response.arrayBuffer()) | ||
expect(result).toMatchImageSnapshot() | ||
}) | ||
it('should align title and have container padding', async () => { | ||
const response = await handler({ | ||
url: 'http://localhost:3000?title=Hello this is a test of really really really really really really long title', | ||
} as Request) | ||
const result = Buffer.from(await response.arrayBuffer()) | ||
expect(result).toMatchImageSnapshot() | ||
}) | ||
it('should align title without whitespaces', async () => { | ||
const response = await handler({ | ||
url: 'http://localhost:3000?title=Home', | ||
} as Request) | ||
const result = Buffer.from(await response.arrayBuffer()) | ||
expect(result).toMatchImageSnapshot() | ||
}) | ||
|
||
describe('show individual languages', () => { | ||
for (const [lang, title] of Object.entries({ | ||
ar: 'الأسئلة الشائعة حول الفرعيةرسم بياني استوديو', | ||
hi: 'फोर्क्स का उपयोग करके त्वरित और आसान सबग्राफ डिबगिंग', | ||
ja: 'フォークを用いた迅速かつ容易なサブグラフのデバッグ', | ||
ko: '다중서명 지갑 사용하기', | ||
ru: 'Замените контракт и сохраните его историю с помощью Grafting', | ||
ua: 'Мережа The Graph в порівнянні з Самостійним хостингом', | ||
ur: 'ایک معاہدے کو تبدیل کریں اور اس کی تاریخ کو گرافٹنگ کے ساتھ رکھیں', | ||
zh: '使用分叉快速轻松地调试子图', | ||
})) { | ||
it(lang, async () => { | ||
const response = await handler({ | ||
url: `http://localhost:3000?title=${title}`, | ||
} as Request) | ||
const result = Buffer.from(await response.arrayBuffer()) | ||
expect(result).toMatchImageSnapshot() | ||
}) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
declare module 'vitest' { | ||
import type { Assertion, AsymmetricMatchersContaining } from 'vitest' | ||
|
||
interface CustomMatchers<R = unknown> { | ||
toMatchImageSnapshot(): R | ||
} | ||
|
||
interface Assertion<T = any> extends CustomMatchers<T> {} | ||
|
||
interface AsymmetricMatchersContaining extends CustomMatchers {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "@theguild/og-image", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"private": true, | ||
"scripts": { | ||
"deploy": "wrangler publish", | ||
"postinstall": "tsx scripts/copy-wasm.ts", | ||
"start": "wrangler dev", | ||
"test": "vitest run" | ||
}, | ||
"dependencies": { | ||
"@resvg/resvg-wasm": "2.4.1", | ||
"react": "18.2.0", | ||
"satori": "0.10.1", | ||
"yoga-wasm-web": "0.3.3" | ||
}, | ||
"devDependencies": { | ||
"@cloudflare/workers-types": "^4.20230518.0", | ||
"@types/react": "^18.2.14", | ||
"jest-image-snapshot": "^6.1.0", | ||
"tsx": "^3.12.7", | ||
"typescript": "^5.1.5", | ||
"vitest": "^0.32.2", | ||
"wrangler": "^3.1.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { readFile, writeFile } from 'node:fs/promises' | ||
import { createRequire } from 'node:module' | ||
import { join } from 'node:path' | ||
|
||
const require = createRequire(import.meta.url) | ||
const __dirname = new URL('.', import.meta.url).pathname | ||
|
||
await writeFile( | ||
join(__dirname, '../vender/index_bg.wasm'), | ||
await readFile(require.resolve('@resvg/resvg-wasm/index_bg.wasm')), | ||
) | ||
|
||
// eslint-disable-next-line no-console | ||
console.log('✅ @resvg/resvg-wasm/index_bg.wasm copied!') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { toMatchImageSnapshot } from 'jest-image-snapshot' | ||
|
||
expect.extend({ toMatchImageSnapshot }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* eslint react/no-unknown-property: ['error', { ignore: ['tw'] }] */ | ||
import { toImage, toSVG } from './utils' | ||
|
||
export async function handler(request: Request): Promise<Response> { | ||
try { | ||
const { searchParams } = new URL(request.url) | ||
// ?title=<title> | ||
const title = searchParams.get('title')?.slice(0, 100) | ||
|
||
const rawSvg = await toSVG( | ||
<div | ||
tw="flex h-full flex-col w-full items-center justify-center text-white text-center p-10 pt-20" | ||
style={{ | ||
backgroundImage: 'url(https://storage.googleapis.com/graph-website/seo/graph-website.jpg)', | ||
backgroundPosition: title ? '0 -70%' : '0 -55%', | ||
}} | ||
> | ||
{title && ( | ||
// @ts-expect-error This isn't a valid CSS property supported by browsers yet. | ||
<span tw="text-5xl" style={{ textWrap: title.includes(' ') ? 'balance' : '' }}> | ||
{title} | ||
</span> | ||
)} | ||
</div>, | ||
) | ||
|
||
const buffer = toImage(rawSvg) | ||
|
||
return new Response(buffer, { | ||
headers: { 'Content-Type': 'image/png' }, | ||
}) | ||
} catch (e) { | ||
// eslint-disable-next-line no-console -- to debug | ||
console.error(e) | ||
return new Response(`Failed to generate the image.\n\nError: ${(e as Error).message}`, { | ||
status: 500, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* eslint-disable import/no-default-export */ | ||
/* eslint react/no-unknown-property: ['error', { ignore: ['tw'] }] */ | ||
import { handler } from './handler' | ||
|
||
const hour = 3600 | ||
const day = hour * 24 | ||
const year = 365 * day | ||
|
||
const maxAgeForCDN = year | ||
const maxAgeForBrowser = hour / 2 | ||
|
||
export default { | ||
async fetch(request: Request, _env: unknown, ctx: ExecutionContext) { | ||
const cacheUrl = new URL(request.url) | ||
|
||
// In case you want to purge the cache, please bump the version number below: | ||
cacheUrl.searchParams.set('version', 'v10') | ||
|
||
// Construct the cache key from the cache URL | ||
const cacheKey = new Request(cacheUrl.toString(), request) | ||
const cache = caches.default | ||
|
||
let response = await cache.match(cacheKey) | ||
|
||
if (!response) { | ||
// If not in cache, get it from origin | ||
response = await handler(request) | ||
|
||
if (process.env.NODE_ENV !== 'test' && ![404, 500].includes(response.status)) { | ||
// Any changes made to the response here will be reflected in the cached value | ||
response.headers.append('Cache-Control', 'public') | ||
response.headers.append('Cache-Control', `s-maxage=${maxAgeForCDN}`) | ||
response.headers.append('Cache-Control', `max-age=${maxAgeForBrowser}`) | ||
} | ||
|
||
// Store the fetched response as cacheKey | ||
// Use `waitUntil`, so you can return the response without blocking on | ||
// writing to cache | ||
ctx.waitUntil(cache.put(cacheKey, response.clone())) | ||
} | ||
return response | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
declare module '*.wasm' { | ||
export default ArrayBuffer | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { initWasm, Resvg } from '@resvg/resvg-wasm' | ||
import { ReactNode } from 'react' | ||
import satori, { FontWeight } from 'satori' | ||
|
||
import resvgWasm from '../vender/index_bg.wasm' | ||
|
||
export function toImage(svg: string): Uint8Array { | ||
const resvg = new Resvg(svg) | ||
const pngData = resvg.render() | ||
return pngData.asPng() | ||
} | ||
|
||
type Font = { data: ArrayBuffer; weight: FontWeight; name: string } | ||
|
||
export async function loadGoogleFont({ family, weight }: { family: string; weight?: number }): Promise<Font> { | ||
const params: Record<string, string> = { | ||
family: `${family}${weight ? `:wght@${weight}` : ''}`, | ||
} | ||
|
||
const url = `https://fonts.googleapis.com/css2?${new URLSearchParams(params)}` | ||
|
||
const response = await fetch(url, { | ||
headers: { | ||
// construct user agent to get TTF font | ||
'User-Agent': | ||
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1', | ||
}, | ||
}) | ||
const css = await response.text() | ||
// Get the font URL from the CSS text | ||
|
||
const fontUrl = /src: url\((.+)\) format\('(opentype|truetype)'\)/.exec(css)?.[1] | ||
if (!fontUrl) { | ||
throw new Error('Could not find font URL') | ||
} | ||
|
||
const res = await fetch(fontUrl) | ||
return { | ||
data: await res.arrayBuffer(), | ||
weight: Number(/weight: (.+);/.exec(css)?.[1]) as FontWeight, | ||
name: family, | ||
} | ||
} | ||
|
||
let fonts: Font[] | ||
let init = false | ||
|
||
export async function toSVG(node: ReactNode): Promise<string> { | ||
if (!init) { | ||
fonts = await Promise.all([ | ||
loadGoogleFont({ family: 'Noto Sans', weight: 400 }), | ||
loadGoogleFont({ family: 'Noto Sans Arabic', weight: 400 }), | ||
// await loadGoogleFont({ family: 'Noto Sans JP', weight: 400 }), | ||
loadGoogleFont({ family: 'Noto Sans KR', weight: 400 }), // ko | ||
loadGoogleFont({ family: 'Noto Sans SC', weight: 400 }), // zh | ||
]) | ||
await initWasm(resvgWasm) | ||
init = true | ||
} | ||
return satori(node, { | ||
width: 1200, | ||
height: 600, | ||
fonts, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es2021", | ||
"lib": ["es2021"], | ||
"jsx": "react-jsx", | ||
"module": "es2022", | ||
"moduleResolution": "node", | ||
"types": ["vitest/globals", "@cloudflare/workers-types"], | ||
"resolveJsonModule": true, | ||
"allowJs": true, | ||
"checkJs": false, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"allowSyntheticDefaultImports": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"skipLibCheck": true | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { defaultExclude, defineConfig } from 'vitest/config' | ||
|
||
export default defineConfig({ | ||
test: { | ||
globals: true, | ||
setupFiles: 'setup-file.ts', | ||
exclude: [...defaultExclude, '**/*.d.ts'], | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name = "graph-docs-opengraph-image" | ||
main = "src/index.ts" | ||
compatibility_date = "2022-10-07" | ||
compatibility_flags = ["streams_enable_constructors"] | ||
rules = [ | ||
{ type = "Data", globs = ["**/*.ttf", "**/*.otf"], fallthrough = true }, | ||
] |
Oops, something went wrong.