mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
✨(build) add cache-busting source version in build
This commit is contained in:
@@ -100,6 +100,12 @@ jobs:
|
||||
context: "src/frontend"
|
||||
target: runtime-prod
|
||||
arm64_reuse_amd64_build_arg: "FRONTEND_IMAGE"
|
||||
# Bakes the commit SHA into the build as the asset cache-buster
|
||||
# (vite.config.ts → __SOURCE_VERSION__); without it the build falls back to a
|
||||
# timestamp. The src/frontend context ships no .git, so this is the only
|
||||
# way the image gets the real SHA. SOURCE_VERSION matches the var name
|
||||
# Scalingo's buildpack sets natively, so both deploy paths agree.
|
||||
build_args: "SOURCE_VERSION=${{ github.sha }}"
|
||||
|
||||
docker-publish-backend:
|
||||
needs: docker-publish-python-uv
|
||||
|
||||
@@ -41,6 +41,13 @@ COPY --chown=${DOCKER_USER} . ./
|
||||
ARG API_ORIGIN
|
||||
ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN}
|
||||
|
||||
# Commit SHA (or any per-deploy version) baked in as the asset cache-buster —
|
||||
# see vite.config.ts. Named SOURCE_VERSION to match the var Scalingo's buildpack
|
||||
# sets natively. Pass `--build-arg SOURCE_VERSION=$(git rev-parse --short HEAD)`;
|
||||
# if omitted the build falls back to a timestamp (still unique per deploy).
|
||||
ARG SOURCE_VERSION
|
||||
ENV SOURCE_VERSION=${SOURCE_VERSION}
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Normalize output path to /app (matches the runtime-prod layout)
|
||||
|
||||
@@ -47,6 +47,17 @@
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
}
|
||||
|
||||
# --- Cache policy for the static SPA output ---
|
||||
# Vite fingerprints everything under /assets, so a given URL's bytes
|
||||
# never change: cache it forever. A new deploy emits new hashed URLs.
|
||||
@immutable path /assets/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
# The SPA shell and other non-fingerprinted files (index.html, the
|
||||
# /locales JSON, images) must revalidate every load, or a deploy's new
|
||||
# asset hashes / translations only show up after a manual hard refresh.
|
||||
@revalidate not path /assets/*
|
||||
header @revalidate Cache-Control "no-cache"
|
||||
|
||||
# SPA fallback: any unmatched request returns index.html so the
|
||||
# client-side router can resolve the path.
|
||||
try_files {path} /index.html
|
||||
|
||||
@@ -332,9 +332,9 @@
|
||||
"Check DNS again": "Revérifier les DNS",
|
||||
"Check for new mail regularly": "Synchroniser ce compte régulièrement",
|
||||
"Checking DNS records...": "Vérification des enregistrements DNS...",
|
||||
"Checking every {{count}} min_one": "Synchronisation toutes les {{count}} min",
|
||||
"Checking every {{count}} min_many": "Synchronisation toutes les {{count}} min",
|
||||
"Checking every {{count}} min_other": "Synchronisation toutes les {{count}} min",
|
||||
"Checking every {{count}} min_one": "Synchro toutes les {{count}} min",
|
||||
"Checking every {{count}} min_many": "Synchro toutes les {{count}} min",
|
||||
"Checking every {{count}} min_other": "Synchro toutes les {{count}} min",
|
||||
"Choose calendar": "Choisir l'agenda",
|
||||
"Choose the type of integration you want to create": "Choisissez le type d'intégration que vous souhaitez créer",
|
||||
"Clear filters": "Retirer les filtres",
|
||||
|
||||
@@ -43,6 +43,10 @@ export const initI18n = (config: AppConfig) => {
|
||||
returnEmptyString: false,
|
||||
backend: {
|
||||
loadPath: "/locales/{{ns}}/{{lng}}.json",
|
||||
// Cache-bust the static locale files on every deploy: their URLs are
|
||||
// otherwise fixed, so a CDN/browser keeps serving a stale copy and any
|
||||
// newly added translation falls back to English (a half-translated UI).
|
||||
queryStringParams: { v: __SOURCE_VERSION__ },
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
Vendored
+4
@@ -44,3 +44,7 @@ interface ImportMetaEnv {
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
// Per-build version stamp injected by Vite (see vite.config.ts `define`). Used
|
||||
// to cache-bust runtime-loaded assets such as the locale JSON files.
|
||||
declare const __SOURCE_VERSION__: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { defineConfig, type Plugin } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
@@ -10,8 +11,32 @@ import pkg from './package.json';
|
||||
// Single source of truth for supported browsers; drives which polyfills the
|
||||
// legacy plugin injects (see the `legacy` plugin below).
|
||||
const browserslist = pkg.browserslist;
|
||||
// A per-build version stamp, baked in as `__SOURCE_VERSION__`. Used to cache-bust
|
||||
// the runtime-fetched locale JSONs (their URLs are otherwise fixed, so a CDN /
|
||||
// browser keeps serving a stale copy after a deploy — a half-translated UI).
|
||||
// Resolution order, most authoritative first:
|
||||
// - SOURCE_VERSION commit SHA — injected by Scalingo's buildpack natively,
|
||||
// and passed as a build-arg from our CI Docker build
|
||||
// - `git` local builds / any context that ships the .git dir
|
||||
// - timestamp last-resort, still unique from one deploy to the next
|
||||
const appVersion =
|
||||
process.env.SOURCE_VERSION ||
|
||||
(() => {
|
||||
try {
|
||||
return execSync('git rev-parse --short HEAD', {
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
})
|
||||
.toString()
|
||||
.trim();
|
||||
} catch {
|
||||
return `t${Date.now()}`;
|
||||
}
|
||||
})();
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
__SOURCE_VERSION__: JSON.stringify(appVersion),
|
||||
},
|
||||
plugins: [
|
||||
// See ./tsr.config.json for tanstackRouter config
|
||||
tanstackRouter(),
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import path from 'path';
|
||||
import { defineConfig, mergeConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
import viteConfig from './vite.config';
|
||||
|
||||
export default mergeConfig(
|
||||
// Inherit the app's build config — `define`, `resolve.alias`, `envPrefix` —
|
||||
// so tests compile modules exactly like the app does and can't drift from it.
|
||||
// Plugins are dropped rather than merged: the router codegen, the legacy
|
||||
// polyfill pass and the bundle analyzer are build-time concerns that only
|
||||
// slow the runner down. `mergeConfig` concatenates arrays, so the reset has
|
||||
// to happen before the merge, not in the override below.
|
||||
{ ...viteConfig, plugins: [] },
|
||||
defineConfig({
|
||||
// Pin the build stamp: vite.config.ts derives it from the git HEAD (or a
|
||||
// timestamp when .git is absent, as in the test container), which would
|
||||
// make every run compile to different output for no benefit.
|
||||
define: {
|
||||
__SOURCE_VERSION__: JSON.stringify('test'),
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
@@ -13,12 +28,5 @@ export default defineConfig({
|
||||
reportsDirectory: '.coverage',
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
// Keep parity with vite.config.ts so tests can stub the NEXT_PUBLIC_*
|
||||
// deprecated fallbacks via import.meta.env.
|
||||
envPrefix: 'NEXT_PUBLIC_',
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user