Skip to content

[SDK-778] Support React Native bundles and source maps in upload-sourcemaps - #43

Merged
brianr merged 5 commits into
masterfrom
sdk-778-react-native-sourcemaps
Oct 14, 2026
Merged

brianr merged 5 commits into
masterfrom
sdk-778-react-native-sourcemaps

Conversation

@devtools-agent

@devtools-agent devtools-agent Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #16. Linear: SDK-778.

What changed

upload-sourcemaps couldn't handle React Native builds. It only globbed <path>/**/*.js and only found maps through a //# sourceMappingURL directive. RN bundles are named main.jsbundle (iOS) and index.android.bundle (Android), and Hermes bytecode bundles don't have a directive at all. That produced the two outputs in #16: [files:] [] when given a file, and map not found when given a directory. The reporter's map is sourcemap.ios.js, the --sourcemap-output name used in Rollbar's React Native docs. That name doesn't end in .map and doesn't say which bundle the map belongs to.

In src/sourcemaps/scanner.js:

  • <path> can now be a single bundle file or a single .map file, not only a directory. A .map is uploaded for the bundle it's named after: index.android.bundle.map goes to minified_url = <url-prefix>index.android.bundle. This handles Hermes, where the RN Gradle plugin writes the map to build/generated/sourcemaps/react/release/ and the bundle somewhere else.
  • A single-file <path> also counts as a source map when its content is one (a JSON object with version and mappings/sections), so sourcemap.ios.js from the docs is recognized. The name doesn't say which bundle the map is for, and Metro writes no file field, so the new --bundle-name option sets it: upload-sourcemaps sourcemap.ios.js --bundle-name main.jsbundle --url-prefix http://reactnativehost uploads for http://reactnativehost/main.jsbundle. Without the flag, the CLI reports an error naming it instead of map not found. --bundle-name also overrides the name derived from a .map file and is refused when <path> is a directory. A source map found during a directory scan, which is the second command in Is react native supported? Sourcemaps not found #16, warns that it should be passed as <path> with --bundle-name.
  • Directory scans also pick up .jsbundle and .bundle files (nodir, so macOS *.bundle directories are ignored). An RN bundle found in a subdirectory uploads under its bundle name, not its relative path, because rollbar.js's default rewriteFilenamePatterns strip everything before the bundle name. When more than one validated map would upload under the same name, which a whole Android build/ folder can produce, the CLI warns.
  • RN bundles (.jsbundle/.bundle) with no directive fall back to <bundle>.map in the same directory. I kept this fallback away from .js on purpose so web builds behave exactly as before. The angular9 fixture has directive-less .js files with adjacent maps, and its tests still expect those not to be uploaded.
  • mappedFile, used by the --next zip, is now resolved relative to the bundle's own directory. It used to be resolved relative to the scan root, which was wrong for nested files (for example build/static/js/x.js gave build/x.js.map) and would have been wrong for a single-file <path>. An absolute sourceMappingURL is kept as is, and the map is validated from that same mappedFile. Before, an absolute directive validated but was then left out of the zip.
  • If nothing is found at <path>, the CLI now warns instead of silently printing [files:] [].

In src/sourcemaps/signed-url-uploader.js, the --next zip now names each map <fileName>.map instead of the map's basename. Its consumer, ingestion's SourceMapProcessor._map_files, uploads each entry for <baseUrl><entry name without .map>. So --next now gives every map the same minified_url as the standard upload, including RN bundles found in subdirectories, nested web build paths (static/js/x.js, which used to be flattened to x.js), and maps named differently from their bundle.

The README has a new React Native section: use --url-prefix http://reactnativehost/, and either name the map after the bundle or pass --bundle-name. It includes the exact command for the docs' sourcemap.ios.js / sourcemap.android.js outputs. --next is documented as uploading for the same URLs.

Server side

Nothing to change:

  • rollbar-react-native rewrites frames to http://reactnativehost/<bundle> (rollbar.js src/react-native/transforms.js), and mox's frame_can_be_source_mapped explicitly accepts reactnativehost frames whatever the extension (mox/lib/source_map.py).
  • POST /api/1/sourcemap (moxapi src/routes/sourcemap.js) only needs minified_url, version and source_map. The bundle itself is never uploaded.
  • Size: production nginx allows 100M on /api/1/sourcemap, and moxapi's multipart parser doesn't cap file parts. The reporter's 18 MB map is fine. The client-side axios body cap is already Infinity as of 0.3.0.

