From b87e164087af34557dc78102a976d0091f132af4 Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Sat, 29 Aug 2026 10:52:25 +0200 Subject: [PATCH 1/2] fix(ci): inject Sentry debug IDs at build time so uploaded source maps match the deployed bundles --- .github/workflows/ci.yml | 11 +++++++++++ nuxt.config.ts | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13ab29d5e..48df5c8d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,13 @@ jobs: - name: Build application run: NUXT_APP_COMMIT_ID=$(git rev-parse --short HEAD) pnpm run build + # Injection rewrites the bundles and shifts their source maps by the same amount, so it + # has to happen on the artifact everyone downloads. Done in the `sentry` job instead, it + # would only ever touch a copy that is never deployed, and the maps would be uploaded + # two lines off. No credentials needed: the command only writes ids into the files. + - name: Inject Sentry debug IDs + run: pnpm dlx @sentry/cli sourcemaps inject .output/public/_nuxt + - name: Upload build artifacts uses: actions/upload-artifact@v7 with: @@ -421,6 +428,10 @@ jobs: SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_URL: ${{ secrets.SENTRY_URL }} with: + # Injection happens during the build, on the artifact this job downloads. Letting + # the action do it here would rewrite its own copy and upload maps two lines off + # from the deployed bundles. + inject: false sourcemaps: ".output/public/_nuxt" url_prefix: "~/_nuxt" environment: ${{ github.ref_name }} diff --git a/nuxt.config.ts b/nuxt.config.ts index cc0a6ff23..c075dd12d 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -410,7 +410,10 @@ export default defineNuxtConfig({ }, sentry: { sourceMapsUploadOptions: { - // disable sourcemaps upload from build, it's done later during the release with sentry-cli + // Keeping the bundler plugin out also keeps it from stamping its own debug id: it only + // writes one as a runtime snippet, never the `//# debugId=` comment and the matching + // key in the map that an upload pairs on, so it would compete with the id the CI build + // injects rather than replace it. Upload is done during the release with sentry-cli. enabled: false, }, }, @@ -431,6 +434,4 @@ export default defineNuxtConfig({ // TODO: add /support pages }, - // TODO: add sentry config for stack traces based on source maps - // https://docs.sentry.io/platforms/javascript/guides/nuxt/#add-readable-stack-traces-to-errors }) From 0770928a8d06fbd40779a8a08f37835b09c7ac1c Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Tue, 15 Sep 2026 10:12:52 +0200 Subject: [PATCH 2/2] fix(ci): stamp Sentry debug IDs during the build so uploaded source maps match the deployed bundles --- .github/workflows/ci.yml | 56 +++++++--------------------------------- nuxt.config.ts | 18 ++++++++----- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ab449b44..a7b9ee58d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,16 +46,17 @@ jobs: - name: Install dependencies run: pnpm install + # The Sentry plugin stamps debug ids into the bundles here, and uploads the source maps + # too, but only when it gets a token. Withholding the token everywhere except `main` and + # manual runs is what keeps PR builds from uploading: they inject and stay quiet. - name: Build application + env: + SENTRY_AUTH_TOKEN: ${{ (github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch') && secrets.SENTRY_AUTH_TOKEN || '' }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} run: NUXT_APP_COMMIT_ID=$(git rev-parse --short HEAD) pnpm run build - # Injection rewrites the bundles and shifts their source maps by the same amount, so it - # has to happen on the artifact everyone downloads. Done in the `sentry` job instead, it - # would only ever touch a copy that is never deployed, and the maps would be uploaded - # two lines off. No credentials needed: the command only writes ids into the files. - - name: Inject Sentry debug IDs - run: pnpm dlx @sentry/cli sourcemaps inject .output/public/_nuxt - - name: Upload build artifacts uses: actions/upload-artifact@v7 with: @@ -403,46 +404,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} # ============================================ - # JOB 5: Sentry - upload source maps (automatic on main, manual on any branch) - # ============================================ - sentry: - needs: [quality_and_test, e2e] - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v6 - - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - name: build-output - path: .output - - - name: Set version - id: version - run: | - echo "short=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT - - - name: Create Sentry release - uses: getsentry/action-release@v3 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - SENTRY_URL: ${{ secrets.SENTRY_URL }} - with: - # Injection happens during the build, on the artifact this job downloads. Letting - # the action do it here would rewrite its own copy and upload maps two lines off - # from the deployed bundles. - inject: false - sourcemaps: ".output/public/_nuxt" - url_prefix: "~/_nuxt" - environment: ${{ github.ref_name }} - version: ${{ steps.version.outputs.short }} - ignore_missing: true - - # ============================================ - # JOB 6: Publish datagouv-components (automatic on main, manual on any branch) + # JOB 5: Publish datagouv-components (automatic on main, manual on any branch) # ============================================ publish-datagouv-components: needs: [quality_and_test, e2e] diff --git a/nuxt.config.ts b/nuxt.config.ts index 79a7b700f..97a248a55 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -408,14 +408,20 @@ export default defineNuxtConfig({ xl: 1248, }, }, + // The bundler plugin stamps a debug id into every chunk and the same id into the copies it + // uploads, so Sentry pairs a frame with its map by identity instead of by release and URL. + // It does that during `renderChunk`, which is the only moment it can: Nitro records each + // asset's size and pre-compresses it at the end of the build, so anything rewriting + // `.output/public` afterwards serves a truncated script and a stale `.br`. + // The upload rides along and needs an auth token, which the CI only hands to `main` builds. sentry: { - sourceMapsUploadOptions: { - // Keeping the bundler plugin out also keeps it from stamping its own debug id: it only - // writes one as a runtime snippet, never the `//# debugId=` comment and the matching - // key in the map that an upload pairs on, so it would compete with the id the CI build - // injects rather than replace it. Upload is done during the release with sentry-cli. - enabled: false, + release: { + // Has to match what the SDK reports at runtime (`appConfig.commitId`), otherwise the + // plugin falls back to the full git sha and the two never line up. + name: process.env.NUXT_APP_COMMIT_ID, + deploy: process.env.GITHUB_REF_NAME ? { env: process.env.GITHUB_REF_NAME } : undefined, }, + telemetry: false, }, sitemap: {