From 93972aef35c5fb52616e412c63d7fd57607ccd2d Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:00:46 +0200 Subject: [PATCH] fix: persist OG image cache outside act runner workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cache was written to .cache/og/ relative to the workspace, which is under /usr/local/git/.cache/act//hostexecutor/ — a new path per run, so every build regenerated all images from scratch. OG_CACHE_DIR env var now controls the cache path (resolved to an absolute path). CI sets it to /usr/local/git/.cache/og, which survives between runs. Locally it still defaults to .cache/og inside the project dir. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/deploy.yml | 1 + eleventy.config.js | 32 +++++++++++++++++--------------- lib/og-cli.js | 10 +++++----- lib/og.js | 3 +-- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 61f0ae4..5bc3cd0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -125,6 +125,7 @@ jobs: GITEA_URL: https://gitea.giersig.eu GITEA_INTERNAL_URL: http://127.0.0.1:3000 GITEA_ORG: giersig.eu + OG_CACHE_DIR: /usr/local/git/.cache/og - name: Deploy via rsync run: | diff --git a/eleventy.config.js b/eleventy.config.js index 8cb83e6..24397f2 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -26,6 +26,12 @@ const postGraph = esmRequire("@rknightuk/eleventy-plugin-post-graph"); const __dirname = dirname(fileURLToPath(import.meta.url)); const siteUrl = process.env.SITE_URL || "https://example.com"; +// OG image cache — persistent across CI runs when OG_CACHE_DIR env var is set. +// In CI, point this outside the act runner workspace (e.g. /usr/local/git/.cache/og). +const OG_CACHE_DIR = process.env.OG_CACHE_DIR + ? resolve(process.env.OG_CACHE_DIR) + : resolve(__dirname, ".cache", "og"); + // Slugify each path segment, preserving "/" separators for nested tags (e.g. "tech/programming") const nestedSlugify = (str) => { if (!str) return ""; @@ -49,7 +55,7 @@ export default function (eleventyConfig) { eleventyConfig.setUseGitIgnore(false); // Passthrough copy for OG images - eleventyConfig.addPassthroughCopy({ ".cache/og": "images/og" }); + eleventyConfig.addPassthroughCopy({ [OG_CACHE_DIR]: "images/og" }); // Ignore output directory (prevents re-processing generated files via symlink) eleventyConfig.ignores.add("_site"); @@ -78,8 +84,8 @@ export default function (eleventyConfig) { eleventyConfig.watchIgnores.add("/app/data/site/**"); eleventyConfig.watchIgnores.add("pagefind"); eleventyConfig.watchIgnores.add("pagefind/**"); - eleventyConfig.watchIgnores.add(".cache/og"); - eleventyConfig.watchIgnores.add(".cache/og/**"); + eleventyConfig.watchIgnores.add(OG_CACHE_DIR); + eleventyConfig.watchIgnores.add(OG_CACHE_DIR + "/**"); eleventyConfig.watchIgnores.add(".cache/unfurl"); eleventyConfig.watchIgnores.add(".cache/unfurl/**"); @@ -388,9 +394,8 @@ export default function (eleventyConfig) { eleventyConfig.on("eleventy.before", () => { _ogFileSet = null; }); function hasOgImage(ogSlug) { if (!_ogFileSet) { - const ogDir = resolve(__dirname, ".cache", "og"); try { - _ogFileSet = new Set(readdirSync(ogDir)); + _ogFileSet = new Set(readdirSync(OG_CACHE_DIR)); } catch { _ogFileSet = new Set(); } @@ -658,7 +663,7 @@ export default function (eleventyConfig) { eleventyConfig.addPassthroughCopy("favicon.ico"); eleventyConfig.addPassthroughCopy("robots.txt"); eleventyConfig.addPassthroughCopy("interactive"); - eleventyConfig.addPassthroughCopy({ ".cache/og": "og" }); + eleventyConfig.addPassthroughCopy({ [OG_CACHE_DIR]: "og" }); // Funkwhale images are copied in eleventy.after (after data files download them) // Copy vendor web components from node_modules @@ -929,8 +934,7 @@ export default function (eleventyConfig) { // Check if a generated OG image exists for this slug eleventyConfig.addFilter("hasOgImage", (slug) => { if (!slug) return false; - const ogPath = resolve(__dirname, ".cache", "og", `${slug}.png`); - return existsSync(ogPath); + return existsSync(resolve(OG_CACHE_DIR, `${slug}.png`)); }); // Inline file contents (for critical CSS inlining) @@ -1589,7 +1593,6 @@ export default function (eleventyConfig) { eleventyConfig.on("eleventy.before", () => { console.time("[og] image generation"); const contentDir = resolve(__dirname, "content"); - const cacheDir = resolve(__dirname, ".cache"); const siteName = process.env.SITE_NAME || "My IndieWeb Blog"; const BATCH_SIZE = 100; try { @@ -1601,7 +1604,7 @@ export default function (eleventyConfig) { "--expose-gc", resolve(__dirname, "lib", "og-cli.js"), contentDir, - cacheDir, + OG_CACHE_DIR, siteName, String(BATCH_SIZE), ], { @@ -1620,16 +1623,15 @@ export default function (eleventyConfig) { } // Sync new OG images to output directory. - // During incremental builds, .cache/og is in watchIgnores so Eleventy's + // During incremental builds, OG_CACHE_DIR is in watchIgnores so Eleventy's // passthrough copy won't pick up newly generated images. Copy them manually. - const ogCacheDir = resolve(cacheDir, "og"); const ogOutputDir = resolve(__dirname, "_site", "og"); - if (existsSync(ogCacheDir) && existsSync(resolve(__dirname, "_site"))) { + if (existsSync(OG_CACHE_DIR) && existsSync(resolve(__dirname, "_site"))) { mkdirSync(ogOutputDir, { recursive: true }); let synced = 0; - for (const file of readdirSync(ogCacheDir)) { + for (const file of readdirSync(OG_CACHE_DIR)) { if (file.endsWith(".png") && !existsSync(resolve(ogOutputDir, file))) { - copyFileSync(resolve(ogCacheDir, file), resolve(ogOutputDir, file)); + copyFileSync(resolve(OG_CACHE_DIR, file), resolve(ogOutputDir, file)); synced++; } } diff --git a/lib/og-cli.js b/lib/og-cli.js index e80c961..47d4494 100644 --- a/lib/og-cli.js +++ b/lib/og-cli.js @@ -4,7 +4,7 @@ * CLI entry point for OG image generation. * Runs as a separate process to isolate memory from Eleventy. * - * Usage: node lib/og-cli.js [batchSize] + * Usage: node lib/og-cli.js [batchSize] * * batchSize: Max images to generate per invocation (0 = unlimited). * When set, exits after generating that many images so the caller @@ -14,15 +14,15 @@ import { generateOgImages } from "./og.js"; -const [contentDir, cacheDir, siteName, batchSizeStr] = process.argv.slice(2); +const [contentDir, ogDir, siteName, batchSizeStr] = process.argv.slice(2); -if (!contentDir || !cacheDir || !siteName) { - console.error("[og] Usage: node og-cli.js [batchSize]"); +if (!contentDir || !ogDir || !siteName) { + console.error("[og] Usage: node og-cli.js [batchSize]"); process.exit(1); } const batchSize = parseInt(batchSizeStr, 10) || 0; -const result = await generateOgImages(contentDir, cacheDir, siteName, batchSize); +const result = await generateOgImages(contentDir, ogDir, siteName, batchSize); // Exit code 2 signals "batch complete, more images remain" if (result?.hasMore) { diff --git a/lib/og.js b/lib/og.js index 97854b8..4fda361 100644 --- a/lib/og.js +++ b/lib/og.js @@ -426,8 +426,7 @@ function scanContentFiles(contentDir) { * @param {number} batchSize - Max images to generate (0 = unlimited) * @returns {{ hasMore: boolean }} Whether more images need generation */ -export async function generateOgImages(contentDir, cacheDir, siteName, batchSize = 0) { - const ogDir = join(cacheDir, "og"); +export async function generateOgImages(contentDir, ogDir, siteName, batchSize = 0) { mkdirSync(ogDir, { recursive: true }); const manifestPath = join(ogDir, "manifest.json");