From 9242afef9796633bd926f26ab6b714f37b695ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Tue, 7 Apr 2026 10:53:46 +0200 Subject: [PATCH 1/4] refactor(init): use guardNonInteractive for TTY check Replace the manual stdin.isTTY check in preamble() with the shared guardNonInteractive() utility. This also fixes --dry-run being rejected in non-interactive mode (dry-run is safe without a TTY). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/init/wizard-runner.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index 99b8087a4..c3be99c7f 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -26,6 +26,7 @@ import { CLI_VERSION } from "../constants.js"; import { getAuthToken } from "../db/auth.js"; import { terminalLink } from "../formatters/colors.js"; import { getSentryBaseUrl } from "../sentry-urls.js"; +import { guardNonInteractive } from "../mutate-command.js"; import { slugify } from "../utils.js"; import { abortIfCancelled, @@ -325,9 +326,11 @@ async function preamble( yes: boolean, dryRun: boolean ): Promise { - if (!(yes || process.stdin.isTTY)) { + try { + guardNonInteractive({ yes, "dry-run": dryRun }); + } catch (err) { process.stderr.write( - "Error: Interactive mode requires a terminal. Use --yes for non-interactive mode.\n" + `Error: ${err instanceof Error ? err.message : "Interactive mode requires a terminal. Use --yes for non-interactive mode."}\n` ); process.exitCode = 1; return false; From bff518a23db7f645ea1fe5a3456db026adaf4936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Tue, 7 Apr 2026 10:56:27 +0200 Subject: [PATCH 2/4] fix: correct import ordering for lint Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/init/wizard-runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index c3be99c7f..e49db2d80 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -25,8 +25,8 @@ import { formatBanner } from "../banner.js"; import { CLI_VERSION } from "../constants.js"; import { getAuthToken } from "../db/auth.js"; import { terminalLink } from "../formatters/colors.js"; -import { getSentryBaseUrl } from "../sentry-urls.js"; import { guardNonInteractive } from "../mutate-command.js"; +import { getSentryBaseUrl } from "../sentry-urls.js"; import { slugify } from "../utils.js"; import { abortIfCancelled, From ec51f80d281ac37272fe5878f9969cec027a78e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Tue, 7 Apr 2026 11:21:01 +0200 Subject: [PATCH 3/4] fix: use process.stdin.isTTY with dry-run bypass guardNonInteractive uses isatty(0) which isn't mockable in tests and has a different error message. Keep the original isTTY check but add --dry-run bypass so dry-run works non-interactively. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/init/wizard-runner.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index e49db2d80..b37c10fbe 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -25,7 +25,6 @@ import { formatBanner } from "../banner.js"; import { CLI_VERSION } from "../constants.js"; import { getAuthToken } from "../db/auth.js"; import { terminalLink } from "../formatters/colors.js"; -import { guardNonInteractive } from "../mutate-command.js"; import { getSentryBaseUrl } from "../sentry-urls.js"; import { slugify } from "../utils.js"; import { @@ -326,11 +325,9 @@ async function preamble( yes: boolean, dryRun: boolean ): Promise { - try { - guardNonInteractive({ yes, "dry-run": dryRun }); - } catch (err) { + if (!(yes || dryRun || process.stdin.isTTY)) { process.stderr.write( - `Error: ${err instanceof Error ? err.message : "Interactive mode requires a terminal. Use --yes for non-interactive mode."}\n` + "Error: Interactive mode requires a terminal. Use --yes for non-interactive mode.\n" ); process.exitCode = 1; return false; From 4a4930f4345cbc970fcfcbb329914a7aaf06e4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Beteg=C3=B3n?= Date: Tue, 7 Apr 2026 11:56:42 +0200 Subject: [PATCH 4/4] fix: bypass interactive prompts in dry-run non-TTY mode When --dry-run passes the TTY guard, downstream prompts (confirmExperimental, checkGitStatus, resolvePreSpinnerOptions) would still attempt interactive input and hang. Pass dryRun as yes-equivalent so all prompts auto-accept in dry-run mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/init/wizard-runner.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index b37c10fbe..c0f49067a 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -338,7 +338,7 @@ async function preamble( let confirmed: boolean; try { - confirmed = await confirmExperimental(yes); + confirmed = await confirmExperimental(yes || dryRun); } catch (err) { if (err instanceof WizardCancelledError) { // Intentionally captured: track why users bail before completing @@ -359,7 +359,7 @@ async function preamble( log.warn("Dry-run mode: no files will be modified."); } - const gitOk = await checkGitStatus({ cwd: directory, yes }); + const gitOk = await checkGitStatus({ cwd: directory, yes: yes || dryRun }); if (!gitOk) { cancel("Setup cancelled."); process.exitCode = 0; @@ -577,7 +577,12 @@ export async function runWizard(initialOptions: WizardOptions): Promise { `\nFor manual setup: ${terminalLink(SENTRY_DOCS_URL)}` ); - const options = await resolvePreSpinnerOptions(initialOptions); + // In dry-run mode, treat all interactive prompts as non-interactive + // (auto-accept defaults) since we passed the TTY guard above. + const effectiveOptions = dryRun + ? { ...initialOptions, yes: true } + : initialOptions; + const options = await resolvePreSpinnerOptions(effectiveOptions); if (!options) { return; }