feat: show build SHA + timestamp in a small footer for deploy verification
Adds a BuildStamp component pinned to the bottom-right of every page (desktop only, dim monospace text) so it's obvious at a glance which build is currently running. The git short SHA and an ISO build timestamp are injected at build time via Vite's `define`, falling back to 'unknown' if git is not available. ESLint config declares the two compile-time constants as readonly globals so no per-file disable comments are needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,11 @@ export default defineConfig([
|
|||||||
reactRefresh.configs.vite,
|
reactRefresh.configs.vite,
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: globals.browser,
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
__BUILD_SHA__: 'readonly',
|
||||||
|
__BUILD_TIME__: 'readonly',
|
||||||
|
},
|
||||||
parserOptions: { ecmaFeatures: { jsx: true } },
|
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Routes, Route, Navigate, Link } from 'react-router-dom'
|
|||||||
import { BookOpen, CheckSquare, LayoutDashboard, Trophy, Settings, LogOut } from 'lucide-react'
|
import { BookOpen, CheckSquare, LayoutDashboard, Trophy, Settings, LogOut } from 'lucide-react'
|
||||||
import { useApp } from './store/AppContext'
|
import { useApp } from './store/AppContext'
|
||||||
import Mark from './components/ui/Mark'
|
import Mark from './components/ui/Mark'
|
||||||
|
import BuildStamp from './components/ui/BuildStamp'
|
||||||
import ChatLauncher from './components/chat/ChatLauncher'
|
import ChatLauncher from './components/chat/ChatLauncher'
|
||||||
|
|
||||||
import Login from './pages/Login'
|
import Login from './pages/Login'
|
||||||
@@ -88,6 +89,7 @@ function App() {
|
|||||||
if (state.isLoading) return <div className="p-8 flex items-center justify-center min-h-screen">Loading application...</div>
|
if (state.isLoading) return <div className="p-8 flex items-center justify-center min-h-screen">Loading application...</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
|
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
|
||||||
@@ -96,6 +98,8 @@ function App() {
|
|||||||
<Route path="/leaderboard" element={<ProtectedRoute><Leaderboard /></ProtectedRoute>} />
|
<Route path="/leaderboard" element={<ProtectedRoute><Leaderboard /></ProtectedRoute>} />
|
||||||
<Route path="/admin/*" element={<ProtectedRoute requireAdmin={true}><Admin /></ProtectedRoute>} />
|
<Route path="/admin/*" element={<ProtectedRoute requireAdmin={true}><Admin /></ProtectedRoute>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
<BuildStamp />
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/components/ui/BuildStamp.jsx
Normal file
19
src/components/ui/BuildStamp.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const sha = typeof __BUILD_SHA__ === 'string' ? __BUILD_SHA__ : 'dev';
|
||||||
|
const time = typeof __BUILD_TIME__ === 'string' ? __BUILD_TIME__ : new Date().toISOString();
|
||||||
|
|
||||||
|
const formatted = (() => {
|
||||||
|
const d = new Date(time);
|
||||||
|
if (Number.isNaN(d.getTime())) return time;
|
||||||
|
return d.toLocaleString('nl-NL', { dateStyle: 'short', timeStyle: 'short' });
|
||||||
|
})();
|
||||||
|
|
||||||
|
const BuildStamp = () => (
|
||||||
|
<div
|
||||||
|
className="hidden md:block fixed bottom-1 right-2 text-[10px] font-mono text-fg-muted/60 z-40 pointer-events-none select-none"
|
||||||
|
title={`Build ${sha} at ${time}`}
|
||||||
|
>
|
||||||
|
v{sha} · {formatted}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BuildStamp;
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
function getBuildSha() {
|
||||||
|
try {
|
||||||
|
return execSync('git rev-parse --short HEAD', { stdio: ['ignore', 'pipe', 'ignore'] })
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
} catch {
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
define: {
|
||||||
|
__BUILD_SHA__: JSON.stringify(getBuildSha()),
|
||||||
|
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
|
||||||
|
},
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Reference in New Issue
Block a user