Conversation
Preview what would happen without executing mutating operations. ## sentry api --dry-run Shows the fully resolved request without sending it: - Method, full URL (with resolved base + query params) - Headers and body - Supports --json for machine-readable output - Short alias: -n ## sentry project create --dry-run Validates inputs and shows what would be created: - Organization (resolved from args/config/DSN) - Team (auto-selected/created as normal) - Name, slug, platform - Does NOT call createProject or fetch DSN - Still validates platform and resolves org/team Also adds output: "json" to the api command for --json/--fields support (previously had no structured output mode). Fixes #349
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Init
Other
Bug Fixes 🐛Init
Other
Internal Changes 🔧Init
Other
🤖 This preview updates automatically when you update the PR. |
Codecov Results 📊✅ 104 passed | Total: 104 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ✅ Patch coverage is 99.26%. Project has 900 uncovered lines. Files with missing lines (2)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 95.49% 95.50% +0.01%
==========================================
Files 142 142 —
Lines 19954 19999 +45
Branches 0 0 —
==========================================
+ Hits 19055 19099 +44
- Misses 899 900 +1
- Partials 0 0 —Generated by Codecov Action |
- Prevent team auto-creation side effect in dry-run mode by passing autoCreateSlug: undefined when --dry-run is set (BugBot HIGH) - Use getDefaultSdkConfig().baseUrl in resolveRequestUrl to match rawApiRequest URL normalization, preventing double-slash URLs when SENTRY_URL has trailing slash (BugBot MEDIUM)
Continuation lines of JSON.stringify output are now indented to align with the first line after the 'Body: ' label prefix. Also uses getDefaultSdkConfig().baseUrl (with trailing-slash normalization) instead of raw getApiBaseUrl() in resolveRequestUrl to match the URL rawApiRequest would actually use.
When an org has no teams, normal mode auto-creates a team. Dry-run
now reflects this by returning { slug, source: 'auto-created' }
without calling the createTeam API.
Previously dry-run passed autoCreateSlug: undefined which caused a
ContextError. Now passes dryRun: true to resolveOrCreateTeam which
skips the mutation while preserving the preview.
The api command is a raw API proxy — the response is already JSON from the Sentry API. Having output: 'json' from buildCommand injected --json and --fields flags that only worked in --dry-run mode, which was confusing. Now --json is an explicit flag documented as applying to dry-run preview output only. Removed --fields since it doesn't apply.
--fields is useful for filtering both the raw API response and dry-run output. Restored output: 'json' on apiCommand so buildCommand injects --json/--fields flags. handleResponse now applies --fields filtering to the response body when provided.
Replace manual flags.json/writeJson branching with writeOutput(), which is the shared utility that handles --json/--fields/human formatting. Extract formatDryRun as a pure function for the human-readable dry-run preview.
Mirror rawApiRequest behavior: when body is an object and no Content-Type header was explicitly provided, auto-add Content-Type: application/json in buildDryRunRequest.
Biome's auto-fix placed the type and function between import statements. Move them after all imports to maintain standard module structure.
Standardize dry-run human output to use the same markdown KV table pattern as formatProjectCreated. Uses ## heading, > blockquote notes for team source, and mdKvTable for key-value data. Removes custom manual alignment formatting. Also fixes interleaved imports: DryRunData type and formatDryRun function are now placed after all import statements.
Both paths now construct a ProjectCreatedResult and return { data }
through buildCommand's output wrapper. The single formatProjectCreated
formatter handles both modes — dry-run adds a dryRun flag that adjusts
the heading and team source note wording.
Removes DryRunData type, formatDryRun function, and writeOutput import.
Net -32 lines.
- Upgrade output: 'json' → { json: true, human: formatDryRunRequest }
so dry-run returns { data } like project create (normal path returns
void, which the wrapper silently ignores)
- Convert writeDryRunHuman to formatDryRunRequest: pure function
returning rendered markdown using mdKvTable (same pattern as other
commands), replaces imperative stdout.write calls
- Simplify buildDryRunRequest signature: (method, endpoint, options)
instead of single object — mirrors how rawApiRequest is called
- Remove muted import (now uses <muted> color tag in markdown)
The api command is a JSON proxy — its output should always be JSON, not conditionally switch to human-readable markdown in dry-run mode. - Revert output config to 'json' (flag-only, no human formatter) - Dry-run writes JSON directly via writeJson (imperative, since flag-only mode doesn't intercept returns) - Remove formatDryRunRequest and its markdown imports entirely - Remove formatDryRunRequest test suite (6 tests)
…uest
Both code paths now return { data } through the standard output system:
- Dry-run: returns { data: { method, url, headers, body } } preview
- Normal: returns { data: response.body } after side-effect writes
(verbose/include headers, error exit code)
Key changes:
- output: 'json' → { json: true } (JSON-only config, no human formatter)
- Make OutputConfig.human optional — when absent, renderCommandOutput
always serializes as JSON regardless of --json flag
- Remove buildDryRunRequest — inline request preview construction using
resolveRequestUrl + new resolveEffectiveHeaders helper
- Remove handleResponse — its behaviors are inlined in func:
verbose/include headers as pre-return side effects, silent mode
returns void, error uses process.exitCode (not process.exit) so
the output wrapper can render before the process exits
- Remove DryRunRequest type, writeJson import
Tests: replace handleResponse tests (8) and buildDryRunRequest tests
(4+5 property) with resolveEffectiveHeaders tests (6+4 property).
288 tests pass across 3 files.
Stricli overwrites process.exitCode after the command returns,
so process.exitCode = 1 was silently reset to 0. For error
responses, write JSON directly then process.exit(1). Success
responses still use return-based { data } through the output
system.
… divergence Address three bot review comments: 1. String response quoting regression (Seer + BugBot): the api command is a raw proxy — non-JSON responses (plain text, HTML error pages) must not be wrapped in JSON quotes. Revert to output: 'json' (flag-only) and write response body imperatively via writeResponseBody, which writes strings directly and JSON-formats objects with --fields. 2. Null body header divergence (BugBot): resolveEffectiveHeaders had an extra body !== null check that rawApiRequest doesn't. For --data "null", dry-run would omit Content-Type while the real request adds it. Aligned condition to match rawApiRequest exactly: !(isStringBody || hasContentType) && body !== undefined. 3. Stale comment about dry-run always outputting JSON (BugBot on old commit 70e7f2d): the referenced code was completely rewritten in 6980508. No action needed — will reply to dismiss. Also reverts OutputConfig.human back to required (the JSON-only config form is no longer needed since the api command doesn't use it).
|
Re: Seer finding on dry-run human format (line 1091): This is intentional. The api command is a raw API proxy — its |
c06fe6f to
8d4aebf
Compare
…ttern
Both dry-run and normal response paths now return { data } through the
output system. No more imperative writeJson/writeResponseBody calls.
Changes:
- Add OutputError class (extends CliError) for commands that produce
valid output but should exit non-zero. The buildCommand wrapper
catches it, renders data through the output system, then calls
process.exit(). This replaces the muddled exitCode field on
CommandOutput<T>.
- Add formatApiResponse human formatter — preserves raw strings (plain
text, HTML error pages) without JSON quoting, JSON-formats objects.
Does NOT add trailing newline (renderCommandOutput appends it).
- Api command uses output: { json: true, human: formatApiResponse }
instead of flag-only output: "json". Error responses throw
OutputError(response.body) instead of returning { exitCode: 1 }.
- Remove writeResponseBody function and writeJson import from api.ts.
- Separate stdout/stderr in test context to prevent Stricli error
messages from polluting JSON output assertions.
Replace imperative writeVerboseRequest, writeVerboseResponse, and
writeResponseHeaders functions with return-based output:
- Default: return raw response.body
- --include: return { status, headers, body } envelope
- --verbose: return { request, status, headers, body } full envelope
The human formatter (formatApiResponse) handles all three shapes:
- Raw body → formatBody (JSON-format objects, passthrough strings)
- --include envelope → HTTP status + headers + body
- --verbose envelope → '> ' request block + '< ' response block + body
JSON mode gets the same structured data — --include/--verbose now
produce structured JSON output instead of mixed text.
Also: --silent + error uses OutputError(null) instead of bare
process.exit(1), keeping the framework in control of exit codes.
Delete dead functions: writeResponseHeaders, writeVerboseRequest,
writeVerboseResponse.
--include wraps the body in { status, headers, body } which breaks
--fields filtering (users would need --fields body.name instead of
--fields name). Remove it entirely.
--verbose request/response metadata is diagnostic info that belongs
on stderr, not in the return data. Move to logger.debug() calls via
consola — the framework already sets log level to debug when
--verbose is passed, so the log lines appear automatically.
The api command now always returns raw response.body. No envelopes,
no shape changes per flags. --fields works directly on API response
fields.
Deleted: ApiResponseEnvelope type, isEnvelope() guard, formatBody()
(renamed back to formatApiResponse), envelope rendering logic,
headersToObject helper, --include flag + -i alias.
…utput bug - Delete E2E tests for removed --include/-i flag - Fix --verbose E2E test: check stderr (logger.debug) not stdout - Fix renderCommandOutput null data bug: skip rendering when OutputError.data is null/undefined instead of writing spurious \n - Update api docs: replace --include example with --verbose section - Regenerate SKILL.md to remove stale --include references
|
Consola's BasicReporter (used when stderr is piped, e.g. in CI or Bun.spawn) routes debug and info messages to stdout instead of stderr. This contaminated command output when --verbose was used in non-TTY contexts. Fix: set stdout: process.stderr in createConsola() so ALL diagnostic log output goes to stderr regardless of reporter or TTY mode.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
|
Re: BugBot finding on Fixed both issues:
The |
- Guard verbose logging with !flags.silent to prevent leak when --verbose and --silent are combined (BugBot finding) - Extract logRequest() and logResponse() helpers to reduce func cognitive complexity from 17 to within limit
Summary
Add
--dry-runflag tosentry apiandsentry project createso agents and users can preview what would happen without executing mutating operations.Fixes #349
Changes
sentry api --dry-runShows the fully resolved request without sending it:
--jsonfor machine-readable output (--fieldsfor filtering)-nsentry project create --dry-runValidates inputs and shows what would be created:
createProjector fetch DSN--jsonAlso
output: "json"to theapicommand for--json/--fieldssupportTesting
resolveRequestUrl,buildDryRunRequest,writeDryRunHuman