feat: native debug ID injection and sourcemap upload#543
Merged
Conversation
Replace `npx @sentry/cli sourcemaps inject` with a pure TypeScript implementation in script/debug-id.ts. Zero new dependencies — uses only node:crypto and node:fs/promises. The implementation: - contentToDebugId(): SHA-256 content hash → UUID v4 format, byte-for-byte compatible with @sentry/bundler-plugin-core's stringToUUID (verified against the actual package) - getDebugIdSnippet(): Runtime IIFE that registers debug IDs in globalThis._sentryDebugIds, matching the bundler plugin's snippet - injectDebugId(): Injects snippet + comment into JS, adjusts sourcemap mappings, adds debugId/debug_id fields to sourcemap JSON Key behaviors: - Idempotent: files with existing //# debugId= are skipped - Hashbang-safe: preserves #!/usr/bin/env lines at top of file - Sourcemap-aware: prepends one ; to mappings for line offset - Always injects debug IDs (even without SENTRY_AUTH_TOKEN) so local builds get debug IDs for development/testing Tests: 13 tests (5 property-based via fast-check, 8 unit tests) covering UUID format, determinism, idempotency, hashbang preservation, and sourcemap mutation.
Replace `npx @sentry/cli sourcemaps upload` and `@sentry/esbuild-plugin`
with a native TypeScript implementation. Zero new dependencies.
New modules:
- src/lib/sourcemap/zip.ts: Streaming ZIP builder using node:fs/promises
FileHandle + node:zlib deflateRaw. Writes entries to disk one at a time
— memory usage is O(one compressed entry + central directory metadata).
Uses STORE method for empty files, DEFLATE for everything else.
- src/lib/api/sourcemaps.ts: Implements the Sentry chunk-upload + assemble
protocol (6-step flow: get options → build artifact bundle → chunk + hash
→ assemble → upload missing chunks in parallel → poll until done).
Uses gzip compression for chunks (server only supports gzip, tested
on both US and DE regions). Parallel uploads via p-limit.
Build pipeline changes:
- script/build.ts: Uses uploadSourcemaps() instead of execSync('npx ...')
for the Bun binary build. Always injects debug IDs even without auth.
- script/bundle.ts: Custom sentrySourcemapPlugin esbuild plugin replaces
@sentry/esbuild-plugin. Uses onEnd hook → injectDebugId + uploadSourcemaps.
Dependency removal:
- @sentry/esbuild-plugin removed from devDependencies (and its transitive
@sentry/cli Rust binary dependency is eliminated)
Tests: 10 tests for ZipWriter (5 unit + 2 property-based + 3 binary format)
verifying round-trip integrity, empty file handling, and ZIP structure.
Contributor
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
Contributor
Codecov Results 📊✅ 126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ✅ Patch coverage is 100.00%. Project has 1028 uncovered lines. Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 96.02% 96.03% +0.01%
==========================================
Files 185 186 +1
Lines 25805 25897 +92
Branches 0 0 —
==========================================
+ Hits 24777 24869 +92
- Misses 1028 1028 —
- Partials 0 0 —Generated by Codecov Action |
- Add try/finally in uploadSourcemaps() to always clean up the temp ZIP file, even on error. Extracted uploadArtifactBundle() as inner function to keep the cleanup boundary clean. - Merge overall checksum computation into hashChunks() — maintains a running SHA-1 hasher alongside per-chunk hashing, eliminating the redundant full-file re-read. - Use STORE method for empty files in ZipWriter (DEFLATE of empty input confuses some extractors). - Use options object for uploadArtifactBundle() to stay under the 4-parameter lint limit.
Prevents file handle leak if stat(), read(), or hash operations throw during chunk processing.
- sentrySourcemapPlugin is now always added to the esbuild plugins array. The upload step inside it is already gated on SENTRY_AUTH_TOKEN, so debug IDs are always injected even for local builds without auth — matching the behavior in build.ts. - Wrap chunk upload file handle in try/finally to prevent leaks if read() throws, same pattern as the hashChunks fix.
- ZipWriter.finalize() now wraps central directory writes in try/finally to always close the file handle. - Added ZipWriter.close() for error cleanup when addEntry fails partway through (closes handle without finalizing). - buildArtifactBundle wraps entry writes in try/catch and calls zip.close() on failure. - Moved buildArtifactBundle call inside the try block in uploadSourcemaps so temp file is cleaned up even on build error.
The upload uses the CLI's authenticated fetch via getSdkConfig() instead of a manually-provided token.
…thToken - uploadInjectedSourcemaps now returns a boolean indicating success. .map files are only deleted after a successful upload, preserving them on failure so retrying is possible without a full rebuild. - Remove unused authToken field from UploadOptions.
- Extract urlToBundlePath() helper used by both manifest builder and ZIP entry loop, eliminating divergence risk. - Handle edge case where JS file has a hashbang (#!) but no trailing newline — now inserts a newline separator before the snippet.
Check for error state immediately after the first assemble POST instead of falling through to the chunk upload + poll loop.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
BYK
added a commit
that referenced
this pull request
Mar 24, 2026
…mands (#547) ## Summary Add user-facing CLI commands for sourcemap management, completing the native sourcemap toolchain started in #543. ### New Commands **`sentry sourcemap inject <dir>`** - Scans a directory recursively for JS files (`.js`/`.mjs`/`.cjs`) and their companion `.map` files - Injects Sentry debug IDs into each pair (idempotent — skips files that already have debug IDs) - Supports `--ext` for custom extensions, `--dry-run` for preview - Skips `node_modules` and hidden directories **`sentry sourcemap upload [org/project/]<dir>`** - Uploads sourcemaps to Sentry using the chunk-upload + assemble protocol (from #543) - Supports `--release` and `--url-prefix` flags - Auto-detects org/project from DSN/config or accepts explicit `org/project/` prefix - Both `--json` output and human-readable output supported ### Code Changes - Moved debug ID functions from `script/debug-id.ts` → `src/lib/sourcemap/debug-id.ts` (canonical location) - `script/debug-id.ts` becomes a thin re-export (build scripts still import from here) - Added `src/lib/sourcemap/inject.ts` for directory scanning + injection - Registered `sourcemap` route in `app.ts` with `sourcemaps` plural alias - Generated SKILL.md and reference docs ### Usage Examples ```bash # Inject debug IDs into build output sentry sourcemap inject ./dist # Preview what would be modified sentry sourcemap inject ./dist --dry-run # Upload to Sentry sentry sourcemap upload ./dist sentry sourcemap upload my-org/my-project/./dist --release 1.0.0 ``` Depends on: #543 (merged) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Replace
npx @sentry/cli sourcemaps injectand@sentry/esbuild-pluginwith native TypeScript implementations. Zero new dependencies — only usesnode:crypto,node:fs/promises,node:zlib, and existingp-limit.What changed
Debug ID Injection (
script/debug-id.ts)contentToDebugId(): SHA-256 → UUID v4 (byte-for-byte compatible with@sentry/bundler-plugin-core'sstringToUUID, verified against the actual package)getDebugIdSnippet(): Runtime IIFE that registers debug IDs inglobalThis._sentryDebugIdsinjectDebugId(): Injects snippet + comment into JS, adjusts sourcemap mappings, addsdebugId/debug_idfieldsSENTRY_AUTH_TOKEN)Streaming ZIP Builder (
src/lib/sourcemap/zip.ts)node:fs/promisesFileHandle— only one compressed entry in memory at a timenode:zlib) for compression, STORE for empty filesSourcemap Upload API (
src/lib/api/sourcemaps.ts)p-limitwith server-configured concurrencyBuild Pipeline
script/build.ts: Uses native injection + upload (removedexecSync('npx @sentry/cli ...'))script/bundle.ts: CustomsentrySourcemapPluginesbuild plugin replaces@sentry/esbuild-pluginDependency Removal
@sentry/esbuild-pluginremoved from devDependencies@sentry/cli(Rust binary) dependency is eliminatedTests
Follow-up (PR 3)
User-facing CLI commands (
sentry sourcemap inject/upload) with worker-based parallel processing — tracked separately.