Skip to content

feat(local): add command to run a local Spotlight sidecar#888

Open
MathurAditya724 wants to merge 5 commits intomainfrom
feat/local-spotlight-command
Open

feat(local): add command to run a local Spotlight sidecar#888
MathurAditya724 wants to merge 5 commits intomainfrom
feat/local-spotlight-command

Conversation

@MathurAditya724
Copy link
Copy Markdown
Member

Summary

Adds sentry local, a long-running command that starts a minimal Hono HTTP server wire-compatible with the Spotlight sidecar protocol. It uses @spotlightjs/spotlight/sdk's createSpotlightBuffer and pushToSpotlightBuffer helpers to ingest envelopes from any Sentry SDK running in the user's dev stack and tails them to the terminal.

This gives users a one-command path to "Sentry for development" without leaving the CLI: errors, traces, and logs from their dev stack stream straight into their shell, and the SSE endpoint stays compatible with the Spotlight overlay for richer rendering.

What's new

  • src/commands/local.ts — new command. Flags: --port / -p (default 8969), --host / -H (default localhost), --open / -o, --quiet / -q. Runs without auth.
  • src/app.ts — wires local into the top-level route map.
  • package.json — adds @spotlightjs/spotlight, hono, and @hono/node-server as devDependencies (per the no-runtime-deps rule; everything is bundled at build time).
  • docs/src/fragments/commands/local.md — hand-written examples + endpoint table.

Endpoints exposed

Method Path Description
POST /stream Spotlight-compatible envelope ingest
POST /api/{projectId}/envelope/ Sentry SDK ingest path
GET /stream Server-Sent Events feed (Spotlight overlay)
GET /health Liveness check

Why a thin in-tree server instead of spawning npx @spotlightjs/spotlight

The SDK helpers give us decompression (gzip/deflate/br) + lazy envelope parsing for free, while keeping the command's surface focused on a CLI-friendly tail UX. Bundling through esbuild also keeps the published binary self-contained per the no-runtime-dependencies rule — users don't need a separate npx install or a network connection on first run.

Testing

  • bun run typecheck
  • bun x ultracite check (only the pre-existing unrelated markdown.ts warning)
  • bun run check:deps / check:fragments / check:errors / check:docs-sections
  • bun test --timeout 15000 --isolate --parallel test/lib test/commands test/types — 6343 pass / 0 fail
  • SENTRY_CLIENT_ID=test bun run script/bundle.ts — bundle builds cleanly
  • ✅ Manual smoke: started the sidecar, posted an envelope to /stream, observed 204 No Content and the tail line HH:MM:SS.sss • event appear; SIGTERM shut down gracefully. Same flow against the bundled node dist/bin.cjs.

Out of scope

  • The Spotlight overlay UI itself — setupSpotlight() from the upstream package can serve it but pulls in MCP / @sentry/node / a much larger dependency tree; this PR sticks to the small Hono server to keep the CLI lean.
  • Persistent storage of envelopes — the buffer is in-memory, capped at 500 items.

Adds 'sentry local', a long-running command that starts a minimal Hono
HTTP server wire-compatible with the Spotlight sidecar protocol. The
server uses @spotlightjs/spotlight/sdk's createSpotlightBuffer +
pushToSpotlightBuffer helpers to ingest envelopes from any Sentry SDK
running in the user's dev stack and tails them to the terminal.

Endpoints exposed:
  POST /stream                          - Spotlight ingest
  POST /api/{projectId}/envelope/       - Sentry SDK ingest path
  GET  /stream                          - SSE feed for the Spotlight overlay
  GET  /health                          - liveness check

Why a thin in-tree server instead of spawning npx @spotlightjs/spotlight:
the SDK helpers give us decompression + lazy parsing for free while
keeping the surface focused on a CLI-friendly tail UX, and bundling
through esbuild keeps the published binary self-contained per the
no-runtime-dependencies rule.

The command runs without auth (it's a local dev tool) and shuts down
gracefully on SIGINT/SIGTERM, force-closing keep-alive connections so
SSE subscribers don't block exit.
@github-actions
Copy link
Copy Markdown
Contributor

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-888/

Built to branch gh-pages at 2026-04-29 20:40 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

Codecov Results 📊

6343 passed | Total: 6343 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

❌ Patch coverage is 25.10%. Project has 13230 uncovered lines.
❌ Project coverage is 75.8%. Comparing base (base) to head (head).

Files with missing lines (1)
File Patch % Lines
src/commands/local.ts 24.50% ⚠️ 188 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    76.03%    75.80%    -0.23%
==========================================
  Files          294       295        +1
  Lines        54404     54659      +255
  Branches         0         0         —
==========================================
+ Hits         41362     41429       +67
- Misses       13042     13230      +188
- Partials         0         0         —

Generated by Codecov Action

Comment thread src/commands/local.ts Outdated
Replace the minimal 'timestamp • type' one-liner with rich formatted
output that shows actual event content: error type/message with stack
location, transaction name/op/duration/span count, and log messages
with attributes. Uses the CLI's own color system since Spotlight's
humanFormatters aren't publicly exported from the package.
Adds a repeatable --filter flag that accepts error, transaction, or log.
When set, only matching envelope items are rendered in the tail output;
non-matching items are silently dropped. No filter = show everything.

Usage:
  sentry local -f error            # errors only
  sentry local -f error -f log     # errors and logs
  sentry local -f transaction      # transactions only
Three fixes based on audit against Spotlight's reference implementation:

1. Signal handling: process.once -> process.on so the 'second signal =
   force exit' code path is reachable (process.once unregisters after
   the first signal, making the shuttingDown check dead code).

2. SSE format: match the Spotlight protocol so the overlay UI works.
   - event name is the content type (not 'envelope')
   - id field is the Spotlight-assigned envelope UUID
   - data is the parsed envelope JSON (not base64-encoded raw bytes)
   - Last-Event-ID reconnection is now supported

3. Browser SDK: detect sendBeacon() payloads (Content-Type: text/plain
   with sentry_client query param) and override to the canonical
   application/x-sentry-envelope, matching Spotlight's workaround.
The --open flag opened the raw SSE endpoint in a browser, which just
shows streaming text — not useful without the Spotlight overlay UI.
Removed it and updated the fragment docs to document the new
pretty-print tail output and --filter flag instead.
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.

1 participant