diff --git a/cspell.json b/cspell.json index 3edbc89..943ab37 100644 --- a/cspell.json +++ b/cspell.json @@ -222,6 +222,9 @@ "MSVC", "rustls", "RUSTSEC", + "jsbundle", + "rustflags", + "sandboxing", "libil" ] } \ No newline at end of file diff --git a/docs/cli/commands.md b/docs/cli/commands.md index e5bddfb..d5c153b 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -38,6 +38,7 @@ See [Configuration](/cli/configuration/) for details. | `upload build-info` | Upload per-build metadata sidecars in one bundle. | [Builds](/cli/builds/#upload-build-info) | | `pack` | Build the normalized upload ZIP locally, without uploading. | [Builds](/cli/builds/#pack) | | `xcode post-action` | Run the whole iOS build-publish flow from an Xcode post-action. | [iOS build publishing](/cli/xcode/) | +| `xcode upload-dsyms` | Upload dSYMs from an Xcode Run Script build phase, with no build registration. | [iOS build publishing](/cli/xcode/#xcode-upload-dsyms) | | `vcs-metadata` | Resolve VCS metadata (provider, commit, branch, PR, repo). | [Metadata resolvers](/cli/metadata/#vcs-metadata) | | `ios-deps collect` | Collect the iOS dependency graph from lockfiles + linked frameworks. | [Metadata resolvers](/cli/metadata/#ios-deps-collect) | | `build-env xcode-version` | Resolve the dotted Xcode version. | [Metadata resolvers](/cli/metadata/#build-env) | diff --git a/docs/cli/debug-files.md b/docs/cli/debug-files.md index d1fcdfe..a740fa0 100644 --- a/docs/cli/debug-files.md +++ b/docs/cli/debug-files.md @@ -25,10 +25,13 @@ relevant debug files for the chosen type. | `proguard` *(default)* | Android R8 / ProGuard `mapping.txt` | The default when `--type` is unset. | | `elf` | Native ELF symbols (Android NDK / Linux) | Also uploaded with a Breakpad transform. | | `dsym` | Apple dSYM bundles | Recursive discovery + pre-upload UUID dedup. | +| `pdb` | Windows PDB files | Keyed by the PDB debug id (GUID + age). | | `sourcemaps` | JavaScript source maps | See [Source maps](/cli/sourcemaps/). | +| `rust` | Rust (Cargo) build output | Discovers whichever format the target emitted. See [Rust](#rust-cargo). | +| `il2cpp-linemap` | Unity IL2CPP line-number mappings | See [Unity IL2CPP](#unity-il2cpp). | -Other types (`pe`, `pdb`, `portable-pdb`, `breakpad`, `jvm`, `wasm`, -`sourcebundle`) are recognised by the discovery layer but not yet processed. +Other types (`pe`, `portable-pdb`, `breakpad`, `jvm`, `wasm`, `sourcebundle`) +are recognised by the discovery layer but not yet processed. ## Common options @@ -42,6 +45,25 @@ Other types (`pe`, `pdb`, `portable-pdb`, `breakpad`, `jvm`, `wasm`, | `--zstd-level ` | Zstd level `9..=22` (default `11`); or pass `--no-zstd`. | | `--force` | Re-upload even if the server already has the symbol. | | `--dry-run` | Discover and pack files but skip the HTTP upload. | +| `--concurrency ` | *(`--type sourcemaps` only, CLI 0.7.10+)* Ceiling on uploads in flight, `1..=32`. | +| `--allow-empty` | *(`--type sourcemaps` only, CLI 0.7.10+)* Treat "nothing to upload" as success. | +| `--il2cpp-uuid ` | *(`--type il2cpp-linemap` only)* Additional module UUID for a multi-ABI build. | +| `--il2cpp-root ` | *(`--type il2cpp-linemap` only)* Path to `il2cppFileRoot.txt` when it isn't beside the JSON. | + +`--concurrency` and `--allow-empty` are **rejected with exit `20`** for any other +`--type`, rather than accepted and ignored — so a caller who passed +`--allow-empty` to keep a build green can't silently still get exit `10`. + +## Re-uploads and missing paths + +A symbol the server already has is **skipped, and the batch continues** — so +rebuilding an app uploads only what changed. Pass `--force` to re-upload anyway. + +A path that **does not exist is an error** (exit `10`, `path does not exist: …`), +even when other paths in the same invocation do hold symbols, and nothing is +uploaded. A path you named and the tool cannot find is a typo or a build that +never ran, and half-uploading a build's symbols hides that until a crash arrives +unsymbolicated. ## Android (R8 / ProGuard) @@ -111,6 +133,62 @@ part of the whole build-publish flow. Use `debug-files upload --type dsym` directly for standalone or after-the-fact symbol uploads. ::: +## Rust (Cargo) + +A Rust project has no single symbol format: it's 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 and Android. `--type rust` discovers whichever the +build produced, so one command covers every target: + +```bash +bugsee-cli debug-files upload --type rust target/release \ + --version 1.4.0 --build 250 +``` + +Artefacts are classified by container magic rather than by host OS, so a +cross-compiled `target//release` uploads correctly from any machine. +Cargo intermediates (`deps/`, `build/`, `incremental/`, `.fingerprint/`) are +skipped, and `--uuid` is rejected — every Rust debug format carries its own +identity, which is what the SDK reports at crash time. + +A stock `cargo build --release` emits nothing uploadable. Each format needs a +build setting that, if missing, produces an upload that is accepted and then +resolves nothing: + +```toml +# Cargo.toml +[profile.release] +debug = 1 # emit DWARF at all +split-debuginfo = "packed" # macOS/iOS: collect it into a .dSYM +``` + +```toml +# .cargo/config.toml — Linux and Android only +[target.'cfg(target_os = "linux")'] +rustflags = ["-C", "link-arg=-Wl,--build-id"] +``` + +The command reports whichever setting is missing and exits `10` when it finds no +symbols at all. Use `--dry-run` to see the diagnosis without uploading. + +## Unity IL2CPP + +An IL2CPP build needs its line-number mapping in addition to the platform's +native symbols (iOS dSYMs, Android ELF): + +```bash +bugsee-cli debug-files upload path/to/Symbols/LineNumberMappings.json \ + --type il2cpp-linemap \ + --version 1.2.3 --build 45 \ + --uuid , +``` + +The mapping is keyed by the IL2CPP module UUID(s) (`libil2cpp` on Android, +`UnityFramework` on iOS). For a multi-ABI Android build, comma-separate the +values, repeat `--uuid`, or append with `--il2cpp-uuid`. Sibling +`MethodMap.tsv` and `il2cppFileRoot.txt` files are picked up automatically when +they sit next to the JSON. + ## Compression Symbol payloads are Zstd-compressed at level `11` by default. Tune it with diff --git a/docs/cli/index.mdx b/docs/cli/index.mdx index 71da331..d4171a9 100644 --- a/docs/cli/index.mdx +++ b/docs/cli/index.mdx @@ -37,7 +37,7 @@ themselves. | Debug information files | [`debug-files upload`](/cli/debug-files/) | | JavaScript source maps | [`sourcemaps inject`](/cli/sourcemaps/) + `debug-files upload --type sourcemaps` | | Build artefacts & metadata | [`upload build`](/cli/builds/), [`upload build-info`](/cli/builds/), [`pack`](/cli/builds/) | -| iOS build publishing | [`xcode post-action`](/cli/xcode/) | +| iOS build publishing | [`xcode post-action`](/cli/xcode/), [`xcode upload-dsyms`](/cli/xcode/#xcode-upload-dsyms) | | Build metadata resolvers | [`vcs-metadata`, `ios-deps`, `build-env`, `dsym`](/cli/metadata/) | | Self-update | [`update`](/cli/update/) | diff --git a/docs/cli/installation.md b/docs/cli/installation.md index 213599c..6e2f847 100644 --- a/docs/cli/installation.md +++ b/docs/cli/installation.md @@ -28,8 +28,9 @@ a hint. powershell -ExecutionPolicy ByPass -c "irm https://download.bugsee.com/cli/install.ps1 | iex" ``` -This installs to `%LOCALAPPDATA%\Bugsee\bin`. Only 64-bit (x86_64/AMD64) Windows -is published. +This installs to `%LOCALAPPDATA%\Bugsee\bin`. Both 64-bit (x86_64/AMD64) and +ARM64 Windows are published; the script detects the host architecture, including +when a 32-bit PowerShell is running on 64-bit Windows. ## Installer environment overrides @@ -57,9 +58,22 @@ work for direct installs too. | Channel | Package | |---|---| | npm | `@bugsee/cli` (per-OS `optionalDependencies` pull the right binary) | +| npm | `@bugsee/bugsee-cli` (single package; downloads the binary on install) | | Homebrew | the Bugsee tap | | Maven Central | `com.bugsee:bugsee-cli` (consumed by the Android Gradle plugin) | +:::tip +Prefer `@bugsee/cli` in JavaScript projects. Its binary ships inside per-platform +packages, so npm resolves exactly one and **nothing runs at install time** — it +works under `--ignore-scripts`, under a lockfile-pinned CI install, and offline +from a warm cache. `@bugsee/bugsee-cli` is the older single package whose +`postinstall` downloads the binary on every fresh install; it is still published +and supported, but it needs network access at install time. +::: + +Published platforms: macOS arm64 and x86_64, Linux x86_64 and aarch64, Windows +x86_64 and arm64. + :::note You normally don't install the CLI yourself for an SDK integration — the Bugsee SDKs and build plugins download and pin a compatible CLI automatically. Install diff --git a/docs/cli/release-notes.md b/docs/cli/release-notes.md index 9241420..99a92ed 100644 --- a/docs/cli/release-notes.md +++ b/docs/cli/release-notes.md @@ -14,6 +14,64 @@ date for you. ## 0.7.x +### 0.7.10 (September 18 2026) + +Source maps upload several at a time, an empty build can be a no-op, and a path +you named but the tool can't find now stops the run. + +- **Source-map uploads run concurrently.** `debug-files upload --type sourcemaps` + sent one map at a time, so a web build with one map per chunk spent most of its + upload time waiting on round-trips. Against a server with 50 ms of latency, 60 + maps went from 7.1 s to 1.3 s and 200 maps from 23.6 s to 4.1 s. + + `--concurrency ` (`1..=32`) sets a ceiling; left unset it scales with the + batch — one upload per 8 maps, at least 4, at most 8. `--concurrency 1` + restores the previous sequential behaviour. An explicit `--uuid` forces + sequential uploads whatever the ceiling, because it keys every map under one + ID and those registrations must not race. See + [Source maps](/cli/sourcemaps/#concurrency). + +- **`--allow-empty` makes "nothing to upload" a success.** A monorepo package + built without maps, or a framework whose server output has none, previously + failed the caller's build with exit `10`. The flag applies to + `--type sourcemaps`; `xcode upload-dsyms` already treated nothing-to-upload as + success. + +- **A path that does not exist is now an error.** + `debug-files upload --type sourcemaps dist/ missing/` used to warn about + `missing/`, upload what it found under `dist/`, and exit `0`. It now exits `10` + with `path does not exist: …` before uploading anything — a path you named and + the tool can't find is a typo or a build that didn't run, and half-uploading a + build's symbols hides that until a crash is unsymbolicated. Drop the missing + path from the invocation if you relied on the old leniency. The bundler plugins + pass a single output directory and are unaffected. + +- **A failed upload stops the batch.** Now that uploads are concurrent, the first + failure cancels the rest instead of letting every remaining map pack, register + and transfer into a server that has already refused one — a rejected token on a + 200-chunk build was 400 doomed round-trips. + +- **`--concurrency` and `--allow-empty` are rejected for other `--type`s** + (exit `20`) rather than accepted and ignored, so a caller who passed + `--allow-empty` to keep a build green can't still get exit `10`. + +- **A throttled request is retried.** The symbol-metadata and build-registration + `POST`s are sent without status retries, because a 5xx may mean the server + processed the request and only the response was lost. A `429` carries no such + ambiguity — the request was rejected without being processed — so it is now + retried with the usual backoff. Those requests are also the first thing a + server throttles when several uploads run at once. + +### 0.7.9 (September 17 2026) + +- **`sourcemaps inject` registers a debug ID another tool already wrote.** A + bundle carrying its own `//# debugId=` — Rollup 4 writes one with + `output.sourcemapDebugIds` — counted as already injected, so it never got the + `globalThis._bugseeDebugIds` runtime registration and the SDK could not attach + its debug ID to a crash frame. Such a bundle now keeps its ID, since its map + already carries it, and gains only the registration, without a second comment. + It is still never re-keyed. See [Source maps](/cli/sourcemaps/). + ### 0.7.8 (September 17 2026) Re-uploading a symbol the server already has no longer fails, including in an diff --git a/docs/cli/sourcemaps.md b/docs/cli/sourcemaps.md index 8ef0c23..723075d 100644 --- a/docs/cli/sourcemaps.md +++ b/docs/cli/sourcemaps.md @@ -39,6 +39,21 @@ This debug ID is what ties a crashing bundle in production to the right source map on the server — the runtime stub means the SDK can report the debug ID of the exact bundle that ran, and the uploaded map carries the matching key. +:::warning[`inject` only rewrites `.js`, `.cjs` and `.mjs` files] +A bundle with any other extension is skipped **silently**: the run reports +`js_injected=0` and exits `0`, and the problem only surfaces at upload time as a +map with no debug ID. React Native's default `main.jsbundle` output is the +common case — either emit the bundle with a `.js` name, or use the +[`bugsee-sourcemaps`](/tools/sourcemaps/) tool, which handles that naming. Check +the `js_injected` count in the log to confirm injection actually happened. +::: + +A bundle that already carries a `//# debugId=` written by **another tool** — for +example Rollup 4 with `output.sourcemapDebugIds` — keeps that ID, since its map +already carries it, and gains only the `globalThis._bugseeDebugIds` +registration, without a second comment. The SDK needs that registration to +attach the debug ID to a crash frame. *(CLI 0.7.9 and newer.)* + ## `sourcemaps inject` ```bash @@ -66,6 +81,52 @@ The upload discovers `.map` files and keys each one by its embedded debug ID server auto-detects the source-map format by content and re-derives the same key. +A map the server already has is skipped and the batch continues, so rebuilding +an app with unchanged chunks uploads only the ones that changed. `--force` +re-uploads anyway. + +### What fails, and when + +Directory scans are processed in sorted order, and every map is identified +**before** anything is uploaded — so these failures don't leave a partial upload +behind (a network or server error part-way through still can). + +| Situation | Result | +|---|---| +| A map named as a stylesheet or type declaration (`.css.map`, `.d.ts.map`, `.d.mts.map`, `.d.cts.map`) in a scanned directory | Skipped without being read | +| Any other map with no debug ID | **Exit `11`** — `inject` never stamped that bundle | +| A path that does not exist | **Exit `10`**, even when other paths hold maps | +| Nothing found to upload | **Exit `10`**, unless `--allow-empty` | +| One upload fails | The rest of the batch is cancelled | + +`--allow-empty` (CLI 0.7.10+) turns "nothing to upload" into success. 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 still an error, so a typo'd output directory isn't swallowed by the +flag. + +### Concurrency + +*(CLI 0.7.10 and newer.)* Each map is an independent register + `PUT` pair, so a +web build with one map per chunk used to spend its upload time waiting on +round-trips. Maps now upload several at a time. + +```bash +bugsee-cli debug-files upload ./dist --type sourcemaps \ + --version 1.4.0 --build 1400 --concurrency 8 +``` + +`--concurrency N` (`1..=32`) is a **ceiling**, not a fixed width — no more +uploads run than there are maps. Left unset it scales with the batch: one upload +per 8 maps, at least 4, at most 8. That default is deliberately modest, because +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. Raise +it if you've measured your own link. + +`--concurrency 1` restores strictly sequential uploads. An explicit `--uuid` +forces sequential uploads whatever the ceiling says: it keys every map in the +scan under one ID, and those registrations must not race each other. + ## Relationship to the legacy `bugsee-sourcemaps` tool The older [`bugsee-sourcemaps`](/tools/sourcemaps/) npm tool generates and diff --git a/docs/cli/xcode.md b/docs/cli/xcode.md index 1a66684..1447d1b 100644 --- a/docs/cli/xcode.md +++ b/docs/cli/xcode.md @@ -1,12 +1,27 @@ --- title: "iOS build publishing" -description: "Run the entire iOS build-publish flow from an Xcode Run Script post-action with bugsee-cli xcode post-action." +description: "Publish iOS builds and upload dSYMs from Xcode with bugsee-cli xcode post-action and xcode upload-dsyms." sidebar_position: 6 slug: "/cli/xcode" --- # iOS build publishing +Two commands read the Xcode build environment and upload from within a build. + +| Command | Runs from | Uploads | Can fail the build | +|---|---|---|---| +| [`xcode post-action`](#xcode-post-action) | A scheme's **Archive → Post-actions** | dSYMs, build registration, build-info, artefact, size check | Only the size check, and only with `--force-foreground` | +| [`xcode upload-dsyms`](#xcode-upload-dsyms) | A target's **Run Script build phase** | dSYMs only | Yes — by design | + +Choose `post-action` for the full build-publish flow, which is what the iOS SDK +wires up. Choose `upload-dsyms` when you only want symbols, when a build phase +is easier to generate than scheme XML (a React Native or Flutter config plugin +editing `project.pbxproj`), or when you want a failed upload to be visible +rather than silent. + +## `xcode post-action` + `bugsee-cli xcode post-action` runs the entire iOS build-publish flow from an Xcode "Run Script" post-action. It reads the Xcode build environment, decides whether it should run, and — when admitted — registers the build, uploads the @@ -98,3 +113,78 @@ For the authoritative, always-current list, run: ```bash bugsee-cli xcode post-action --help ``` + +## `xcode upload-dsyms` + +:::info[Requires CLI 0.7.7 or newer] +::: + +`bugsee-cli xcode upload-dsyms` 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. + +Add a "Run Script" phase to your target — after "Embed Frameworks" — with: + +```bash +"$SRCROOT/path/to/bugsee-cli" xcode upload-dsyms --app-token "$BUGSEE_APP_TOKEN" +``` + +It scans `DWARF_DSYM_FOLDER_PATH`, which Xcode sets in every Run Script phase, +and falls back to `/dSYMs`. + +:::warning[Xcode 15 and newer] +`ENABLE_USER_SCRIPT_SANDBOXING` defaults to `YES`, which stops a build phase +from reading the dSYM folder. Set it to `NO` on the target, or declare the +folder in the phase's input file lists. The scheme post-action is unaffected. +::: + +### A genuine failure fails the build + +This is the opposite of `post-action`'s policy, and it is deliberate: a build +phase that swallows errors means symbolication silently stops working and nobody +notices until a crash report is unreadable. + +| Situation | Exit code | Build | +|---|---|---| +| Uploaded, or there was nothing to upload | `0` | continues | +| A bundle could not be read or packed | `10` / `11` | **fails** | +| Missing or rejected app token, or a refused flag combination from the environment | `20` / `21` | **fails** | +| Server error or network failure | `30` / `31` | **fails** | +| A refused flag combination passed as flags | `2` | **fails** | + +"Nothing to upload" — no dSYM folder, or a folder with no `.dSYM` bundles — is a +success. A target that produces no debug symbols is a normal state; only real +problems fail the build. + +### Failing and detaching are independent + +Each has a flag pair and an environment variable, and a flag overrides its +variable. + +| Behaviour | Flags | Environment variable | Default | +|---|---|---|---| +| Fail the build on error | `--fail` / `--no-fail` | `BUGSEE_DSYM_UPLOAD_NO_FAIL` | fail | +| Detach the upload | `--background` / `--no-background` | `BUGSEE_DSYM_UPLOAD_BACKGROUND` | follows the failure policy | + +| Invocation | Fails the build | Waits for the upload | +|---|---|---| +| *(default)* | yes | yes | +| `--no-fail` | no | no — detaches | +| `--no-fail --no-background` | no | **yes** | +| `--fail --background` | *refused* | — | + +`--no-fail --no-background` is usually what CI wants: never break the build, but +still wait for the upload, so a runner tearing down its process tree the moment +`xcodebuild` returns cannot kill it mid-flight. + +`--fail --background` is **refused**, not honoured — exit `2` when given as +flags, exit `20` when it arrives through the environment. A detached process's +exit code reaches nobody, so "fail the build" would silently do nothing. + +A detached run logs to `$PROJECT_TEMP_DIR/bugsee-cli.log` instead of the Xcode +build log, so `--no-background` is also how you keep its warnings visible. On +Windows there is no fork and every run is synchronous. + +```bash +bugsee-cli xcode upload-dsyms --help +```