feat(analytics): upload web source maps to Sentry + tie release to build (#369)

* feat(analytics): upload web source maps to Sentry + tie release to build

Web crash reports were unusable: the bundle ships minified with no source
maps uploaded, and every build reported as the frozen APP_VERSION, so a
Sentry error showed an unreadable stack under a single release.

- Add @sentry/vite-plugin: emit hidden source maps and upload them by debug
  id when SENTRY_AUTH_TOKEN is present (published Docker build only), then
  delete the maps so they never ship. No-op for dev and the source archive.
- Set the Sentry release from SENTRY_RELEASE / VITE_SENTRY_RELEASE (the Docker
  build passes the release version), falling back to APP_VERSION.
- Relax beforeSend so app bundle frames keep a host-stripped path (Sentry needs
  it to match the uploaded map) while the instance hostname, error message, and
  PII stay stripped. Filesystem paths still collapse to the basename.
- Wire the Dockerfile (sentry_auth_token build secret + SENTRY_RELEASE arg/env)
  and the release docker job.

* fix(analytics): point source map upload at the snapotter org (project node)
This commit is contained in:
SnapOtter
2026-06-29 10:47:30 +08:00
committed by GitHub
parent 9819c5885e
commit 1c202c6ef0
10 changed files with 386 additions and 10 deletions
+3 -2
View File
@@ -220,9 +220,10 @@ describe("analytics lib (baked model)", () => {
expect(result.breadcrumbs).toBeUndefined();
// The exception message is replaced by its type so no free text leaves.
expect(result.exception.values[0].value).toBe("TypeError");
// Stack frames keep only the basename; absolute paths are dropped.
// Filesystem paths collapse to the basename (directory and any username
// stripped); abs_path is scrubbed the same way rather than dropped.
expect(result.exception.values[0].stacktrace.frames[0].filename).toBe("file.png");
expect(result.exception.values[0].stacktrace.frames[0].abs_path).toBeUndefined();
expect(result.exception.values[0].stacktrace.frames[0].abs_path).toBe("photo.jpeg");
});
it("handles event without user or exception fields", async () => {