# Datadog RUM Sourcemaps Via CI Implementation Plan > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Generate private frontend sourcemaps for Datadog RUM/Error Tracking, upload them from GitHub Actions, and keep `.map` files out of deployed frontend assets. **Architecture:** Add an explicit release-version contract to the main app’s RUM init, enable hidden sourcemaps in all Vite frontends, and create a frontend CI workflow that builds each app, uploads sourcemaps with matching Datadog metadata, and removes `.map` files before any deploy step consumes the build output. **Tech Stack:** React 19, Vite 6, Vitest, GitHub Actions, Datadog CI CLI, Cloudflare/Wrangler deploy targets, pnpm workspaces. **Spec:** `docs/superpowers/specs/2026-04-22-datadog-rum-sourcemaps-via-ci-design.md` --- ## File Structure ### New files - `.github/workflows/frontend-sourcemaps.yml` — GitHub Actions workflow that builds the frontend apps on a self-hosted runner and uploads sourcemaps to Datadog. - `apps/clients/src/vite-env.d.ts` — Vite env typings for client-specific build variables, including `VITE_APP_VERSION`. - `apps/main/src/lib/rum.test.ts` — regression test for Datadog RUM init config. - `apps/main/vite.config.test.ts` — production sourcemap config test for `apps/main`. - `apps/clients/vite.config.test.ts` — production sourcemap config test for `apps/clients`. - `apps/external/vite.config.test.ts` — production sourcemap config test for `apps/external`. ### Modified files - `package.json` — add `@datadog/datadog-ci` to root dev dependencies. - `apps/main/src/lib/rum.ts` — add `version: import.meta.env.VITE_APP_VERSION`. - `apps/main/src/vite-env.d.ts` — add `VITE_APP_VERSION` typing. - `apps/external/src/vite-env.d.ts` — add `VITE_APP_VERSION` typing. - `apps/main/vite.config.ts` — enable hidden sourcemaps for non-test builds. - `apps/clients/vite.config.ts` — enable hidden sourcemaps for non-test builds. - `apps/external/vite.config.ts` — enable hidden sourcemaps for non-test builds. --- ## Chunk 1: Runtime Release Metadata ### Task 1: Wire Datadog RUM version in `apps/main` **Files:** - Create: `apps/main/src/lib/rum.test.ts` - Modify: `apps/main/src/lib/rum.ts` - Modify: `apps/main/src/vite-env.d.ts` - [ ] **Step 1: Write the failing RUM init test** Create `apps/main/src/lib/rum.test.ts`: ```typescript import { beforeEach, describe, expect, it, vi } from "vitest"; const init = vi.fn(); vi.mock("@datadog/browser-rum", () => ({ datadogRum: { init, }, })); vi.mock("@datadog/browser-rum-react", () => ({ reactPlugin: () => "react-plugin", })); describe("rum config", () => { beforeEach(() => { init.mockReset(); vi.resetModules(); }); it("sets the Datadog release version from VITE_APP_VERSION", async () => { vi.stubEnv("VITE_APP_VERSION", "test-sha"); vi.stubEnv("MODE", "production"); await import("./rum"); expect(init).toHaveBeenCalledWith( expect.objectContaining({ service: "xtablo-ui", env: "production", version: "test-sha", }) ); }); }); ``` - [ ] **Step 2: Run the test to verify it fails** Run: ```bash pnpm --filter @xtablo/main exec vitest run src/lib/rum.test.ts --mode dev ``` Expected: FAIL because `version` is not set in `datadogRum.init`. - [ ] **Step 3: Add the runtime version** Update `apps/main/src/lib/rum.ts`: ```typescript service: "xtablo-ui", env: import.meta.env.MODE, version: import.meta.env.VITE_APP_VERSION, ``` Update `apps/main/src/vite-env.d.ts`: ```typescript /// interface ImportMetaEnv { readonly VITE_APP_VERSION: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` - [ ] **Step 4: Run the test to verify it passes** Run: ```bash pnpm --filter @xtablo/main exec vitest run src/lib/rum.test.ts --mode dev pnpm --filter @xtablo/main typecheck ``` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add apps/main/src/lib/rum.ts apps/main/src/lib/rum.test.ts apps/main/src/vite-env.d.ts git commit -m "feat: add Datadog release version to main rum config" ``` --- ## Chunk 2: Hidden Sourcemaps In All Vite Frontends ### Task 2: Add production sourcemap config tests **Files:** - Create: `apps/main/vite.config.test.ts` - Create: `apps/clients/vite.config.test.ts` - Create: `apps/external/vite.config.test.ts` - [ ] **Step 1: Write the failing config test for `apps/main`** Create `apps/main/vite.config.test.ts`: ```typescript import { describe, expect, it } from "vitest"; import configFactory from "./vite.config"; describe("main vite config", () => { it("uses hidden sourcemaps for production builds", () => { const config = configFactory({ mode: "production" }); expect(config.build?.sourcemap).toBe("hidden"); }); }); ``` - [ ] **Step 2: Write the failing config test for `apps/clients`** Create `apps/clients/vite.config.test.ts`: ```typescript import { describe, expect, it } from "vitest"; import configFactory from "./vite.config"; describe("clients vite config", () => { it("uses hidden sourcemaps for production builds", () => { const config = configFactory({ mode: "production" }); expect(config.build?.sourcemap).toBe("hidden"); }); }); ``` - [ ] **Step 3: Write the failing config test for `apps/external`** Create `apps/external/vite.config.test.ts`: ```typescript import { describe, expect, it } from "vitest"; import configFactory from "./vite.config"; describe("external vite config", () => { it("uses hidden sourcemaps for production builds", () => { const config = configFactory({ mode: "production" }); expect(config.build?.sourcemap).toBe("hidden"); }); }); ``` - [ ] **Step 4: Run the tests to verify they fail** Run: ```bash pnpm --filter @xtablo/main exec vitest run vite.config.test.ts --mode dev pnpm --filter @xtablo/clients exec vitest run vite.config.test.ts --mode test pnpm --filter @xtablo/external exec vitest run vite.config.test.ts --mode test ``` Expected: FAIL because `build.sourcemap` is not configured. - [ ] **Step 5: Implement hidden sourcemaps in all three Vite configs** Update: - `apps/main/vite.config.ts` - `apps/clients/vite.config.ts` - `apps/external/vite.config.ts` Add the same build section in each returned config: ```typescript build: { sourcemap: mode === "test" ? false : "hidden", }, ``` Keep it alongside the existing `plugins`, `server`, `define`, and `test` sections. - [ ] **Step 6: Add env typing for the remaining apps** Create `apps/clients/src/vite-env.d.ts`: ```typescript /// interface ImportMetaEnv { readonly VITE_APP_VERSION: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` Update `apps/external/src/vite-env.d.ts`: ```typescript /// interface ImportMetaEnv { readonly VITE_APP_VERSION: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` - [ ] **Step 7: Run the tests and typechecks** Run: ```bash pnpm --filter @xtablo/main exec vitest run vite.config.test.ts --mode dev pnpm --filter @xtablo/clients exec vitest run vite.config.test.ts --mode test pnpm --filter @xtablo/external exec vitest run vite.config.test.ts --mode test pnpm --filter @xtablo/main typecheck pnpm --filter @xtablo/clients typecheck pnpm --filter @xtablo/external typecheck ``` Expected: PASS. - [ ] **Step 8: Commit** ```bash git add apps/main/vite.config.ts apps/main/vite.config.test.ts apps/clients/vite.config.ts apps/clients/vite.config.test.ts apps/clients/src/vite-env.d.ts apps/external/vite.config.ts apps/external/vite.config.test.ts apps/external/src/vite-env.d.ts git commit -m "build: enable hidden sourcemaps for frontend apps" ``` --- ## Chunk 3: Datadog CI Upload Workflow ### Task 3: Add pinned Datadog CLI and frontend sourcemap workflow **Files:** - Create: `.github/workflows/frontend-sourcemaps.yml` - Modify: `package.json` - [ ] **Step 1: Add the Datadog CLI dependency** Update root `package.json`: ```json "devDependencies": { "@biomejs/biome": "2.2.5", "@datadog/datadog-ci": "^3.21.0", "turbo": "^2.5.8", "typescript": "^5.7.0" } ``` - [ ] **Step 2: Install dependencies and verify lockfile changes** Run: ```bash pnpm install ``` Expected: `pnpm-lock.yaml` updates with `@datadog/datadog-ci`. - [ ] **Step 3: Create the workflow** Create `.github/workflows/frontend-sourcemaps.yml`: ```yaml name: Frontend Sourcemaps on: workflow_dispatch: push: branches: - main - develop jobs: upload-sourcemaps: runs-on: - self-hosted - linux - x64 env: DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} DATADOG_SITE: ${{ secrets.DATADOG_SITE }} RELEASE_VERSION: ${{ github.sha }} steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v5 with: version: 10.19.0 - uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile --child-concurrency=2 - name: Build main run: pnpm --filter @xtablo/main build:prod env: VITE_APP_VERSION: ${{ env.RELEASE_VERSION }} - name: Upload main sourcemaps run: pnpm exec datadog-ci sourcemaps upload apps/main/dist --service xtablo-ui --release-version "$RELEASE_VERSION" --minified-path-prefix https://app.xtablo.com/assets - name: Remove main sourcemaps run: find apps/main/dist -name '*.map' -delete - name: Build clients run: pnpm --filter @xtablo/clients build:prod env: VITE_APP_VERSION: ${{ env.RELEASE_VERSION }} - name: Upload clients sourcemaps run: pnpm exec datadog-ci sourcemaps upload apps/clients/dist --service xtablo-clients --release-version "$RELEASE_VERSION" --minified-path-prefix https://clients.xtablo.com/assets - name: Remove clients sourcemaps run: find apps/clients/dist -name '*.map' -delete - name: Build external run: pnpm --filter @xtablo/external build env: VITE_APP_VERSION: ${{ env.RELEASE_VERSION }} - name: Upload external sourcemaps run: pnpm exec datadog-ci sourcemaps upload apps/external/dist --service xtablo-external --release-version "$RELEASE_VERSION" --minified-path-prefix https://embed.xtablo.com/assets - name: Remove external sourcemaps run: find apps/external/dist -name '*.map' -delete ``` - [ ] **Step 4: Validate the workflow file structure** Run: ```bash rg -n "datadog-ci sourcemaps upload|RELEASE_VERSION|find .*\\.map" .github/workflows/frontend-sourcemaps.yml ``` Expected: one upload and one cleanup step for each frontend app. - [ ] **Step 5: Commit** ```bash git add package.json pnpm-lock.yaml .github/workflows/frontend-sourcemaps.yml git commit -m "ci: upload frontend sourcemaps to Datadog" ``` --- ## Chunk 4: End-To-End Verification ### Task 4: Prove builds emit hidden sourcemaps and cleanup works **Files:** - No new source files - [ ] **Step 1: Build each frontend locally with a release version** Run: ```bash VITE_APP_VERSION=test-sha pnpm --filter @xtablo/main build:prod VITE_APP_VERSION=test-sha pnpm --filter @xtablo/clients build:prod VITE_APP_VERSION=test-sha pnpm --filter @xtablo/external build ``` Expected: each `dist/` contains built assets and `.map` files, but the generated JS bundles do not include public `sourceMappingURL` comments. - [ ] **Step 2: Verify `.map` files exist before cleanup** Run: ```bash find apps/main/dist apps/clients/dist apps/external/dist -name '*.map' | sort ``` Expected: at least one sourcemap per app. - [ ] **Step 3: Simulate CI cleanup locally** Run: ```bash find apps/main/dist apps/clients/dist apps/external/dist -name '*.map' -delete find apps/main/dist apps/clients/dist apps/external/dist -name '*.map' | sort ``` Expected: no output from the second command. - [ ] **Step 4: Run the targeted regression suite** Run: ```bash pnpm --filter @xtablo/main exec vitest run src/lib/rum.test.ts vite.config.test.ts --mode dev pnpm --filter @xtablo/clients exec vitest run vite.config.test.ts --mode test pnpm --filter @xtablo/external exec vitest run vite.config.test.ts --mode test pnpm --filter @xtablo/main typecheck pnpm --filter @xtablo/clients typecheck pnpm --filter @xtablo/external typecheck ``` Expected: PASS. - [ ] **Step 5: Commit** ```bash git status --short ``` Expected: clean worktree before opening the implementation PR.