Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
name: Deploy to Cloudflare Pages

on:
push:
branches: [main]
pull_request:
types: [closed]
branches: [main]

jobs:
deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: production
permissions:
Expand All @@ -33,11 +31,6 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm generate:og
env:
NODE_VERSION: '22'

- name: Build
run: pnpm build
env:
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/generator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Generate Data and OG Images

on:
pull_request:
branches: [main]

jobs:
generate-and-commit:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Generate Data
run: pnpm generate:data
env:
NODE_VERSION: '22'

- name: Generate OG Images
run: pnpm generate:og
env:
NODE_VERSION: '22'

- name: Commit and Push changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: auto-generate data and og images"
64 changes: 0 additions & 64 deletions .github/workflows/update-data.yml

This file was deleted.

2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,3 @@ pnpm-debug.log*
.DS_Store

.idea/

public/og/*
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"prepare": "husky",
"check": "astro check",
"generate:og": "node scripts/generate-og.mjs",
"generate:data": "node scripts/genedate-data.mjs"
"generate:data": "node scripts/update-data.mjs --lang=tr && node scripts/update-data.mjs --lang=en"
},
"dependencies": {
"@astrojs/partytown": "^2.1.5",
Expand Down
Empty file added public/og/en/.gitkeep
Empty file.
Binary file added public/og/en/index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added public/og/tr/.gitkeep
Empty file.
Binary file added public/og/tr/index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 77 additions & 78 deletions scripts/generate-og.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,32 @@ const fontBold = await fetch(
'https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYMZg.ttf',
).then((res) => res.arrayBuffer())

const DEFAULT_COLORS = { bg: '#1a1a2e', text: '#e2e8f0' }
const repoRoot = path.join(process.cwd())
const logoDark = fs.readFileSync(path.join(repoRoot, 'src/assets/logo-dark.png')).toString('base64')

async function makeSvg(title, color, isArticle) {
const DEFAULT_COLORS = { bg: '#18181b', text: '#e5e7eb' }

const LANG_TEXTS = {
tr: {
explore: 'Makaleleri Keşfet',
category: 'KATEGORİ',
article: 'MAKALE',
tagline: 'Sinir ağları, transformerlar, pekiştirmeli öğrenme ve daha fazlası',
description: 'Makine öğrenimi kavramlarının net açıklamaları',
suffix: 'ML Kavramları Açıklandı',
},
en: {
explore: 'Explore Articles',
category: 'CATEGORY',
article: 'ARTICLE',
tagline: 'Neural networks, transformers, reinforcement learning & more',
description: 'Clear explanations of machine learning concepts',
suffix: 'ML Concepts Explained',
},
}

async function makeSvg(title, color, isArticle, lang = 'en', logo) {
const texts = LANG_TEXTS[lang] || LANG_TEXTS.en
const bg = color || DEFAULT_COLORS.bg
const svg = await satori(
{
Expand All @@ -26,7 +49,7 @@ async function makeSvg(title, color, isArticle) {
flexDirection: 'column',
justifyContent: 'space-between',
padding: '60px 80px',
background: `linear-gradient(135deg, ${bg} 0%, #0a0a1a 100%)`,
background: `linear-gradient(180deg, ${bg} 0%, #0d0d0d 100%)`,
fontFamily: 'Inter',
},
children: [
Expand All @@ -41,36 +64,12 @@ async function makeSvg(title, color, isArticle) {
style: { display: 'flex', alignItems: 'center', gap: '16px' },
children: [
{
type: 'div',
type: 'img',
props: {
style: {
width: '48px',
height: '48px',
borderRadius: '12px',
background: '#22d3ee',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#0a0a1a',
fontSize: '24px',
fontWeight: '700',
fontFamily: 'Inter',
},
children: '4',
src: `data:image/png;base64,${logo}`,
height: 48
},
},
{
type: 'span',
props: {
style: {
fontSize: '28px',
fontWeight: '700',
color: '#f8fafc',
fontFamily: 'Inter',
},
children: '4byte.dev',
},
},
}
],
},
},
Expand All @@ -79,11 +78,11 @@ async function makeSvg(title, color, isArticle) {
props: {
style: {
fontSize: '16px',
color: '#94a3b8',
color: '#8b949e',
fontWeight: '500',
fontFamily: 'Inter',
},
children: isArticle ? 'Machine Learning & AI' : 'Explore Articles',
children: isArticle ? 'Machine Learning & AI' : texts.explore,
},
},
],
Expand All @@ -99,13 +98,13 @@ async function makeSvg(title, color, isArticle) {
props: {
style: {
fontSize: '14px',
color: '#e94560',
color: '#388bfd',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '2px',
fontFamily: 'Inter',
},
children: isArticle ? 'ARTICLE' : 'LEARN MACHINE LEARNING',
children: isArticle ? texts.article : texts.category,
},
},
{
Expand All @@ -114,7 +113,7 @@ async function makeSvg(title, color, isArticle) {
style: {
fontSize: title.length > 60 ? '42px' : '56px',
fontWeight: '700',
color: '#f8fafc',
color: '#e5e7eb',
lineHeight: 1.2,
fontFamily: 'Inter',
maxWidth: '900px',
Expand All @@ -127,14 +126,12 @@ async function makeSvg(title, color, isArticle) {
props: {
style: {
fontSize: '20px',
color: '#94a3b8',
color: '#8b949e',
lineHeight: 1.6,
fontFamily: 'Inter',
maxWidth: '800px',
},
children: isArticle
? 'Clear explanations of machine learning concepts'
: 'Neural networks, transformers, reinforcement learning & more',
children: isArticle ? texts.description : texts.tagline,
},
},
],
Expand All @@ -146,29 +143,17 @@ async function makeSvg(title, color, isArticle) {
style: { display: 'flex', alignItems: 'center', gap: '12px' },
children: [
{
type: 'div',
type: 'img',
props: {
style: {
width: '40px',
height: '40px',
borderRadius: '50%',
background: '#22d3ee',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px',
fontWeight: '700',
color: '#0a0a1a',
fontFamily: 'Inter',
},
children: '4',
src: `data:image/png;base64,${logo}`,
height: 48
},
},
{
type: 'span',
props: {
style: { fontSize: '18px', color: '#94a3b8', fontFamily: 'Inter' },
children: '4byte.dev — ML Concepts Explained',
style: { fontSize: '18px', color: '#8b949e', fontFamily: 'Inter' },
children: texts.suffix,
},
},
],
Expand All @@ -189,32 +174,46 @@ async function makeSvg(title, color, isArticle) {
return svg
}

const outDir = path.join(process.cwd(), 'public', 'og')
const outDir = path.join(repoRoot, 'public', 'og')
fs.mkdirSync(outDir, { recursive: true })

const dataDir = path.join(process.cwd(), 'src', 'data')
const categories = JSON.parse(fs.readFileSync(path.join(dataDir, 'categories.json'), 'utf8'))
const articles = JSON.parse(fs.readFileSync(path.join(dataDir, 'articles.json'), 'utf8'))
const SUPPORTED_LANGS = ['tr', 'en']

const categoryColorMap = {}
for (const cat of categories) {
categoryColorMap[cat.name] = cat.color || null
}
for (const lang of SUPPORTED_LANGS) {
const langOutDir = path.join(outDir, lang)
fs.mkdirSync(langOutDir, { recursive: true })

const dataDir = path.join(repoRoot, 'src', 'data', lang)
let categories = []
let articles = []

try {
categories = JSON.parse(fs.readFileSync(path.join(dataDir, 'categories.json'), 'utf8'))
articles = JSON.parse(fs.readFileSync(path.join(dataDir, 'articles.json'), 'utf8'))
} catch {
console.log(`No data found for language: ${lang}`)
}

const categoryColorMap = {}
for (const cat of categories) {
categoryColorMap[cat.name] = cat.color || null
}

const svgIndex = await makeSvg('4byte.dev', null, false)
const resvgIndex = new Resvg(svgIndex, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(outDir, 'index.png'), Buffer.from(resvgIndex.render().asPng()))
console.log('Generated public/og/index.png')
const svgIndex = await makeSvg('4byte.dev', null, false, lang, logoDark)
const resvgIndex = new Resvg(svgIndex, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(langOutDir, 'index.png'), Buffer.from(resvgIndex.render().asPng()))
console.log(`Generated public/og/${lang}/index.png`)

if (articles.length === 0) {
console.log('No articles found – skipping article OG images.')
} else {
for (const { slug, title, category } of articles) {
const color = categoryColorMap[category] || null
const svgArticle = await makeSvg(title, color, true)
const resvgArticle = new Resvg(svgArticle, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(outDir, `${slug}.png`), Buffer.from(resvgArticle.render().asPng()))
console.log(`Generated public/og/${slug}.png`)
if (articles.length === 0) {
console.log(`No articles for ${lang} – skipping article OG images.`)
} else {
for (const { slug, title, category } of articles) {
const color = categoryColorMap[category] || null
const svgArticle = await makeSvg(title, color, true, lang, logoDark)
const resvgArticle = new Resvg(svgArticle, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(langOutDir, `${slug}.png`), Buffer.from(resvgArticle.render().asPng()))
console.log(`Generated public/og/${lang}/${slug}.png`)
}
}
}

Expand Down
Loading
Loading