From 80f6aba4a8874775aec3e923b82ac410aaa6eed4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:19:43 +0000 Subject: [PATCH] feat: display version hash and build date in footer Refactored scripts/hash-dist.mjs to export buildHashState and support module import. Modified scripts/build-dist.mjs to calculate the dist hash after asset rewriting and inject it along with the current date into dist/index.html below the tagline. Verified with Playwright screenshot. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- scripts/build-dist.mjs | 35 +++++++++++++++++++++++++++++++++++ scripts/hash-dist.mjs | 7 +++++-- verification.png | Bin 0 -> 5944 bytes verify_version.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 verification.png create mode 100644 verify_version.py diff --git a/scripts/build-dist.mjs b/scripts/build-dist.mjs index c19d684d..1aa36a6f 100644 --- a/scripts/build-dist.mjs +++ b/scripts/build-dist.mjs @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { execSync } from 'node:child_process'; import crypto from 'node:crypto'; +import { buildHashState } from './hash-dist.mjs'; const DIST = 'dist'; const FILES_TO_COPY = [ @@ -154,6 +155,38 @@ function rewriteEntryHtmlAssetPaths(manifest) { } } +function injectVersionInfo() { + console.log('Injecting version hash and date...'); + try { + const hashState = buildHashState(DIST); + const hash = hashState.combined; + const date = new Date().toISOString().split('T')[0]; + + const indexHtmlPath = path.join(DIST, 'index.html'); + if (fs.existsSync(indexHtmlPath)) { + let content = fs.readFileSync(indexHtmlPath, 'utf8'); + + const versionHtml = ` +
+ v: ${hash.slice(0, 8)} • ${date} +
`; + + // Fallback search if whitespace differs + const taglineRegex = /(seed\. zap\. subscribe\.\s*<\/h2>)/; + if (taglineRegex.test(content)) { + content = content.replace(taglineRegex, '$1' + versionHtml); + fs.writeFileSync(indexHtmlPath, content); + console.log(`Injected version: ${hash.slice(0, 8)} • ${date}`); + } else { + console.warn('Could not find tagline to inject version info.'); + } + } + } catch (error) { + console.error('Failed to inject version info:', error); + // Continue even if injection fails, as it's not critical for functionality + } +} + function main() { console.log('Validating service worker compatibility guard...'); try { @@ -209,6 +242,8 @@ function main() { console.log('Rewriting HTML entry points with hashed assets...'); rewriteEntryHtmlAssetPaths(manifest); + injectVersionInfo(); + console.log('Build complete.'); } diff --git a/scripts/hash-dist.mjs b/scripts/hash-dist.mjs index 8637c776..dc41b53e 100644 --- a/scripts/hash-dist.mjs +++ b/scripts/hash-dist.mjs @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; +import { fileURLToPath } from 'node:url'; function parseArgs(argv) { const args = { @@ -72,7 +73,7 @@ function hashBuffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); } -function buildHashState(distDir) { +export function buildHashState(distDir) { const resolvedDistDir = path.resolve(distDir); if (!fs.existsSync(resolvedDistDir)) { throw new Error(`Dist directory does not exist: ${resolvedDistDir}`); @@ -112,4 +113,6 @@ function main() { console.log(serialized); } -main(); +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main(); +} diff --git a/verification.png b/verification.png new file mode 100644 index 0000000000000000000000000000000000000000..fda37f88bd5f6d2244fd8e13174f2292c9b85225 GIT binary patch literal 5944 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 literal 0 HcmV?d00001 diff --git a/verify_version.py b/verify_version.py new file mode 100644 index 00000000..0130aaff --- /dev/null +++ b/verify_version.py @@ -0,0 +1,29 @@ +from playwright.sync_api import sync_playwright + +def run(): + with sync_playwright() as p: + browser = p.chromium.launch() + page = browser.new_page() + page.goto("http://localhost:3000") + + # Locate the tagline + tagline = page.get_by_text("seed. zap. subscribe.") + + # Locate the version info which should be nearby + # The tagline is an h2 inside a div. + container = tagline.locator("..") + + # Wait for fade-in to settle or just force visibility? + # The container has "fade-in" class. It might take time. + # I'll wait for the version text to be visible. + version_locator = container.locator("div.text-xs") + version_locator.wait_for() + + print("Version text found:", version_locator.inner_text()) + + # Screenshot the container + container.screenshot(path="verification.png") + browser.close() + +if __name__ == "__main__": + run()