feat: implement build version check and update build info handling

This commit is contained in:
2026-02-25 15:53:00 +00:00
parent f6f565bbcb
commit e13e696253
10 changed files with 186 additions and 66 deletions

View File

@@ -1,6 +1,36 @@
const fs = require("fs")
const { execSync } = require("child_process")
const postcssPlugin = require("esbuild-postcss")
// Resolve version at build time via git describe (tag + hash), fallback to env var or "dev"
let gitHash = "dev"
try {
gitHash = process.env.GIT_HASH || execSync("git describe --tags --always --dirty").toString().trim()
} catch (_) {
// .git not available (e.g. Docker build without .git context)
}
// Generate buildInfo module that can be imported by frontend code
function writeBuildInfo() {
const info = {
gitHash,
buildTime: new Date().toISOString(),
}
fs.writeFileSync(
__dirname + "/frontend/src/lib/buildInfo.ts",
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nexport const gitHash = ${JSON.stringify(info.gitHash)}\nexport const buildTime = ${JSON.stringify(info.buildTime)}\n`
)
// Write same buildInfo for backend hooks (X-Build-Time / X-Release headers)
fs.writeFileSync(
__dirname + "/api/hooks/lib/buildInfo.js",
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nmodule.exports = { gitHash: ${JSON.stringify(info.gitHash)}, buildTime: ${JSON.stringify(info.buildTime)} }\n`
)
}
// NOTE: writeBuildInfo() is NOT called here at top-level.
// It is called by esbuild-wrapper.js before each build.
// This prevents the server build from overwriting the frontend's timestamp
// (which would cause a version mismatch and spurious auto-reload).
const resolvePlugin = {
name: "resolvePlugin",
setup(build) {
@@ -63,7 +93,7 @@ const options = {
".ttf": "file",
},
sourcemap: true,
target: ["es2020", "chrome61", "firefox60", "safari11", "edge18"],
target: ["es2022", "chrome100", "firefox100", "safari15", "edge100"],
}
const bsMiddleware = []
@@ -83,6 +113,56 @@ if (process.argv[2] == "start") {
})
)
// Sentry tunnel proxy: forwards /_s requests to Sentry ingest to avoid ad-blockers
const https = require("https")
const http = require("http")
bsMiddleware.push(function (req, res, next) {
if (req.url !== "/_s" || req.method !== "POST") return next()
let body = []
req.on("data", (chunk) => body.push(chunk))
req.on("end", () => {
const payload = Buffer.concat(body).toString()
const firstLine = payload.split("\n")[0]
let envelope
try {
envelope = JSON.parse(firstLine)
} catch {
res.writeHead(400)
res.end()
return
}
const dsn = envelope.dsn || ""
let sentryUrl
try {
sentryUrl = new URL(dsn)
} catch {
res.writeHead(400)
res.end()
return
}
const projectId = sentryUrl.pathname.replace(/\//g, "")
const ingestUrl = `${sentryUrl.protocol}//${sentryUrl.host}/api/${projectId}/envelope/`
const transport = ingestUrl.startsWith("https") ? https : http
const proxyReq = transport.request(
ingestUrl,
{
method: "POST",
headers: { "Content-Type": "application/x-sentry-envelope" },
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode || 200)
proxyRes.pipe(res)
}
)
proxyReq.on("error", () => {
res.writeHead(502)
res.end()
})
proxyReq.write(payload)
proxyReq.end()
})
})
if (process.env.SSR) {
bsMiddleware.push(
createProxyMiddleware({
@@ -103,6 +183,7 @@ if (process.argv[2] == "start") {
module.exports = {
sveltePlugin: sveltePlugin,
resolvePlugin: resolvePlugin,
writeBuildInfo: writeBuildInfo,
options: options,
distDir,
watch: {
@@ -121,6 +202,20 @@ module.exports = {
...bsMiddleware,
require("connect-history-api-fallback")({
index: "/spa.html",
// Allow dots in URL paths (e.g. /version-3.7.1) to fall through to SPA,
// but only if they don't look like actual file requests.
rewrites: [
{
from: /\.(js|css|mjs|map|woff2?|ttf|eot|svg|png|jpe?g|gif|webp|ico|json|pdf)(\?.*)?$/i,
to: function (context) {
return context.parsedUrl.pathname
},
},
{
from: /./,
to: "/spa.html",
},
],
// verbose: true,
}),
],