feat(observability): add GlitchTip source map uploads for un-minified stack traces
Some checks failed
CI / Check ui (pull_request) Failing after 11s
CI / Docker / caddy (pull_request) Failing after 11s
CI / Docker / ui (pull_request) Has been skipped
CI / Test backend (pull_request) Successful in 30s
Release / Check ui (push) Failing after 38s
Release / Upload source maps (push) Has been skipped
Release / Docker / ui (push) Has been skipped
Release / Test backend (push) Successful in 48s
Release / Docker / caddy (push) Successful in 45s
CI / Docker / backend (pull_request) Has been cancelled
CI / Docker / runner (pull_request) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled

- Enable sourcemap:true in vite.config.ts
- Add sentryVitePlugin: uploads maps to errors.libnovel.cc, deletes them post-upload so they never ship in the Docker image
- Wire release: PUBLIC_BUILD_VERSION in both hooks.client.ts and hooks.server.ts so events correlate to the correct artifact
- Add upload-sourcemaps CI job in release.yaml (parallel to docker-ui, uses GLITCHTIP_AUTH_TOKEN secret)
This commit is contained in:
Admin
2026-03-25 20:26:19 +05:00
parent 7e99fc6d70
commit 023b1f7fec
6 changed files with 68 additions and 4 deletions

View File

@@ -135,6 +135,35 @@ jobs:
cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-runner:latest cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-runner:latest
cache-to: type=inline cache-to: type=inline
# ── ui: source map upload ─────────────────────────────────────────────────────
# Builds the UI with source maps and uploads them to GlitchTip so that error
# stack traces resolve to original .svelte/.ts file names and line numbers.
# Runs in parallel with docker-ui (both need check-ui to pass first).
upload-sourcemaps:
name: Upload source maps
runs-on: ubuntu-latest
needs: [check-ui]
defaults:
run:
working-directory: ui
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build with source maps and upload to GlitchTip
env:
SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }}
BUILD_VERSION: ${{ gitea.ref_name }}
run: npm run build
# ── docker: ui ──────────────────────────────────────────────────────────────── # ── docker: ui ────────────────────────────────────────────────────────────────
docker-ui: docker-ui:
name: Docker / ui name: Docker / ui
@@ -213,7 +242,7 @@ jobs:
release: release:
name: Gitea Release name: Gitea Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [docker-backend, docker-runner, docker-ui, docker-caddy] needs: [docker-backend, docker-runner, docker-ui, docker-caddy, upload-sourcemaps]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:

1
ui/package-lock.json generated
View File

@@ -17,6 +17,7 @@
"pocketbase": "^0.26.8" "pocketbase": "^0.26.8"
}, },
"devDependencies": { "devDependencies": {
"@sentry/vite-plugin": "^5.1.1",
"@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/adapter-node": "^5.5.4", "@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.50.2", "@sveltejs/kit": "^2.50.2",

View File

@@ -12,6 +12,7 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
}, },
"devDependencies": { "devDependencies": {
"@sentry/vite-plugin": "^5.1.1",
"@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/adapter-node": "^5.5.4", "@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.50.2", "@sveltejs/kit": "^2.50.2",

View File

@@ -6,7 +6,10 @@ import { env } from '$env/dynamic/public';
if (env.PUBLIC_GLITCHTIP_DSN) { if (env.PUBLIC_GLITCHTIP_DSN) {
Sentry.init({ Sentry.init({
dsn: env.PUBLIC_GLITCHTIP_DSN, dsn: env.PUBLIC_GLITCHTIP_DSN,
tracesSampleRate: 0.1 tracesSampleRate: 0.1,
// Must match the release name used when uploading source maps in CI
// (BUILD_VERSION injected by Dockerfile as PUBLIC_BUILD_VERSION).
release: env.PUBLIC_BUILD_VERSION || undefined
}); });
} }

View File

@@ -13,7 +13,10 @@ import { drain as drainPresignCache } from '$lib/server/presignCache';
if (pubEnv.PUBLIC_GLITCHTIP_DSN) { if (pubEnv.PUBLIC_GLITCHTIP_DSN) {
Sentry.init({ Sentry.init({
dsn: pubEnv.PUBLIC_GLITCHTIP_DSN, dsn: pubEnv.PUBLIC_GLITCHTIP_DSN,
tracesSampleRate: 0.1 tracesSampleRate: 0.1,
// Must match the release name used when uploading source maps in CI
// (BUILD_VERSION injected by Dockerfile as PUBLIC_BUILD_VERSION).
release: pubEnv.PUBLIC_BUILD_VERSION || undefined
}); });
} }

View File

@@ -1,9 +1,36 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { sentryVitePlugin } from '@sentry/vite-plugin';
// Source maps are always generated so that the CI pipeline can upload them to
// GlitchTip after a release build. The sentryVitePlugin upload step is only
// active when SENTRY_AUTH_TOKEN is present (i.e. in the release CI job).
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), sveltekit()], build: {
sourcemap: true
},
plugins: [
tailwindcss(),
sveltekit(),
sentryVitePlugin({
org: 'libnovel',
project: 'libnovel-ui',
url: 'https://errors.libnovel.cc/',
// Auth token injected by CI via SENTRY_AUTH_TOKEN env var.
// When the env var is absent the plugin is a no-op.
authToken: process.env.SENTRY_AUTH_TOKEN,
// Release name matches the Docker image tag (e.g. "1.2.3").
release: { name: process.env.BUILD_VERSION },
// Don't upload source maps in local dev or CI type-check jobs.
disable: !process.env.SENTRY_AUTH_TOKEN,
// Source maps are uploaded to GlitchTip; do not ship them in the
// production bundle served to browsers.
sourceMapsUploadOptions: {
filesToDeleteAfterUpload: ['./build/**/*.map']
}
})
],
ssr: { ssr: {
// Force these packages to be bundled into the server output rather than // Force these packages to be bundled into the server output rather than
// treated as external requires. The production Docker image has no // treated as external requires. The production Docker image has no