Skip to content

Commit 68eb109

Browse files
authored
Merge pull request open-neuromorphic#248 from neural-loop/cache-images
Cache images
2 parents 53807ba + 01bce83 commit 68eb109

File tree

6 files changed

+98
-32
lines changed

6 files changed

+98
-32
lines changed

.github/workflows/main.yml

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Deploy Hugo site to Pages
22

33
on:
44
push:
5-
branches: ["main", "design-adjustments"]
5+
branches: ["main", "cache-images"]
66

77
permissions:
88
contents: read
@@ -17,23 +17,19 @@ env:
1717

1818
jobs:
1919
build:
20-
# Switched to a standard x86_64 runner for better package compatibility
2120
runs-on: ubuntu-24.04
2221
steps:
2322
- name: Install Hugo
2423
run: |
25-
# Updated to use amd64 for the new runner
2624
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
2725
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
2826
2927
- name: Install Go
3028
run: |
31-
# Updated to use amd64 for the new runner
3229
wget -O ${{ runner.temp }}/go.tar.gz https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz \
3330
&& sudo tar -C /usr/local -xzf ${{ runner.temp }}/go.tar.gz \
3431
&& sudo ln -s /usr/local/go/bin/go /usr/local/bin/go
3532
36-
3733
- name: Checkout
3834
uses: actions/checkout@v4.2.2
3935
with:
@@ -44,15 +40,29 @@ jobs:
4440
id: pages
4541
uses: actions/configure-pages@v5
4642

47-
# Best practice: Use setup-node with caching
4843
- name: Setup Node.js
4944
uses: actions/setup-node@v4
5045
with:
5146
node-version: ${{ env.NODE_VERSION }}
5247
cache: 'npm'
5348

5449
- name: Install npm dependencies
55-
run: npm ci # Use npm ci for faster, more reliable CI builds
50+
run: npm ci
51+
52+
- name: Cache and Restore OG Images
53+
uses: actions/cache@v4
54+
id: og-cache
55+
with:
56+
path: |
57+
content/**/*-og.jpg
58+
static/images/og-image.jpg
59+
tmp/og-cache-manifest.json
60+
key: ${{ runner.os }}-og-images-${{ hashFiles('**/content/**/index.md', '**/content/**/_index.md', 'assets/og-template/template.html', 'assets/images/ONM-logo.png') }}
61+
restore-keys: |
62+
${{ runner.os }}-og-images-
63+
64+
- name: Generate OG Images
65+
run: npm run og-images
5666

5767
- name: Determine Base URL
5868
id: base_url
@@ -63,7 +73,6 @@ jobs:
6373
BASE_URL="https://open-neuromorphic.github.io/refactor-preview/" # Example preview URL
6474
else
6575
REPO_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2)
66-
# For forks or other repositories, adjust as needed
6776
BASE_URL="https://${{ github.repository_owner }}.github.io/${REPO_NAME}/"
6877
fi
6978
echo "BASE_URL=$BASE_URL" >> $GITHUB_ENV
@@ -75,12 +84,12 @@ jobs:
7584
sed -i "s|baseURL = .*|baseURL = \"$BASE_URL\"|" hugo.toml
7685
echo "hugo.toml after modification:"
7786
cat hugo.toml
78-
79-
- name: Build site
87+
88+
- name: Build Hugo Site
8089
run: |
81-
echo "Starting site build..."
82-
npm run build
83-
echo "Site build completed."
90+
echo "Starting Hugo site build..."
91+
npm run hugo-build
92+
echo "Hugo site build completed."
8493
echo "Contents of public directory after build:"
8594
ls -la public
8695
@@ -132,7 +141,6 @@ jobs:
132141
environment:
133142
name: github-pages
134143
url: ${{ steps.deployment.outputs.page_url }}
135-
# Switched to a standard x86_64 runner for consistency
136144
runs-on: ubuntu-24.04
137145
needs: build
138146
steps:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@ yarn.lock
2020
/tmp/ogImageData.json
2121
/tmp/output_full.txt
2222
/tmp/
23+
24+
# Ignore generated OG images and cache files
25+
content/**/*-og.jpg
26+
static/images/og-image.jpg
27+
tmp/og-cache-manifest.json