Validation

  • npm test: 69 passing (47 before), 1 pending (pre-existing). npm run lint is clean.
  • New fixtures in test/fixtures/builds/react-native/: an iOS main.jsbundle with a directive, and a Hermes-style index.android.bundle with no directive and an adjacent map. test/fixtures/builds/react-native-sourcemap/sourcemap.ios.js is a valid map named as in the docs, with no file field.
  • --bundle-name tests: the scanner with and without the flag, overriding a .map name, the directory-scan warning, refusing a directory, an uploader test asserting minified_url is http://reactnativehost/main.jsbundle with the Is react native supported? Sourcemaps not found #16 prefix (no trailing slash), and two CLI dry-run tests. All 8 fail without the change. I also ran both Is react native supported? Sourcemaps not found #16 commands by hand against a sourcemap.ios.js: before the change, both print map not found; after it, they print the new error and warning, and --bundle-name main.jsbundle validates the map.
  • New tests cover scanning an RN directory, a single bundle file, a single .map file, nested and absolute-directive mappedFile resolution, and a missing path. Uploader tests assert minified_url is http://reactnativehost/main.jsbundle / http://reactnativehost/index.android.bundle, for single files and for a directory scan with the bundles in subdirectories. A unit test covers the duplicate-name warning. There's also a CLI dry-run test with a single bundle file, which matches the exact valid line. I checked that it fails if validation fails.
  • I ran the CLI in dry-run mode against the fixtures (map file, RN directory, missing path) and checked the output by hand.
  • zipFiles tests cover zip entry names for an RN directory scan, the nested react16 build, and a map named differently from its bundle. The last two fail on the old basename naming.
  • I ran ingestion's real _unzip_source_map/_get_manifest/_map_files on zips the CLI built from the fixtures. They produced //reactnativehost/index.android.bundle, //reactnativehost/main.jsbundle and //example.com/static/js/<chunk>.js.
  • Not tested: a real upload to api.rollbar.com, standard or signed-URL.

🤖 Generated with Claude Code

