Cross-platform Rust binary that collects debug information files (dSYM, ELF, PE/PDB, Portable PDB, Breakpad, R8/ProGuard mappings, JS source maps), resolves build-environment metadata (VCS, CI provider, iOS dependency graph, Xcode version, Mach-O UUIDs), and uploads symbols to Bugsee. One binary, shelled by thin per-build-system orchestrators (Android Gradle plugin, Xcode Run Script via the iOS SDK's BugseeAgent, fastlane plugin, MSBuild target, Unity post-build hook, Flutter Dart plugin, npm package).
Install the latest release — it downloads and SHA-256-verifies the binary for
your host from download.bugsee.com (no GitHub dependency):
macOS / Linux
curl --proto '=https' --tlsv1.2 -sSfL https://download.bugsee.com/cli/install.sh | shWindows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://download.bugsee.com/cli/install.ps1 | iex"The installer auto-detects OS/arch and installs to /usr/local/bin (or
~/.local/bin) on Unix / %LOCALAPPDATA%\Bugsee\bin on Windows, printing a PATH
hint if needed. Override via env vars: BUGSEE_CLI_VERSION (pin an exact
X.Y.Z), BUGSEE_CLI_INSTALL_DIR (install location), BUGSEE_CLI_BASE_URL
(download root, e.g. an internal mirror). Keep it current afterwards with
bugsee-cli update (same-major only). Other channels: npm (@bugsee/cli —
npm i -D @bugsee/cli && npx bugsee-cli --version), a Homebrew tap, or the
per-build-system bundles — see Distribution.
cargo build --releaseBinary lands at target/release/bugsee-cli. Pinned to stable Rust via rust-toolchain.toml.
The metadata-resolving subcommands print JSON to stdout and exit 0 on parseable failure (empty list / null / empty object) so Python integrators can shell with check=False and rely on the output shape rather than the exit code. (xcode upload-dsyms is deliberately not one of these: it prints nothing to stdout and is designed to exit non-zero so a build phase fails — see its section below.) Hard failures (network, auth, malformed argv) follow the exit-code contract below.
bugsee-cli debug-files upload <paths>... \
--version <X> --build <Y> \
[--type proguard|rust|elf|dsym|pdb|sourcemaps|il2cpp-linemap] \
[--uuid <UUID>] # override / IL2CPP module id(s); comma-separate for multi-ABI \
[--icon <PATH>] # attach launcher icon to the symbol zip \
[--zstd-level N] # 9..=22, default 11; or pass --no-zstd
[--force] # re-upload even if the server already has it (dsym/pdb/rust/il2cpp-linemap/sourcemaps)
[--concurrency N] # sourcemaps only: ceiling on uploads in flight, 1..=32 (default: scaled)
[--allow-empty] # sourcemaps only: "nothing to upload" is success, not exit 10
[--strip-sources-content] # sourcemaps only: upload maps without the embedded source
[--dry-run]
The upload flow itself. ProGuard, Rust, ELF, dSYM, PDB, sourcemap, and Unity IL2CPP line-map types are working; other types are planned via debug-files convert once their wire format stabilises.
Uploads LineNumberMappings.json (+ sibling MethodMap.tsv / il2cppFileRoot.txt) as format il2cpp-linemap, keyed by the IL2CPP module UUID(s) (libil2cpp / UnityFramework). See docs/unity-il2cpp-linenumber-mappings.md.
bugsee-cli debug-files upload path/to/Symbols/LineNumberMappings.json \
--type il2cpp-linemap \
--version 1.2.3 --build 45 \
--uuid <arm64-build-id>,<armeabi-build-id>A Rust project has no single symbol format: it is a .dSYM bundle for Apple targets, a .pdb for *-pc-windows-msvc, and the ELF binary itself (keyed by its GNU build-id) for Linux/Android. --type rust discovers whichever the build produced, so one command covers every target:
bugsee-cli debug-files upload --type rust target/release --version 1.4.0 --build 250- Content-based discovery. Artifacts are classified by container magic, not by host OS, so a cross-compiled
target/x86_64-unknown-linux-gnu/releaseuploads correctly from a Mac. - Cargo intermediates are skipped —
deps/,build/,incremental/,.fingerprint/. Walking them would register a symbol document for every dependency and build script. - Symlinked
.dSYMs are followed. Cargo writes the real bundle intodeps/and leaves a symlink at the profile root; that symlink is the only path to it oncedeps/is skipped. --uuidis rejected. Every Rust debug format carries its own identity (Mach-O UUID / PDB debug id / GNU build-id), and that identity is what the SDK reports for the module at crash time — an override could never match it.
Build configuration is required. A stock cargo build --release emits nothing uploadable, and each format has a setting that, if missing, produces an upload that is accepted and then resolves nothing. The command reports whichever is missing and exits 10 when it finds no symbols at all:
# Cargo.toml
[profile.release]
debug = 1 # emit DWARF at all
split-debuginfo = "packed" # macOS/iOS: collect it into a .dSYM# .cargo/config.toml — Linux/Android only
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-Wl,--build-id"]CI recipe (GitHub Actions):
- run: cargo build --release
- run: |
curl -fsSL https://download.bugsee.com/cli/install.sh | sh
bugsee-cli debug-files upload --type rust target/release \
--version "${{ github.ref_name }}" --build "${{ github.run_number }}"
env:
BUGSEE_APP_TOKEN: ${{ secrets.BUGSEE_APP_TOKEN }}Pass --dry-run to verify discovery and see the preflight warnings without uploading.
Resolves VCS metadata (provider, commit SHA, branch, base branch, PR number, repo) from CI provider env vars (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI, Bitrise, Jenkins, Xcode Cloud, generic CI) or a git fallback. Output shape pinned by tests/cross_language_contract.rs:
{
"provider": "github",
"commit_sha": "abc123…",
"repo": "org/repo",
"branch": "main"
}Absent fields are omitted, not serialised as null. Consumed by the fastlane plugin's BugseeAgent and the iOS SDK's tools.bundle/BugseeAgent (_resolve_vcs_metadata_via_cli).
bugsee-cli ios-deps collect --project-root <PATH> [--product-binary <PATH>] [--max-entries N]
Discovers and parses iOS dependency manifests under <PATH>: Podfile.lock (CocoaPods), Package.resolved (SPM — pure-package and Xcode-managed shapes, with sibling *.xcodeproj / *.xcworkspace probing), Cartfile.resolved (Carthage), and vendored frameworks linked into --product-binary (via /usr/bin/otool). Merges with field-wise url-preference dedup. Output:
{
"entries": [{"id":"library::Alamofire","group":"","name":"Alamofire","direct":true,"type":"library","parents":[],"version":"5.10.0","url":"https://github.com/Alamofire/Alamofire.git"}],
"scope_label": "all",
"truncated": false
}version, scope, url, and parents are emitted only when non-empty (mirrored on the Python side). The url field is load-bearing for OSV SwiftURL ecosystem vuln lookups — drift on the optional-field semantics silently degrades vuln-scan coverage.
Three sub-subcommands; each prints its result to stdout or empty string on unresolved. Consumed by both Python BugseeAgents to eliminate duplicated in-process helpers.
build-env xcode-version— readsXCODE_VERSION_ACTUALenv if set ("1620"→"16.2.0"), else shells/usr/bin/xcodebuild -versionand normalises to 3-part dotted form. Empty string on failure.build-env machine-label— returns<provider>[:<detail>]matching the Android Gradle plugin'sBuildMachineResolvercascade so the dashboard can group iOS + Android builds from the same CI runner.build-env read-plist <plist>— emits a JSON dict ofkey → stringfor all scalar entries in the plist (string, int, real, bool, uint). Dict / array / Data values are silently dropped (scalars-only contract). Returns{}on missing file.
Extract Mach-O UUIDs from a .dSYM bundle directory OR a single Mach-O binary inside one. Replaces the per-Python-BugseeAgent dwarfdump -u shell-outs with one canonical symbolic-debuginfo Mach-O parser.
dsym uuid <path>— JSON array of uppercase hyphenated UUID strings, in archive order:["54D75FB3-747F-387F-8A93-4EA034B1F8CF","8D00647D-E563-30F9-9F17-E1FFCEFF70B4"]
dsym slices <path>— same input, arch-aware output. Used by the iOS SDK'sget_main_executable_uuidto pick thearm64slice from a fat .app binary:[{"uuid":"54D75FB3-747F-387F-8A93-4EA034B1F8CF","arch":"x86_64"}, {"uuid":"8D00647D-E563-30F9-9F17-E1FFCEFF70B4","arch":"arm64"}]
Both return [] (exit 0) on any parseable failure. Uppercase casing matches dwarfdump -u's historic output so cross-tool string comparisons don't break.
Embeds a deterministic, content-derived UUIDv5 debug-id into JS bundles
(//# debugId= + a globalThis._bugseeDebugIds runtime stub) and into the
paired .map (debug_id + debugId). Idempotent. Upload the injected maps
through debug-files upload --type sourcemaps.
The debug-id is derived from the bundle's bytes and its map's, so a map that
changes under byte-identical minified JS still gets a new id (and uploads) —
including when the bundler keeps an already-stamped bundle on disk and re-emits
only its map (webpack [contenthash]): inject re-keys that bundle.
A bundle that already carries a //# debugId= another tool wrote (Rollup 4's
output.sourcemapDebugIds) keeps that id — its map already carries it — and
gains only the _bugseeDebugIds runtime registration, without which the SDK
cannot attach the id to a crash frame.
When the upload scans a directory, maps named as stylesheet or type-declaration
maps (.css.map, .d.ts.map, .d.mts.map, .d.cts.map) are skipped: they
never carry a debug-id. Any other map without one fails the run before anything
is uploaded — inject did not stamp that bundle — and so does a scan that leaves
nothing to upload, unless --allow-empty says otherwise. A map the server
already has is skipped and the batch continues, so rebuilding an app with
unchanged chunks uploads only the changed ones; --force re-uploads it anyway.
Maps upload several at a time. Each map is an independent register + PUT pair, so a build with one map per chunk used to spend its upload time waiting on round-trips. Against a mock with 50 ms of latency:
| maps | serial (--concurrency 1) |
default |
|---|---|---|
| 60 | 7.09 s | 1.31 s |
| 200 | 23.58 s | 4.10 s |
--concurrency N sets a ceiling, not a fixed width: no more uploads run than
there are maps, so a 3-map build runs 3 at a time whatever the ceiling says. Left
unset, the ceiling scales with the batch — one upload per 8 maps, at least 4, at
most 8.
That cap is deliberately modest. On a fast link more streams keep helping (200
maps: 2.75 s at --concurrency 16), but the machine that suffers most from
serial uploads is a CI box on a thin uplink, where the transfer is
bandwidth-bound and extra streams only add latency to each one. Raise it if you
have measured your own link; --concurrency 1 restores strictly sequential
uploads.
An explicit --uuid keys every map in the scan under one id, so it forces
sequential uploads whatever the ceiling says — those registrations must not race
each other. A failed upload stops the batch rather than letting the rest run
into a server that has already refused one.
A --dry-run discovers and packs but sends nothing, so a map that carries no debug-id is reported
rather than fatal there (unkeyed in the completion log) — the whole flow can be previewed on a
freshly built directory, where sourcemaps inject --dry-run has deliberately written nothing yet.
A REAL run still refuses such a map (exit 11): uploading it would register a symbol nothing can find.
--strip-sources-content uploads each map WITHOUT its sourcesContent — including the copies an
indexed map keeps inside sections[].map — for teams who would rather their source did not leave
the build machine. Symbolication still resolves file, line and column;
what is lost is the source snippet shown beside a crash frame. The map on disk is never modified —
only the copy that is uploaded — and the declared hash describes the stripped bytes. A map that
carries no sourcesContent is uploaded byte-for-byte unchanged.
--allow-empty turns "nothing to upload" into success (exit 0) instead of
exit 10 — a monorepo package built without maps, or a framework whose server
output has none, is a legitimate no-op rather than a reason to fail the build.
A path that does not exist is an error regardless (path does not exist: <p>,
exit 10) — including when other paths do hold maps — so a typo or a build that
never ran cannot half-upload a build's symbols.
--concurrency, --allow-empty and --strip-sources-content apply to
--type sourcemaps only, and are rejected (exit 20) for any other type rather
than accepted and ignored.
--exclude <glob> (repeatable) keeps inject out of part of a build output — --exclude '**/node_modules/**' leaves vendored third-party code inside a server bundle untouched, --exclude 'polyfills*.js' skips one file by name.
A pattern is tried against the absolute path, the path relative to the current directory, and the
path relative to each walked root, so dist/vendor/**, vendor/** and an absolute path all work
whether you pass dist, ./dist or the absolute directory. * crosses / (globset's default), so
*.js matches vendor/v.js too — anchor with a leading / or a directory prefix if you do not want
that. An unparseable or empty pattern is a configuration error (exit 20) rather than a silent
"matches nothing", which would rewrite exactly the files you meant to protect. Patterns select
BUNDLES: an excluded bundle's .map is left alone with it, but a pattern matching only .map files
excludes nothing.
A bundle with no source map is still stamped, on purpose. It looks like waste — the id cannot
resolve to a symbol — but a crash frame carrying a debug-id whose map was never uploaded marks the
report missing_sym, which is what prompts you to upload it. An unstamped bundle is silently
unsymbolicated instead. Measured on a stock next build with browser source maps on: 39 JS files,
12 maps, so 27 bundles are stamped without one; that is the case that produces the prompt. Use
--exclude when you would rather those files were not touched at all.
A build that pins its own script hashes is REFUSED (exit 20). Injecting appends bytes to every
.js, so a Subresource Integrity hash the HTML already carries stops matching and the browser
refuses to run the script — measured on a real webpack + webpack-subresource-integrity build in
Chromium 151: before injecting the app ran, after it the entry script was blocked and the page
executed nothing. inject looks for <script integrity src=…> and
<link rel=modulepreload|preload integrity href=…> in the HTML under the paths it was given, and
stops before writing anything. Angular's subresourceIntegrity: true is the same mechanism.
Fix it by stamping BEFORE the hashes are computed, by --exclude-ing the pinned files, or — if your
build recomputes hashes after this runs — with --allow-sri.
The guard only ever refuses over a file this run would really REWRITE, so a re-run that changes
nothing is still a no-op — including on a build stamped once with --allow-sri — and an excluded
file, a stale page pinning a bundle that no longer exists, or a page pinning something outside the
output does not stop it either.
Pages are read from anywhere under the path you give it, plus any sitting directly in that path's
parent — the usual layout is dist/index.html beside dist/assets/*.js, so inject dist/assets
still sees the page that pins those bundles. --dry-run refuses too: the preview of a run that
would refuse is a refusal, and it tells you why. A URL is matched literally first and then by file name, so a publicPath — a CDN origin,
/static/, /_next/ — still resolves to the local bytes it names; the cost of that fallback is that
a third-party script sharing a file name with one of your bundles would be treated as yours.
It cannot see SRI that is not in the emitted HTML: a manifest consumed by a server template
(webpack-assets-manifest with integrity: true), a page rendered at request time (Next.js
experimental.sri), or HTML your build writes outside the directory you point this at. Those builds
still break, so keep --allow-sri off and check a deploy before trusting it.
bugsee-cli sourcemaps inject <paths>... [--exclude <glob>]... [--allow-sri] [--dry-run]
bugsee-cli debug-files upload --type sourcemaps <paths>... --version <v> --build <b> \
[--concurrency N] [--allow-empty] [--strip-sources-content]
Registers a build record — the thing a symbol upload, a size analysis and a crash all hang off — and, optionally, ships the build artefact's bytes.
bugsee-cli upload build --payload-json <path> \
[--artifact <.aab|.apk|.ipa>] # omit to REGISTER ONLY, shipping no bytes \
[--mapping <mapping.txt>] # needs --artifact (it rides inside the ZIP) \
[--deps <deps.json>] [--timings <timings.json>] \
[--chunked] # needs --artifact \
[--dry-run [--out <zip>]] # --out needs --artifact
--payload-json is the registration body, written by the producer (the Gradle plugin, the Xcode
post-action, a bundler plugin) and passed through verbatim apart from two fields the CLI injects:
request_artifact_upload, and request_build_info_upload when a sidecar is present.
Without --artifact the build is registered and nothing is packed or sent. That is the normal
case wherever size analysis is not enabled — and the only case a web build can express, having no
single artefact to ship. --deps/--timings still travel, because the build-info bundle is a
separate upload from the artefact. The flags that only describe how artefact bytes move (--mapping,
--chunked, --out) are rejected with exit 20 rather than ignored: dropping a --mapping silently
would cost symbolication.
Dedup is server-side on the payload's uuid (replace-then-create), which is why the registration POST
is retried on a transport error but never on a 5xx.
Uploads dSYMs from an Xcode Run Script build phase, with none of the
BUGSEE_BUILD_INFO_* gating — it neither registers a build nor uploads
build-info, so it is safe to run on every build. This is the shape a
React Native / Flutter config plugin can generate (withXcodeProject edits
project.pbxproj), as opposed to the scheme post-action, which means editing
.xcscheme XML.
# Run Script build phase, after "Embed Frameworks"
"$SRCROOT/path/to/bugsee-cli" xcode upload-dsyms --app-token "$BUGSEE_APP_TOKEN"It reads DWARF_DSYM_FOLDER_PATH, which Xcode sets in every Run Script phase,
and falls back to <ARCHIVE_PATH>/dSYMs.
A failure fails the build, on purpose. A build phase that swallows errors means symbolication silently stops working and nobody notices until a crash report is unreadable:
| Situation | Exit | Build |
|---|---|---|
| Uploaded, or nothing to upload | 0 |
continues |
| A bundle could not be read or packed | 10 / 11 |
fails |
| Missing / rejected app token, or a refused flag combination from the environment | 20 / 21 |
fails |
| Server error / network failure | 30 / 31 |
fails |
"Nothing to upload" — no dSYM folder, or a folder with no .dSYM bundles — is a
success, not a failure: a target that produces no debug symbols is a normal
state. Only real problems fail the build.
Whether a failure breaks the build and whether the upload detaches are
independent, each with an on/off pair (--fail / --no-fail,
--background / --no-background) and a matching env var
(BUGSEE_DSYM_UPLOAD_NO_FAIL, BUGSEE_DSYM_UPLOAD_BACKGROUND). A flag
overrides its env var, so a job exporting one globally can still opt a single
invocation back.
| fails the build | waits for the upload | |
|---|---|---|
| (default) | yes | yes |
--no-fail |
no | no — detaches |
--no-fail --no-background |
no | yes |
--fail --background |
refused — exit 2 (flags) or 20 (env) |
— |
--no-fail --no-background is usually what CI wants: never break the build, but
still wait, so a runner tearing down its process tree the moment xcodebuild
returns cannot kill the upload mid-flight.
--fail --background is refused rather than honoured, because a detached
process's exit code reaches nobody — "fail the build" would silently do nothing.
A detached run's warnings also go to $PROJECT_TEMP_DIR/bugsee-cli.log rather
than the Xcode build log, so --no-background is how you keep them visible. On
Windows there is no fork and every run is synchronous.
Xcode 15+:
ENABLE_USER_SCRIPT_SANDBOXINGdefaults toYES, which stops a build phase reading the dSYM folder. Set it toNOon the target, or declare the folder in the phase's input file lists. The scheme post-action is unaffected.
For the full build-publish flow — build registration, build-info, size checks —
use xcode post-action instead; see bugsee-cli xcode post-action --help.
bugsee-cli debug-files convert <input> --to bmf|bsf --output <path>
--endpoint (env BUGSEE_ENDPOINT), --app-token (env BUGSEE_APP_TOKEN). Both global so every subcommand inherits the same BUGSEE_ENDPOINT override path the per-build-system integrators already standardise on. Only the upload-flavoured subcommands (debug-files upload, upload build, upload build-info, xcode post-action, xcode upload-dsyms) actually consume these values; metadata-resolving subcommands (vcs-metadata, ios-deps, build-env, dsym, sourcemaps inject) do no network I/O and ignore them.
Multi-word subcommand names are hyphenated (vcs-metadata, ios-deps, build-env, debug-files). Single-word names are bare (dsym, sourcemaps). Sub-subcommands keep the hyphenation pattern (debug-files upload, ios-deps collect, build-env xcode-version). This is the same scheme cargo, kubectl, and gh follow, and the Python integrators consume the names verbatim — renaming any subcommand is a wire-shape break under the compatibility policy below.
bugsee-cli --helpandbugsee-cli <subcommand> --helpprint the full surface (clap-derived; covers every flag, env-var alias, and subcommand).bugsee-cli --versionprints the SemVer. Integrators that bind to a specific output shape should pin against this — see the wire-shape compatibility policy.- Sample output for every subcommand lives in this README's Subcommands section. The pinned reference vectors used by the cross-language integration tests live under
tests/fixtures/.
There is no man bugsee-cli page today; the README and built-in --help cover the same ground. A future docs site at docs.bugsee.com/cli/ is planned but not shipped.
Each subcommand's stdout JSON shape is the source of truth for at least one Python script outside this repo (the fastlane plugin's BugseeAgent and/or the iOS SDK's tools.bundle/BugseeAgent). Drift = silent breakage on real builds.
Compatibility rules for releasing changes:
- Backward-compatible (no version coordination required). Adding a new subcommand. Adding a new optional output field with
#[serde(skip_serializing_if = "Option::is_none")]. - Backward-compatible-with-care (announce in CHANGELOG). Adding a new required output field (only if every consumer already tolerates unknown fields — verify in the consumer tests). Adding a new env var the resolver checks.
- Breaking. Renaming a field. Removing a field. Changing a field's type or casing. Changing
[]↔null/{}↔nullsemantics. Changing exit-code semantics. Any of these requires a major version bump and a coordinated landing across the fastlane plugin (fastlane-plugin-bugsee/BugseeAgent) and the iOS SDK (tools.bundle/BugseeAgent).
Cross-language reference vectors live in tests/cross_language_contract.rs + tests/fixtures/. Each test runs the compiled binary against a checked-in fixture and pins the stdout JSON shape end-to-end. Mirror tests on the Python side (scripts/test_bugsee_agent_*_cli.py in the iOS SDK; test/test_bugsee_cli_migration.py in the fastlane plugin) feed canned JSON in the same shape — if the Rust output ever drifts, both sides break.
Stable. Integrators (Gradle plugin, MSBuild target, fastlane plugin, npm wrapper) use these codes to decide whether to fall back to their in-language uploader during the dual-path rollout phase.
| Code | Meaning | Caller should fall back? |
|---|---|---|
| 0 | Success (uploaded, or server reports already-exists, or resolver returned empty/null output). | n/a |
| 1 | Unexpected / unhandled error. | yes |
| 2 | Usage / argv error (likely a plugin↔CLI version mismatch). | yes |
| 10–19 | Input / discovery problems (file not found, unparseable format). | no |
| 20–29 | Configuration problems (bad token, invalid flags). | no |
| 30–39 | Upload problems (network, server 4xx/5xx). | no |
| 40 | Build gate failed deliberately (e.g. size-check FAIL). | no |
| 41+ | Reserved. | no |
The fallback rule: codes ≤ 2 mean the CLI never got a fair chance to run; codes ≥ 10 are substantive failures the in-language uploader would hit the same way. See src/exit_code.rs for the source-of-truth enum.
Note: subcommands that emit JSON (vcs-metadata, ios-deps collect, build-env *, dsym *) return exit 0 even when no useful result is found — callers distinguish "no result" from "tool error" by checking the JSON shape (empty list / empty object / specific field absence), not the exit code. This lets Python integrators use check=False + json.loads(stdout) without branching on returncode.
Every metadata POST sets X-Bugsee-Uploader: cli. The in-language fallback uploaders send a different value of the same header (e.g. kotlin-fallback-cli-exec-failed) so the backend can count CLI-vs-fallback usage without touching customer code. The header is not added to the presigned S3 PUT — that signature is bound to a specific header set, and S3 would reject extras with SignatureDoesNotMatch.
| Channel | Used by |
|---|---|
npm @bugsee/cli — per-platform optionalDependencies |
RN, Cordova, Capacitor, web |
npm @bugsee/bugsee-cli — single package, postinstall downloader |
legacy alias for the above |
Maven Central com.bugsee:bugsee-cli jar bundling binaries |
Android Gradle plugin |
NuGet Bugsee.CLI bundle |
.NET MAUI MSBuild target |
UPM com.bugsee.cli package |
Unity Editor post-build |
| CDN download + SHA-256 checksum on first use | Flutter (Dart plugin), fastlane plugin (resolveCli) |
| Homebrew tap + curl installer | iOS / generic CI |
Target platforms: macOS arm64 + x86_64, Linux x86_64 + aarch64 (glibc; musl if Alpine CI demand exists), Windows x86_64 + arm64. (Windows arm64 builds natively on a windows-11-arm runner — see #20.)
Both ship the same binary at the same version, and both are published from
.github/workflows/npm-publish.yml off the same release. They differ in how
the binary reaches node_modules:
@bugsee/cli — prefer this one. The binary lives in six per-platform
packages (@bugsee/cli-darwin-arm64, -darwin-x64, -linux-arm64,
-linux-x64, -win32-x64, -win32-arm64), each declaring os/cpu and
pinned to the exact version by the front package's optionalDependencies. npm
resolves exactly one. Nothing is downloaded at install time, so it works under
--ignore-scripts, under a lockfile-pinned CI install, and offline from a warm
cache — which is why it is the right default for a JS toolchain. A
postinstall fallback covers the cases optional dependencies cannot
(--no-optional, a mirror carrying only the front package): it fetches the
same release archive and SHA-256-verifies it, and never fails the install —
it warns and exits 0, leaving the error to surface only if bugsee-cli is
actually invoked. It also exports binaryPath() for spawning the binary
directly. Sources: npm/.
@bugsee/bugsee-cli — the cargo-dist package, kept as a working alias.
One package, no binary inside; its postinstall runs install.js, which
downloads the archive for the host on every fresh install. That means it needs
network at install time and does nothing at all under --ignore-scripts.
Generated by cargo-dist from installers = [..., "npm"]; unchanged, still
published, still supported. New integrations should use @bugsee/cli.
| Doc | Topic |
|---|---|
docs/upload-unification.md |
Build-info bundle, chunked upload, cross-platform producers |
docs/upload-unification-activation.md |
Rollout / activation |
docs/unity-il2cpp-linenumber-mappings.md |
Unity IL2CPP LineNumberMappings.json / MethodMap design (format il2cpp-linemap) |
src/
main.rs entry point
cli/ clap command tree
debug_files.rs debug-files upload / convert
sourcemaps.rs sourcemaps inject
vcs_metadata.rs vcs-metadata
ios_deps.rs ios-deps collect
build_env.rs build-env xcode-version / machine-label / read-plist
dsym.rs dsym uuid / dsym slices
symbols/ format-specific discovery + identification (dsym, elf, pdb, portable_pdb, breakpad, proguard, jvm)
compress/ Zstd-in-ZIP packaging
upload/
chunked.rs modern chunked protocol (default)
presigned.rs legacy two-stage POST → PUT
inject/ JS source-map debug-ID injection
error.rs
exit_code.rs source-of-truth enum for the exit-code contract above
tests/
cross_language_contract.rs integration tests against the compiled binary
fixtures/ checked-in JSON / lockfile / plist fixtures