content/terms-conditions/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ You agree to use Open-Neuromorphic.org in a manner that is lawful and in accorda
1818

1919
## 4. Privacy Policy
2020

21-
Your use of Open-Neuromorphic.org is also governed by our Privacy Policy, which can be found [here](/privacy-policy). Please review our Privacy Policy to understand how we collect, use, and protect your personal information.
21+
Your use of Open-Neuromorphic.org is also governed by our Privacy Policy, which can be found [here](/privacy). Please review our Privacy Policy to understand how we collect, use, and protect your personal information.
2222

2323
## 5. Intellectual Property
2424

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"author": "VisionInit.dev",
77
"scripts": {
88
"dev": "hugo serve --buildFuture",
9-
"build": "npm run og-images && rm public -fr && hugo --gc --minify --buildFuture --templateMetrics --templateMetricsHints --forceSyncStatic -e production",
9+
"hugo-build": "hugo --gc --minify --buildFuture --templateMetrics --templateMetricsHints --forceSyncStatic -e production",
10+
"build": "npm run og-images && npm run hugo-build",
1011
"build-preview": "hugo server --disableFastRender --buildFuture --navigateToChanged --templateMetrics --templateMetricsHints --forceSyncStatic -e production --minify",
1112
"og-images": "node scripts/collectOgData.js && node scripts/generateOgImages.js"
1213
},

scripts/collectOgData.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const { join, dirname, basename } = require('path');
33
const { readdir, readFile, stat, mkdir, writeFile } = require('fs/promises');
44
const mimeTypes = require('mime-types');
5+
const crypto = require('crypto');
56

67
// --- Configuration ---
78
const PROJECT_ROOT = process.cwd();
@@ -12,7 +13,8 @@ const TMP_DIR = join(PROJECT_ROOT, 'tmp');
1213
const OUTPUT_JSON_PATH = join(TMP_DIR, 'ogImageData.json');
1314
const OUTPUT_FORMAT = 'jpg';
1415
const LOGO_PATH_IN_ASSETS = 'images/ONM-logo.png';
15-
const BACKGROUND_IMAGE_PATH_IN_ASSETS = 'images/ONM.png'; // Added this line
16+
const BACKGROUND_IMAGE_PATH_IN_ASSETS = 'images/ONM.png';
17+
const OG_TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'template.html');
1618
const HOMEPAGE_TITLE = "Advancing Neuromorphic Computing, Together.";
1719
const HOMEPAGE_DESCRIPTION = "Open Neuromorphic (ONM) is a global community fostering education, research, and open-source collaboration in brain-inspired AI and hardware.";
1820

@@ -30,6 +32,10 @@ async function ensureDir(dirPath) {
3032
catch (err) { if (err.code !== 'EEXIST') throw err; }
3133
}
3234

