Skip to content

Commit 2d2fbff

Browse files
betegonclaude
andauthored
fix(init): gate unsupported --features and tag dry-run runs (#1535)
Two small `init` hardening fixes surfaced by a wizard-run triage. ## 1. Gate unsupported `--features` (source maps, metrics, attachments) `sourcemaps`, `metrics`, and `attachments` are non-selector products — the interactive selector never offers them, but `--features` still accepted them, leaving a half-configured integration (e.g. source-map upload needs an auth token this wizard does not provision). Removed from `FEATURE_ALIASES`, `SUPPORTED_FEATURE_NAMES`, and the flag help, so they now fail loudly (`Unknown init feature "sourcemaps"`) until their setup flow is automated. ## 2. Tag `wizard.dry_run` in telemetry Dry runs (`--dry-run`) apply and install nothing, so the CLI's post-apply `verify-setup` runtime check runs the app's dev command against an **unmodified** project and reports **`init verification failed`** (CLI-2HP) — a false positive. Set `wizard.dry_run` once at `runWizard` start (global scope, so every CLI event inherits it, including that capture). `wizard.dry_run:false` then isolates real runs from dry-run noise. Companion server change tags the same `wizard.dry_run` for the server-side `Verification found issues` (CLI-SERVER-22) — see below. ## Verification - `tsc --noEmit`: no new errors in the changed files. - `test/commands/init.test.ts`: 50/50 — updated the two normalization tests + the "Supported features:" message, and added a `test.each` locking in the rejection of the three gated features. - `interactive.test.ts` + `clack-utils.test.ts`: 45/45 — the interactive selector's filtering and the display labels are separate code paths, unaffected by the flag change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 02f6e8d commit 2d2fbff

4 files changed

Lines changed: 35 additions & 18 deletions

File tree

‎packages/cli/plugins/sentry-cli/skills/sentry-cli/references/init.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Initialize Sentry in your project (experimental)
1818
**Flags:**
1919
- `-y, --yes - Accept non-interactive defaults (requires --features outside a TTY)`
2020
- `-n, --dry-run - Show what would happen without making changes`
21-
- `--features <value>... - Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,attachments,agent-tracing,mcp-observability`
21+
- `--features <value>... - Features to enable: errors,tracing,logs,replay,profiling,crons,agent-tracing,mcp-observability`
2222
- `-t, --team <value> - Team slug to create the project under`
2323
- `--app <value> - App to initialize in a monorepo (required with --yes when multiple apps are detected)`
2424
- `--tui - Use the Ink-based interactive UI (default). Pass --no-tui to fall back to plain log output.`

‎packages/cli/src/commands/init.ts‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ const FEATURE_DELIMITER = /[,+ ]+/;
4242
const NON_INTERACTIVE_USAGE_HINT =
4343
"sentry init --yes --features errors,tracing,replay [target] [directory]";
4444

45+
// Only features backed by a Sentry SDK selector product are accepted here.
46+
// Non-selector products (source maps, metrics, attachments) are intentionally
47+
// not exposed via --features yet: their setup isn't fully automated — e.g.
48+
// source-map upload needs an auth token this wizard does not provision — so
49+
// accepting them would leave a half-configured integration. Re-add an alias
50+
// (and its SUPPORTED_FEATURE_NAMES entry) once that flow is complete.
4551
const FEATURE_ALIASES = {
4652
errors: "errorMonitoring",
4753
errorMonitoring: "errorMonitoring",
@@ -50,12 +56,8 @@ const FEATURE_ALIASES = {
5056
logs: "logs",
5157
replay: "sessionReplay",
5258
sessionReplay: "sessionReplay",
53-
metrics: "metrics",
5459
profiling: "profiling",
55-
sourcemaps: "sourceMaps",
56-
sourceMaps: "sourceMaps",
5760
crons: "crons",
58-
attachments: "attachments",
5961
aiMonitoring: "aiMonitoring",
6062
"agent-tracing": "aiMonitoring",
6163
agentTracing: "aiMonitoring",
@@ -68,11 +70,8 @@ const SUPPORTED_FEATURE_NAMES = [
6870
"tracing",
6971
"logs",
7072
"replay",
71-
"metrics",
7273
"profiling",
73-
"sourcemaps",
7474
"crons",
75-
"attachments",
7675
"agent-tracing",
7776
"mcp-observability",
7877
] as const;
@@ -339,7 +338,7 @@ export const initCommand = buildCommand<
339338
kind: "parsed",
340339
parse: String,
341340
brief:
342-
"Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,attachments,agent-tracing,mcp-observability",
341+
"Features to enable: errors,tracing,logs,replay,profiling,crons,agent-tracing,mcp-observability",
343342
variadic: true,
344343
optional: true,
345344
},

‎packages/cli/src/lib/init/wizard-runner.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,13 @@ export async function runWizard(initialOptions: WizardOptions): Promise<void> {
986986

987987
const { directory, yes, dryRun, features, forceLegacyUi } = initialOptions;
988988

989+
// Tag the whole run's telemetry with dry-run mode. Dry runs plan changes but
990+
// apply and install nothing, so post-apply verification (both this CLI's
991+
// `verify-setup` runtime check and the server's `verify-changes` step) sees an
992+
// unmodified project and reports expected "issues". This tag lets that
993+
// expected noise be filtered out (`wizard.dry_run:false` isolates real runs).
994+
setTag("wizard.dry_run", dryRun === true);
995+
989996
// Construct the UI once for the entire run; tear down on every exit
990997
// path via `await using`. The factory picks `InkUI` for interactive
991998
// runs and `LoggingUI` for CI / `--yes` / `--no-tui`.

‎packages/cli/test/commands/init.test.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,16 +183,12 @@ describe("init command func", () => {
183183
const ctx = makeContext();
184184
await func.call(ctx, {
185185
...DEFAULT_FLAGS,
186-
features: [
187-
"errors,tracing,replay,sourcemaps,attachments,agent-tracing,mcp-observability",
188-
],
186+
features: ["errors,tracing,replay,agent-tracing,mcp-observability"],
189187
});
190188
expect(capturedArgs?.features).toEqual([
191189
"errorMonitoring",
192190
"performanceMonitoring",
193191
"sessionReplay",
194-
"sourceMaps",
195-
"attachments",
196192
"aiMonitoring",
197193
"mcpObservability",
198194
]);
@@ -203,15 +199,13 @@ describe("init command func", () => {
203199
await func.call(ctx, {
204200
...DEFAULT_FLAGS,
205201
features: [
206-
"errorMonitoring,performanceMonitoring,sessionReplay,sourceMaps,attachments,aiMonitoring,mcpObservability",
202+
"errorMonitoring,performanceMonitoring,sessionReplay,aiMonitoring,mcpObservability",
207203
],
208204
});
209205
expect(capturedArgs?.features).toEqual([
210206
"errorMonitoring",
211207
"performanceMonitoring",
212208
"sessionReplay",
213-
"sourceMaps",
214-
"attachments",
215209
"aiMonitoring",
216210
"mcpObservability",
217211
]);
@@ -225,7 +219,7 @@ describe("init command func", () => {
225219
});
226220
await expect(promise).rejects.toThrow(ValidationError);
227221
await expect(promise).rejects.toThrow(
228-
"Supported features: errors, tracing, logs, replay, metrics, profiling, sourcemaps, crons, attachments, agent-tracing, mcp-observability"
222+
"Supported features: errors, tracing, logs, replay, profiling, crons, agent-tracing, mcp-observability"
229223
);
230224
expect(runWizardSpy).not.toHaveBeenCalled();
231225
expect(findProjectsSpy).not.toHaveBeenCalled();
@@ -248,6 +242,23 @@ describe("init command func", () => {
248242
expect(runWizardSpy).not.toHaveBeenCalled();
249243
});
250244

245+
test.each([
246+
"metrics",
247+
"sourcemaps",
248+
"attachments",
249+
])("rejects %s because init does not yet automate its setup", async (feature) => {
250+
const ctx = makeContext();
251+
const promise = func.call(ctx, {
252+
...DEFAULT_FLAGS,
253+
features: [feature],
254+
});
255+
await expect(promise).rejects.toThrow(ValidationError);
256+
await expect(promise).rejects.toThrow(
257+
`Unknown init feature "${feature}"`
258+
);
259+
expect(runWizardSpy).not.toHaveBeenCalled();
260+
});
261+
251262
test("passes undefined when features not provided", async () => {
252263
const ctx = makeContext();
253264
await func.call(ctx, DEFAULT_FLAGS);

0 commit comments

Comments
 (0)