upload-sourcemaps only scanned a directory for *.js files and only found
maps through a sourceMappingURL directive. React Native bundles are
main.jsbundle / index.android.bundle, and Hermes bytecode bundles carry no
directive, so RN users got "[files:] []" or "map not found" (#16).

- <path> can now be a single bundle file or a single .map file; a .map is
  uploaded for the bundle it is named after (main.jsbundle.map ->
  main.jsbundle), which covers Hermes builds where the map lives in a
  different directory from the bundle.
- Directory scans also pick up .jsbundle and .bundle files.
- RN bundles without a directive fall back to <bundle>.map next to them.
- mappedFile (used by --next) now resolves relative to the bundle's own
  directory instead of the scan root, which was wrong for nested files.
- Warn when nothing is found at <path>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 13, 2026

Copy link
Copy Markdown

SDK-778

@rollbar-circleci-machine rollbar-circleci-machine left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Review (openai, openai-astra)

Review of #43: React Native bundles and maps in upload-sourcemaps

The scanner changes are small and hold up against the real code:

  • Single-file and .map targets. targetIsFile and basePath (src/sourcemaps/scanner.js:19-21) give a bare bundle or map name as fileName. A .map target reuses itself as the map (scanner.js:55-57). Uploader.minifiedUrl (src/sourcemaps/uploader.js:72-74) then produces http://reactnativehost/<bundle> as the new uploader tests expect.
  • mappedFile. It is now built from the bundle's own directory (scanner.js:72). That matches how loadMapData reads the map (scanner.js:82-83), so the --next zip (src/sourcemaps/signed-url-uploader.js:33) now finds maps for nested files instead of failing to read them.
  • nodir. The installed glob is 7.2.3 (node_modules/glob/package.json:5, from the ^7.1.6 range in package.json:17), which supports nodir (node_modules/glob/README.md:264). As a side benefit, directories whose names end in .js are no longer handed to parseMapPath.
  • sourceMappingURL flag. It is now only true when a directive was actually found. The only readers are tests (test/sourcemaps/uploader.test.js:154, test/sourcemaps/scanner.test.js).
  • Web builds. The adjacent-map fallback only applies to .jsbundle and .bundle (scanner.js:63-65), so .js behaviour is unchanged as the description says.

Two low-severity findings:

  1. In directory mode, a React Native bundle keeps its subdirectory in the uploaded URL. That doesn't match the http://reactnativehost/<bundle name> frames the new README section describes.
  2. The new CLI test's 'valid' assertion also matches the map not valid output.

Not verified. These claims come from the description and refer to repos that aren't checked out here:

  • rollbar-react-native rewrites frames to reactnativehost.
  • mox/moxapi accept the upload.
  • nginx limits.

The description also says a real upload and --next with React Native maps were not tested. Nothing in the diff shows that the tests or lint pass.

Outside the diff: README line 26 still says "Upload source maps recursively from a directory." That is now incomplete, since path can also be a file.

Comment thread README.md Resolved
Comment thread test/sourcemaps.test.js Resolved
React Native frames are http://reactnativehost/<bundle name> with no build
directory (rollbar.js rewriteFilenamePatterns), so a bundle found in a
subdirectory of <path> now uploads under its basename instead of its
relative path. Warn when more than one validated map would upload under
the same name, which a whole Android build folder can produce.

Also make the CLI test assert the exact `valid` line, since `map not valid`
contains the same substring, and update the README description of <path>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@rollbar-circleci-machine rollbar-circleci-machine left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Review (openai, openai-astra)

Review: React Native bundle and source map support in upload-sourcemaps

I read the changed src/sourcemaps/scanner.js and the code that consumes its output. src/sourcemaps/command.js:54-85 passes scanner.files to Uploader, which builds minified_url from fileName (src/sourcemaps/uploader.js:63-74), or to SignedUrlUploader, which zips mappedFile (src/sourcemaps/signed-url-uploader.js:30-37). I also read the new fixtures and tests.

What checks out

  • The single-file target (scanner.js:19-21, :200-203) and the .map → bundle-name mapping (scanner.js:73-75, :218) give fileName values of main.jsbundle and index.android.bundle. With new URL(fileName, baseUrl) (uploader.js:73) these become http://reactnativehost/<bundle>, whether or not the prefix has a trailing slash.
  • The adjacent-map fallback only runs for .jsbundle/.bundle (scanner.js:81-84), so .js behaves as before. The angular9 assertions that directive-less .js files stay unmapped are unchanged (test/sourcemaps/scanner.test.js:92-94).
  • nodir is supported by the pinned glob 7.2.3 (node_modules/glob/package.json:5, node_modules/glob/common.js:68). It also stops a directory named *.js from reaching readFileSync.
  • mappedFile is now resolved from the bundle's own directory (scanner.js:90), which fixes nested web builds in the --next zip. zipFiles stores entries by basename (signed-url-uploader.js:33), so existing zip behaviour for top-level maps is unchanged.
  • warnDuplicateNames only counts validated files, and only React Native bundles are flattened, so web builds can't trigger it by accident.
  • The CLI dry-run test's trimmed main.jsbundle.map / valid lines match what Output.format produces for an empty label (src/common/output.js:39-50).

Finding (low): mappedFile uses path.join while loadMapData reads the map with path.resolve. A sourceMappingURL that is an absolute path validates, but is then left out of the --next zip. This predates the PR, but the PR rewrote this exact line.

Not verified

  • The server-side claims in the description: mox frame_can_be_source_mapped accepting reactnativehost frames, the moxapi /api/1/sourcemap fields, and the nginx size limits. Those repositories are not checked out here.
  • The claim that rollbar-react-native rewrites frames to http://reactnativehost/<bundle>.
  • The description references issue #16 and ticket SDK-778, not a dependent pull request, so there is no dependency to confirm.
  • I did not run the test suite or lint. The stated "57 passing" is the author's claim, not something this diff proves.

Minor note (no finding): a missing path still exits 0 after the new warning. CI jobs that point at the wrong path will still pass, just more visibly.

Comment thread src/sourcemaps/scanner.js Resolved
mappedFile was built with path.join while loadMapData read the map with
path.resolve, so an absolute sourceMappingURL validated but was then left
out of the --next zip. mappedFile now keeps an absolute directive as is,
and loadMapData reads mappedFile, so the two can't drift again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@rollbar-circleci-machine rollbar-circleci-machine left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Review (openai, openai-astra)

What I checked: src/sourcemaps/scanner.js against its callers (src/sourcemaps/command.js, uploader.js, signed-url-uploader.js, requester.js), plus the new tests and fixtures. I did not run the tests, so the stated 58 passing is not confirmed here.

  • Map path (mappedFile): the code is correct. mappedFile is now resolved relative to the bundle's own directory (scanner.js:91), and loadMapData reads that same path (scanner.js:102). For relative directives this matches the old path.resolve(dirname, mapPathName) read. Absolute directives behave as before during validation. The --next zip (signed-url-uploader.js:33) now gets the real nested map path, where before it got targetPath/<basename>, a file that did not exist.
  • Directory and single-file scans: glob.sync(..., { nodir: true }) is supported by the pinned glob 7.2.3 (node_modules/glob/common.js:68). For a single-file <path>, the base directory is the file's own directory (scanner.js:21), so fileName is the basename. A .map target drops the .map suffix (scanner.js:218). React Native bundles found in subdirectories upload under their basename (scanner.js:222-224).
  • Web builds: the adjacent-map fallback is limited to .jsbundle/.bundle (scanner.js:81), so web .js behaviour is unchanged. The existing angular9 and react16 assertions still apply.
  • Tests: the new tests' expected paths agree with how path.join normalises ./test/... paths. CI runs Node 18 and later (.github/workflows/node.js.yml:36-45), so fs.rmSync in the new test is available.
  • Not verified: the server-side claims (that rollbar-react-native rewrites frames to reactnativehost, mox's frame matching, moxapi's size limits) refer to code that is not checked out here.
  • One gap: the PR description says the README tells React Native users to use the standard upload rather than --next. The new React Native section does not say that (see finding).

Comment thread README.md Resolved
The server side of --next (ingestion's SourceMapProcessor._map_files)
uploads each zip entry for <baseUrl><entry name without .map>. Entries
were named after the map file's basename, so a React Native bundle's
bundle-name rule, a nested web build's subdirectory, and a map named
differently from its bundle never reached that path. Name each entry
<fileName>.map so --next uploads every map for the same minified_url as
the standard upload, and say so in the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rollbar-circleci-machine

Copy link
Copy Markdown

AI Agent Review (openai, openai-astra)

Summary

This PR lets upload-sourcemaps accept a single bundle file or a single .map file. Directory scans now also pick up .jsbundle and .bundle files, and React Native bundles without a sourceMappingURL directive fall back to the <bundle>.map next to them. It also fixes mappedFile resolution for nested files, and --next zip entries are now named <fileName>.map. I read the changed code in src/sourcemaps/scanner.js and src/sourcemaps/signed-url-uploader.js, its callers in src/sourcemaps/command.js and src/sourcemaps/uploader.js, and the pinned dependencies it relies on. I found no correctness defects that I could confirm.

What I verified

  • Zip entry naming. zipFile.addLocalFile(file.mappedFile, '', name) matches adm-zip 0.6.1's signature addLocalFile(localPath, zipPath, zipName, comment) (node_modules/adm-zip/adm-zip.js:309-318, version at node_modules/adm-zip/package.json:3). adm-zip keeps nested entry names like static/js/x.js.map (node_modules/adm-zip/util/utils.js:396-401).
  • Directory scan. glob 7.2.3 supports the nodir option (node_modules/glob/common.js:68, node_modules/glob/package.json:5), so macOS *.bundle directories are skipped. As a side benefit, a directory named *.js no longer reaches parseMapPath and fails with EISDIR.
  • Map resolution for the standard upload is unchanged. loadMapData now reads file.mappedFile, which is path.join(dirname(filePathName), mapPath), or mapPath itself when absolute (scanner.js:91,102). That is the same file the old path.resolve(dirname, mapPathName) read. fileName for .js files is still relative to the scan root (scanner.js:218), so standard-upload minified_urls for web builds don't change.
  • Existing CLI test. The uploads react project test expects exactly 14 output lines (test/sourcemaps.test.js:25). That still holds: the new warnings fire only for an empty scan or duplicate validated names, and neither happens with the react16 fixture.
  • Test and lint compatibility. fs.rmSync in the new test is fine on the CI matrix, which starts at Node 18 (.github/workflows/node.js.yml:36). The new syntax works with ecmaVersion: 8 (.eslintrc:8). I did not run the tests or lint; the description's "61 passing" is the author's claim.

Notes (not findings)

  • Behaviour change for existing --next users with nested builds. Zip entries now carry the relative path, so static/js/x.js.map replaces x.js.map (signed-url-uploader.js:36). Anyone who worked around the old flattening by adding the subdirectory to --url-prefix will now upload to different URLs. The description says this is intended, but it's worth a changelog or release note.
  • Server-side claims I could not verify. The description says ingestion's SourceMapProcessor._map_files uploads each entry to <baseUrl><entry name without .map> and handles entry names that contain directories. It also cites mox's frame_can_be_source_mapped, moxapi's sourcemap route and the nginx size limits. None of those repos is checked out, so none of this was confirmed. The README lines saying --next uploads for the same URLs as the standard upload depend on that server behaviour. They also only hold when --url-prefix ends in /: the standard uploader resolves names with new URL(fileName, baseUrl) (src/sourcemaps/uploader.js:73), which replaces the last path segment when there is no trailing slash.
  • Nit: duplicate-name lookup. warnDuplicateNames keys a plain {} by fileName (scanner.js:55-59). A validated single map named constructor.map or __proto__.map would hit a prototype property, and .push would throw. That's very unlikely, but Object.create(null) or a Map would rule it out.
  • Nit: missing-path message. For a path that doesn't exist, the new warning says no .js/.jsbundle/.bundle files were found (scanner.js:34-35), not that the path is missing. Not wrong, just less direct.

Comment thread src/sourcemaps/scanner.js Resolved
Rollbar's React Native docs generate the map with
`react-native bundle --sourcemap-output sourcemap.ios.js`, which is the
exact input in #16. The scanner only recognized a standalone map by a
`.map` extension, so that file was treated as a bundle and reported
`map not found`.

A single-file <path> is now recognized as a source map by its content
as well as by `.map`. Its name can't say which bundle it belongs to, so
the new `--bundle-name` option sets the upload name (minified_url is
<url-prefix><bundle-name>). Without it the CLI reports an error naming
the option. A source map found during a directory scan gets a warning
that points to the same flow instead of `map not found`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rollbar-circleci-machine

Copy link
Copy Markdown

AI Agent Review (openai, openai-astra)

Review: React Native bundles and source maps in upload-sourcemaps

I found no defects to report. I read every changed source file in the checkout, plus their callers and the dependencies they call.

What I checked

  • Scanner, src/sourcemaps/scanner.js:
    • targetIsFile and basePath are set in the constructor (L19-21). initFile names the upload from the .map name, uses the bare name for RN bundles, and applies --bundle-name (L232-250).
    • extractMapPath handles three cases: a map passed as <path>, the adjacent-map fallback for .jsbundle/.bundle files only, and a warning for a source map found in a directory (L73-111).
    • An absolute sourceMappingURL is kept as is (L107). loadMapData now reads mappedFile (L118), which behaves the same as the old path.resolve(dirname, mapPathName) read.
    • Directive-less .js files still fall through to map not found, so the existing react16/angular9 expectations and the 14-line CLI dry-run count still hold.
    • A source map named without a .map extension and without --bundle-name fails to validate, and Uploader.skip skips it because fileName is null (src/sourcemaps/uploader.js:77).
  • Dependencies:
    • glob.sync(..., { nodir: true }) is supported by the installed glob 7.2.3 (node_modules/glob/common.js:68).
    • The pinned adm-zip (^0.6.1, package.json:13) has the signature addLocalFile(localPath, zipPath, zipName), and an empty zipPath plus an explicit zipName gives entry <fileName>.map (node_modules/adm-zip/adm-zip.js:309-318).
    • A second entry with the same name overwrites the first (adm-zip.js:605-647). That matches the new warnDuplicateNames message that only one map can apply.
  • --next: zip entries were previously built from path.join(targetPath, mapPath). For nested web builds that path didn't exist, so those maps were left out of the zip. The new naming fixes this.

Not verified (claims in the description I could not check)

  • The server side (ingestion SourceMapProcessor._map_files, mox frame_can_be_source_mapped, moxapi /api/1/sourcemap limits) is not checked out here, so the description's server-side claims are unverified.
    • In particular, the standard upload builds minified_url with new URL(fileName, baseUrl) (src/sourcemaps/uploader.js:73). The description says the zip consumer builds <baseUrl><entry name>. If the consumer plainly concatenates, the README's "Each map is uploaded for the same URL as without --next" only holds when --url-prefix ends in /.
    • The new uploader test uses the Is react native supported? Sourcemaps not found #16 prefix http://reactnativehost with no trailing slash, and only for the standard upload.
    • Worth confirming against ingestion before relying on that README sentence.
  • I can't confirm that the tests pass; the diff doesn't show a CI run.

Minor observations (not findings)

  • --bundle-name with a directory only prints a yellow warning and scans nothing. The process still exits 0, and with --next outside a dry run it still requests a signed URL and uploads a zip holding only the manifest. An empty directory already behaved this way before this PR, and the CLI never sets an exit code anywhere.
  • Any <name>.map passed as <path> uploads as <name>. So pointing at a Hermes intermediate such as index.android.bundle.packager.map would upload silently under index.android.bundle.packager. The README points users at the composed map in build/generated/sourcemaps/react/release/, so this is user error, not a code defect.

@brianr
brianr merged commit 2137d25 into master Oct 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is react native supported? Sourcemaps not found

4 participants