35+
function createHash(data) {
36+
return crypto.createHash('sha256').update(data).digest('hex');
37+
}
38+
3339
async function findMarkdownFiles(dir) {
3440
let entries;
3541
try { entries = await readdir(dir); } catch (err) { console.warn(`Could not read directory ${dir}: ${err.message}`); return []; }
@@ -83,37 +89,41 @@ async function getImageDataUri(filePath) {
8389

8490
// --- Main Script ---
8591
async function collectData() {
86-
console.log('📊 Collecting OG image data...');
92+
console.log('📊 Collecting OG image data and calculating hashes...');
8793
await ensureDir(TMP_DIR);
8894

95+
const templateBuffer = await readFile(OG_TEMPLATE_PATH);
96+
const logoBuffer = await readFile(join(ASSETS_DIR, LOGO_PATH_IN_ASSETS));
97+
const backgroundBuffer = await readFile(join(ASSETS_DIR, BACKGROUND_IMAGE_PATH_IN_ASSETS));
98+
const globalHash = createHash(Buffer.concat([templateBuffer, logoBuffer, backgroundBuffer]));
99+
89100
const absoluteLogoPath = join(ASSETS_DIR, LOGO_PATH_IN_ASSETS);
90101
const logoDataUri = await getImageDataUri(absoluteLogoPath);
91102
if (!logoDataUri) {
92103
console.error(`❌ Critical: Could not load logo from ${absoluteLogoPath}. Cannot proceed.`);
93104
process.exit(1);
94105
}
95106

96-
// --- Add logic for background image ---
97107
const absoluteBackgroundPath = join(ASSETS_DIR, BACKGROUND_IMAGE_PATH_IN_ASSETS);
98108
const backgroundDataUri = await getImageDataUri(absoluteBackgroundPath);
99-
if (!backgroundDataUri) {
100-
console.warn(`⚠️ Background image not found at ${absoluteBackgroundPath}, will proceed without it.`);
101-
}
102109

103110
const outputData = {
111+
globalHash,
104112
logoDataUri,
105-
backgroundDataUri: backgroundDataUri || '', // Add background URI
113+
backgroundDataUri: backgroundDataUri || '',
106114
pages: [],
107115
};
108116

109117
// 1. Add Homepage Data
118+
const homepageContentHash = createHash(HOMEPAGE_TITLE + HOMEPAGE_DESCRIPTION);
119+
const homepageFinalHash = createHash(homepageContentHash + globalHash);
110120
const homepageOgDir = join(STATIC_DIR, 'images');
111121
await ensureDir(homepageOgDir);
112122
outputData.pages.push({
113123
title: HOMEPAGE_TITLE,
114124
description: HOMEPAGE_DESCRIPTION,
115125
outputPath: join(homepageOgDir, `og-image.${OUTPUT_FORMAT}`),
116-
tempHtmlPath: join(TMP_DIR, `homepage-temp-og.html`),
126+
finalHash: homepageFinalHash
117127
});
118128

119129
// 2. Process Content Pages
@@ -128,14 +138,17 @@ async function collectData() {
128138
continue;
129139
}
130140

141+
const contentHash = createHash(title + description);
142+
const finalHash = createHash(contentHash + globalHash);
143+
131144
const parentDirName = basename(pageDirectory);
132145
const ogImageFilename = `${parentDirName}-og.${OUTPUT_FORMAT}`;
133146

134147
outputData.pages.push({
135148
title,
136149
description,
137150
outputPath: join(pageDirectory, ogImageFilename),
138-
tempHtmlPath: join(TMP_DIR, `${parentDirName}-${Date.now()}-temp-og.html`),
151+
finalHash
139152
});
140153
} catch (err) {
141154
console.error(`❌ Error processing ${mdFile}: ${err.message}`);

scripts/generateOgImages.js

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// scripts/generateOgImages.js
12
const puppeteer = require('puppeteer');
23
const { join } = require('path');
34
const { readFile, writeFile, unlink, stat } = require('fs/promises');
@@ -7,14 +8,15 @@ const PROJECT_ROOT = process.cwd();
78
const TMP_DIR = join(PROJECT_ROOT, 'tmp');
89
const TEMPLATE_PATH = join(PROJECT_ROOT, 'assets', 'og-template', 'template.html');
910
const INPUT_JSON_PATH = join(TMP_DIR, 'ogImageData.json');
11+
const CACHE_MANIFEST_PATH = join(TMP_DIR, 'og-cache-manifest.json');
1012
const JPEG_QUALITY = 90;
1113

1214
async function pathExists(path) {
1315
try { await stat(path); return true; } catch { return false; }
1416
}
1517

1618
async function generateImages() {
17-
console.log('🖼️ Starting OG Image Generation with Puppeteer...');
19+
console.log('🖼️ Starting OG Image Generation with Caching...');
1820

1921
// --- Pre-flight checks ---
2022
if (!(await pathExists(INPUT_JSON_PATH))) {
@@ -24,9 +26,18 @@ async function generateImages() {
2426
throw new Error(`❌ OG template not found: ${TEMPLATE_PATH}`);
2527
}
2628

27-
// --- Read data and template ---
29+
// --- Read data, template, and cache manifest ---
2830
const templateContent = await readFile(TEMPLATE_PATH, 'utf8');
2931
const jsonData = JSON.parse(await readFile(INPUT_JSON_PATH, 'utf8'));
32+
33+
let oldCache = {};
34+
try {
35+
oldCache = JSON.parse(await readFile(CACHE_MANIFEST_PATH, 'utf8'));
36+
} catch (e) {
37+
console.log('ℹ️ No existing cache manifest found. Will generate all images.');
38+
}
39+
40+
const newCache = {};
3041

3142
if (!jsonData || !jsonData.pages || jsonData.pages.length === 0) {
3243
console.warn('⚠️ No pages to process.');
@@ -41,16 +52,25 @@ async function generateImages() {
4152

4253
let successCount = 0;
4354
let errorCount = 0;
55+
let skippedCount = 0;
4456

4557
// --- Process each page ---
4658
for (const pageData of jsonData.pages) {
47-
const { title, description, outputPath } = pageData;
59+
const { title, description, outputPath, finalHash } = pageData;
4860

4961
try {
62+
const fileAlreadyExists = await pathExists(outputPath);
63+
const isCacheValid = oldCache[outputPath] === finalHash;
64+
65+
if (fileAlreadyExists && isCacheValid) {
66+
skippedCount++;
67+
newCache[outputPath] = finalHash; // Keep valid entry in new cache
68+
continue;
69+
}
70+
5071
const page = await browser.newPage();
5172
await page.setViewport({ width: 1200, height: 630 });
5273

53-
// Populate the template with page data
5474
const htmlContent = templateContent
5575
.replace('LOGO_SRC', jsonData.logoDataUri)
5676
.replace('BACKGROUND_URL', jsonData.backgroundDataUri || '')
@@ -59,7 +79,6 @@ async function generateImages() {
5979

6080
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
6181

62-
// Take the screenshot
6382
await page.screenshot({
6483
path: outputPath,
6584
type: 'jpeg',
@@ -69,6 +88,7 @@ async function generateImages() {
6988
const stats = await stat(outputPath);
7089
console.log(`✅ Generated: ${outputPath.replace(PROJECT_ROOT, '')} (${(stats.size / 1024).toFixed(1)} KB)`);
7190
successCount++;
91+
newCache[outputPath] = finalHash; // Add new entry to cache
7292

7393
await page.close();
7494
} catch (err) {
@@ -77,9 +97,28 @@ async function generateImages() {
7797
errorCount++;
7898
}
7999
}
80-
100+
81101
await browser.close();
82-
console.log(`\n✨ Generation complete! Succeeded: ${successCount}, Failed: ${errorCount}.`);
102+
103+
// --- Cleanup stale images and cache entries ---
104+
const validOutputPaths = new Set(jsonData.pages.map(p => p.outputPath));
105+
let cleanedCount = 0;
106+
for (const oldPath in oldCache) {
107+
if (!validOutputPaths.has(oldPath)) {
108+
if (await pathExists(oldPath)) {
109+
await unlink(oldPath);
110+
cleanedCount++;
111+
}
112+
}
113+
}
114+
if (cleanedCount > 0) {
115+
console.log(`🗑️ Cleaned up ${cleanedCount} stale OG image(s).`);
116+
}
117+
118+
// --- Write the new cache manifest ---
119+
await writeFile(CACHE_MANIFEST_PATH, JSON.stringify(newCache, null, 2));
120+
121+
console.log(`\n✨ Generation complete! Succeeded: ${successCount}, Skipped: ${skippedCount}, Failed: ${errorCount}.`);
83122
if (errorCount > 0) process.exit(1);
84123
}
85124

0 commit comments

Comments
 (0)