From 7dedbfae9c433ebc9862bf444474abf4d91715cf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Apr 2026 12:25:18 +0000 Subject: [PATCH 1/4] fix(span-list): query all projects in trace mode for cross-project traces (#735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In trace mode, `sentry span list ` was always scoping the spans query to a single project via `project:{slug}` in the API query. For traces spanning multiple projects, this returned empty results because the auto-detected project may not match any of the projects in the trace. The fix adds an `allProjects` option to `listSpans` that sends `project=-1` (all projects) instead of a single-project filter. This matches how `getDetailedTrace` (used by `trace view`) works for cross-project traces. The trace mode handler now passes `allProjects: true` since the `trace:{id}` query filter already scopes results correctly. Co-authored-by: Miguel Betegón --- src/commands/span/list.ts | 1 + src/lib/api/traces.ts | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/commands/span/list.ts b/src/commands/span/list.ts index 13345d34f..6b6666c99 100644 --- a/src/commands/span/list.ts +++ b/src/commands/span/list.ts @@ -389,6 +389,7 @@ async function handleTraceMode( cursor, ...timeRangeToApiParams(timeRange), extraFields: extraApiFields, + allProjects: true, }) ); diff --git a/src/lib/api/traces.ts b/src/lib/api/traces.ts index e800cccff..2d3c6e376 100644 --- a/src/lib/api/traces.ts +++ b/src/lib/api/traces.ts @@ -386,6 +386,8 @@ type ListSpansOptions = { start?: string; /** Absolute end datetime (ISO-8601). Mutually exclusive with statsPeriod. */ end?: string; + /** When true, query spans across all projects (sends project=-1). Used for cross-project traces. */ + allProjects?: boolean; }; /** @@ -402,8 +404,10 @@ export async function listSpans( projectSlug: string, options: ListSpansOptions = {} ): Promise> { - const isNumericProject = isAllDigits(projectSlug); - const projectFilter = isNumericProject ? "" : `project:${projectSlug}`; + const useAllProjects = options.allProjects === true; + const isNumericProject = !useAllProjects && isAllDigits(projectSlug); + const projectFilter = + useAllProjects || isNumericProject ? "" : `project:${projectSlug}`; const fullQuery = [projectFilter, options.query].filter(Boolean).join(" "); const fields = options.extraFields?.length @@ -412,6 +416,13 @@ export async function listSpans( const regionUrl = await resolveOrgRegion(orgSlug); + let projectParam: string | number | undefined; + if (useAllProjects) { + projectParam = -1; + } else if (isNumericProject) { + projectParam = projectSlug; + } + const { data: response, headers } = await apiRequestToRegion( regionUrl, `/organizations/${orgSlug}/events/`, @@ -419,7 +430,7 @@ export async function listSpans( params: { dataset: "spans", field: fields, - project: isNumericProject ? projectSlug : undefined, + project: projectParam, query: fullQuery || undefined, per_page: options.limit || 10, statsPeriod: From 146dd36e63f1bf3ab51cd0d81c4cc23ee154fc1c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Apr 2026 12:29:11 +0000 Subject: [PATCH 2/4] fix(init): suppress raw ANSI escape sequences when output is piped (#746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `sentry init --yes` output is piped or redirected, @clack/prompts writes raw terminal control sequences (cursor hide/show, erase codes, spinner animation) that make captured output unreadable. The fix adds a thin adapter module (clack-plain.ts) that intercepts all clack function calls used by the init wizard. When `isPlainOutput()` is true (non-TTY, NO_COLOR, etc.), the adapter replaces clack's box-drawing and ANSI-heavy output with clean, plain-text equivalents. Interactive prompts (confirm, select, multiselect) pass through to real clack since they're only reached in TTY mode. Also updated the wizard spinner to check `isPlainOutput()` in addition to `isTTY` before writing cursor hide/show sequences and starting the animation timer. Co-authored-by: Miguel Betegón --- src/lib/init/clack-plain.ts | 98 +++++++++++++++++++++++++++++++++++ src/lib/init/clack-utils.ts | 2 +- src/lib/init/formatters.ts | 2 +- src/lib/init/git.ts | 2 +- src/lib/init/interactive.ts | 2 +- src/lib/init/preflight.ts | 2 +- src/lib/init/spinner.ts | 8 +-- src/lib/init/wizard-runner.ts | 2 +- 8 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 src/lib/init/clack-plain.ts diff --git a/src/lib/init/clack-plain.ts b/src/lib/init/clack-plain.ts new file mode 100644 index 000000000..623a0d6f8 --- /dev/null +++ b/src/lib/init/clack-plain.ts @@ -0,0 +1,98 @@ +/** + * Plain-mode adapter for @clack/prompts. + * + * When stdout is not a TTY (piped, redirected, CI), clack writes raw ANSI + * escape sequences (cursor hide/show, erase codes, spinner animation) that + * make captured output unreadable. This module re-exports the clack functions + * used by the init wizard, replacing them with plain-text equivalents when + * `isPlainOutput()` is true. + * + * Interactive prompts (confirm, select, multiselect) pass through to real + * clack unconditionally — they're only reached in TTY mode (the wizard + * guards with `process.stdin.isTTY` before prompts). + */ + +import * as clack from "@clack/prompts"; +import { isPlainOutput } from "../formatters/plain-detect.js"; +import { stripAnsi } from "../formatters/plain-detect.js"; + +function plainWrite(message: string): void { + process.stdout.write(`${stripAnsi(message)}\n`); +} + +/** intro() — in plain mode, just print the title without box-drawing chars */ +export function intro(title?: string): void { + if (isPlainOutput()) { + if (title) { + plainWrite(`── ${stripAnsi(String(title))} ──`); + } + return; + } + clack.intro(title); +} + +/** outro() — in plain mode, print a simple closing line */ +export function outro(message?: string): void { + if (isPlainOutput()) { + if (message) { + plainWrite(stripAnsi(String(message))); + } + return; + } + clack.outro(message); +} + +/** cancel() — in plain mode, print the cancellation message */ +export function cancel(message?: string): void { + if (isPlainOutput()) { + if (message) { + process.stderr.write(`${stripAnsi(String(message))}\n`); + } + return; + } + clack.cancel(message); +} + +/** + * Plain-mode log object that mirrors clack's `log` API. + * Strips ANSI and writes to stdout/stderr without box-drawing characters. + */ +const plainLog: typeof clack.log = { + message(message?: string, opts?: { symbol?: string }) { + if (message) { + const prefix = opts?.symbol ? `${stripAnsi(opts.symbol)} ` : ""; + plainWrite(`${prefix}${stripAnsi(message)}`); + } + }, + info(message?: string) { + plainWrite(`INFO: ${stripAnsi(String(message ?? ""))}`); + }, + success(message?: string) { + plainWrite(`OK: ${stripAnsi(String(message ?? ""))}`); + }, + step(message?: string) { + plainWrite(`> ${stripAnsi(String(message ?? ""))}`); + }, + warn(message?: string) { + process.stderr.write(`WARN: ${stripAnsi(String(message ?? ""))}\n`); + }, + warning(message?: string) { + process.stderr.write(`WARN: ${stripAnsi(String(message ?? ""))}\n`); + }, + error(message?: string) { + process.stderr.write(`ERROR: ${stripAnsi(String(message ?? ""))}\n`); + }, +}; + +/** log — routes to plain-text or clack based on output mode */ +export const log: typeof clack.log = new Proxy(clack.log, { + get(_target, prop: keyof typeof clack.log) { + if (isPlainOutput()) { + return plainLog[prop]; + } + return clack.log[prop]; + }, +}); + +export { isCancel } from "@clack/prompts"; +export { confirm, select, multiselect } from "@clack/prompts"; diff --git a/src/lib/init/clack-utils.ts b/src/lib/init/clack-utils.ts index d796fd003..a4a8236af 100644 --- a/src/lib/init/clack-utils.ts +++ b/src/lib/init/clack-utils.ts @@ -4,7 +4,7 @@ * Shared helpers for the clack-based init wizard UI. */ -import { cancel, isCancel } from "@clack/prompts"; +import { cancel, isCancel } from "./clack-plain.js"; import { terminalLink } from "../formatters/colors.js"; import { SENTRY_DOCS_URL } from "./constants.js"; diff --git a/src/lib/init/formatters.ts b/src/lib/init/formatters.ts index a7dda1a4a..5e7f5d1d6 100644 --- a/src/lib/init/formatters.ts +++ b/src/lib/init/formatters.ts @@ -4,7 +4,7 @@ * Format wizard results and errors for terminal display using clack. */ -import { cancel, log, outro } from "@clack/prompts"; +import { cancel, log, outro } from "./clack-plain.js"; import { terminalLink } from "../formatters/colors.js"; import { colorTag, mdKvTable, renderMarkdown } from "../formatters/markdown.js"; import { featureLabel } from "./clack-utils.js"; diff --git a/src/lib/init/git.ts b/src/lib/init/git.ts index d3f7a4760..3012d8f66 100644 --- a/src/lib/init/git.ts +++ b/src/lib/init/git.ts @@ -9,7 +9,7 @@ * `checkGitStatus` orchestrator (coupled to `@clack/prompts` UI). */ -import { confirm, isCancel, log } from "@clack/prompts"; +import { confirm, isCancel, log } from "./clack-plain.js"; import { getUncommittedFiles, isInsideGitWorkTree as isInsideWorkTree, diff --git a/src/lib/init/interactive.ts b/src/lib/init/interactive.ts index 52f39cddf..c18508d51 100644 --- a/src/lib/init/interactive.ts +++ b/src/lib/init/interactive.ts @@ -6,7 +6,7 @@ * Respects --yes flag for non-interactive mode. */ -import { confirm, log, multiselect, select } from "@clack/prompts"; +import { confirm, log, multiselect, select } from "./clack-plain.js"; import chalk from "chalk"; import { abortIfCancelled, diff --git a/src/lib/init/preflight.ts b/src/lib/init/preflight.ts index ba8a9b16e..fec0a967a 100644 --- a/src/lib/init/preflight.ts +++ b/src/lib/init/preflight.ts @@ -1,4 +1,4 @@ -import { cancel, isCancel, log, select } from "@clack/prompts"; +import { cancel, isCancel, log, select } from "./clack-plain.js"; import type { SentryTeam } from "../../types/index.js"; import { listOrganizations } from "../api-client.js"; import { getAuthToken } from "../db/auth.js"; diff --git a/src/lib/init/spinner.ts b/src/lib/init/spinner.ts index 2491705a7..63bf57737 100644 --- a/src/lib/init/spinner.ts +++ b/src/lib/init/spinner.ts @@ -5,6 +5,7 @@ import { renderInlineMarkdown, stripColorTags, } from "../formatters/markdown.js"; +import { isPlainOutput } from "../formatters/plain-detect.js"; const HIDE_CURSOR = "\u001B[?25l"; const SHOW_CURSOR = "\u001B[?25h"; @@ -108,11 +109,12 @@ export function createWizardSpinner( running = true; frameIndex = 0; message = nextMessage; - if (output.isTTY) { + const plain = isPlainOutput(); + if (output.isTTY && !plain) { output.write(HIDE_CURSOR); } renderCurrentFrame(); - if (output.isTTY) { + if (output.isTTY && !plain) { timer = setInterval(() => { frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length; renderCurrentFrame(); @@ -138,7 +140,7 @@ export function createWizardSpinner( `${renderInlineMarkdown(formatStoppedBlock(message, code))}\n` ); } - if (output.isTTY) { + if (output.isTTY && !isPlainOutput()) { output.write(SHOW_CURSOR); } } diff --git a/src/lib/init/wizard-runner.ts b/src/lib/init/wizard-runner.ts index caa8d7f85..9ff8e0789 100644 --- a/src/lib/init/wizard-runner.ts +++ b/src/lib/init/wizard-runner.ts @@ -8,7 +8,7 @@ import { randomBytes } from "node:crypto"; import { basename } from "node:path"; -import { cancel, confirm, intro, log } from "@clack/prompts"; +import { cancel, confirm, intro, log } from "./clack-plain.js"; import { MastraClient } from "@mastra/client-js"; import { captureException, getTraceData } from "@sentry/node-core/light"; import { formatBanner } from "../banner.js"; From 34b289fb2a38c3bc70edec175d7250a8eae2472d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Apr 2026 12:31:40 +0000 Subject: [PATCH 3/4] fix(dsn): detect framework-prefixed DSN env vars in .env files and process.env (#820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sentry init` was not detecting DSNs supplied via framework-specific environment variables like `NEXT_PUBLIC_SENTRY_DSN`, `REACT_APP_SENTRY_DSN`, `VITE_SENTRY_DSN`, etc. This caused init to create a new Sentry project instead of reusing the existing one when the DSN was provided through a framework-prefixed env var. The .env file scanner (env-file.ts) only matched the exact pattern `SENTRY_DSN=...`. The runtime env detection (env.ts) only checked `process.env.SENTRY_DSN`. Fix: - Expanded the .env file regex from `^SENTRY_DSN` to `^(?:\w+_)?SENTRY_DSN` to match framework-prefixed variants (NEXT_PUBLIC_, REACT_APP_, VITE_, etc.) - Added explicit checks for the 5 most common framework-prefixed env vars in the runtime env detection, checked after the canonical SENTRY_DSN Co-authored-by: Miguel Betegón --- src/lib/dsn/env-file.ts | 14 ++++++++++---- src/lib/dsn/env.ts | 34 +++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/lib/dsn/env-file.ts b/src/lib/dsn/env-file.ts index 3193b1a0d..4cc3fc9fb 100644 --- a/src/lib/dsn/env-file.ts +++ b/src/lib/dsn/env-file.ts @@ -44,11 +44,17 @@ export const ENV_FILES = [ ] as const; /** - * Pattern to match SENTRY_DSN in .env files. - * Handles: SENTRY_DSN=value, SENTRY_DSN="value", SENTRY_DSN='value' - * Also handles trailing comments: SENTRY_DSN=value # comment + * Pattern to match Sentry DSN variables in .env files. + * Handles the canonical `SENTRY_DSN` as well as framework-prefixed variants + * commonly used by Next.js, Vite, CRA, Expo, Nuxt, and others: + * NEXT_PUBLIC_SENTRY_DSN, REACT_APP_SENTRY_DSN, VITE_SENTRY_DSN, + * EXPO_PUBLIC_SENTRY_DSN, NUXT_PUBLIC_SENTRY_DSN, etc. + * + * Handles: VAR=value, VAR="value", VAR='value' + * Also handles trailing comments: VAR=value # comment */ -const ENV_DSN_PATTERN = /^SENTRY_DSN\s*=\s*(['"]?)(.+?)\1\s*(?:#.*)?$/; +const ENV_DSN_PATTERN = + /^(?:\w+_)?SENTRY_DSN\s*=\s*(['"]?)(.+?)\1\s*(?:#.*)?$/; /** * Extract SENTRY_DSN value from .env file content. diff --git a/src/lib/dsn/env.ts b/src/lib/dsn/env.ts index c8dfb0dbe..dc2c01d64 100644 --- a/src/lib/dsn/env.ts +++ b/src/lib/dsn/env.ts @@ -13,7 +13,22 @@ import type { DetectedDsn } from "./types.js"; export const SENTRY_DSN_ENV = "SENTRY_DSN"; /** - * Detect DSN from environment variable. + * Framework-prefixed env var names that commonly hold a Sentry DSN. + * Checked in order after `SENTRY_DSN` (canonical name has priority). + */ +const FRAMEWORK_DSN_VARS = [ + "NEXT_PUBLIC_SENTRY_DSN", + "REACT_APP_SENTRY_DSN", + "VITE_SENTRY_DSN", + "EXPO_PUBLIC_SENTRY_DSN", + "NUXT_PUBLIC_SENTRY_DSN", +] as const; + +/** + * Detect DSN from environment variables. + * + * Checks `SENTRY_DSN` first (canonical), then common framework-prefixed + * variants (NEXT_PUBLIC_SENTRY_DSN, REACT_APP_SENTRY_DSN, etc.). * * @returns Detected DSN or null if not set/invalid * @@ -23,10 +38,19 @@ export const SENTRY_DSN_ENV = "SENTRY_DSN"; * // { raw: "...", source: "env", projectId: "456", ... } */ export function detectFromEnv(): DetectedDsn | null { - const dsn = getEnv()[SENTRY_DSN_ENV]; - if (!dsn) { - return null; + const env = getEnv(); + + const canonical = env[SENTRY_DSN_ENV]; + if (canonical) { + return createDetectedDsn(canonical, "env"); + } + + for (const varName of FRAMEWORK_DSN_VARS) { + const value = env[varName]; + if (value) { + return createDetectedDsn(value, "env"); + } } - return createDetectedDsn(dsn, "env"); + return null; } From f9e2abbda6ae1668e292c22c050e2b3352ded4c8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Apr 2026 12:38:09 +0000 Subject: [PATCH 4/4] test(init): set SENTRY_PLAIN_OUTPUT=0 in tests that spy on @clack/prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clack-plain adapter routes to plain-text handlers when isPlainOutput() is true (non-TTY in test environment). Tests that spy on the real clack namespace need rich mode to be active so the adapter delegates through to the spied functions. Co-authored-by: Miguel Betegón --- src/lib/init/clack-plain.ts | 80 +++++++++++++++++++---------- test/lib/init/formatters.test.ts | 9 ++++ test/lib/init/git.test.ts | 9 ++++ test/lib/init/interactive.test.ts | 9 ++++ test/lib/init/preflight.test.ts | 9 ++++ test/lib/init/spinner.test.ts | 16 +++++- test/lib/init/wizard-runner.test.ts | 9 ++++ 7 files changed, 112 insertions(+), 29 deletions(-) diff --git a/src/lib/init/clack-plain.ts b/src/lib/init/clack-plain.ts index 623a0d6f8..aad1d40ef 100644 --- a/src/lib/init/clack-plain.ts +++ b/src/lib/init/clack-plain.ts @@ -20,6 +20,10 @@ function plainWrite(message: string): void { process.stdout.write(`${stripAnsi(message)}\n`); } +function plainWriteErr(message: string): void { + process.stderr.write(`${stripAnsi(message)}\n`); +} + /** intro() — in plain mode, just print the title without box-drawing chars */ export function intro(title?: string): void { if (isPlainOutput()) { @@ -46,7 +50,7 @@ export function outro(message?: string): void { export function cancel(message?: string): void { if (isPlainOutput()) { if (message) { - process.stderr.write(`${stripAnsi(String(message))}\n`); + plainWriteErr(stripAnsi(String(message))); } return; } @@ -54,45 +58,65 @@ export function cancel(message?: string): void { } /** - * Plain-mode log object that mirrors clack's `log` API. - * Strips ANSI and writes to stdout/stderr without box-drawing characters. + * log — delegates to the real clack.log, but in plain mode strips ANSI + * and writes to stdout/stderr without box-drawing characters. */ -const plainLog: typeof clack.log = { +export const log: typeof clack.log = { message(message?: string, opts?: { symbol?: string }) { - if (message) { - const prefix = opts?.symbol ? `${stripAnsi(opts.symbol)} ` : ""; - plainWrite(`${prefix}${stripAnsi(message)}`); + if (isPlainOutput()) { + if (message) { + const prefix = opts?.symbol + ? `${stripAnsi(opts.symbol)} ` + : ""; + plainWrite(`${prefix}${stripAnsi(message)}`); + } + return; } + clack.log.message(message, opts); }, - info(message?: string) { - plainWrite(`INFO: ${stripAnsi(String(message ?? ""))}`); - }, - success(message?: string) { - plainWrite(`OK: ${stripAnsi(String(message ?? ""))}`); + info(message: string) { + if (isPlainOutput()) { + plainWrite(`INFO: ${stripAnsi(message)}`); + return; + } + clack.log.info(message); }, - step(message?: string) { - plainWrite(`> ${stripAnsi(String(message ?? ""))}`); + success(message: string) { + if (isPlainOutput()) { + plainWrite(`OK: ${stripAnsi(message)}`); + return; + } + clack.log.success(message); }, - warn(message?: string) { - process.stderr.write(`WARN: ${stripAnsi(String(message ?? ""))}\n`); + step(message: string) { + if (isPlainOutput()) { + plainWrite(`> ${stripAnsi(message)}`); + return; + } + clack.log.step(message); }, - warning(message?: string) { - process.stderr.write(`WARN: ${stripAnsi(String(message ?? ""))}\n`); + warn(message: string) { + if (isPlainOutput()) { + plainWriteErr(`WARN: ${stripAnsi(message)}`); + return; + } + clack.log.warn(message); }, - error(message?: string) { - process.stderr.write(`ERROR: ${stripAnsi(String(message ?? ""))}\n`); + warning(message: string) { + if (isPlainOutput()) { + plainWriteErr(`WARN: ${stripAnsi(message)}`); + return; + } + clack.log.warning(message); }, -}; - -/** log — routes to plain-text or clack based on output mode */ -export const log: typeof clack.log = new Proxy(clack.log, { - get(_target, prop: keyof typeof clack.log) { + error(message: string) { if (isPlainOutput()) { - return plainLog[prop]; + plainWriteErr(`ERROR: ${stripAnsi(message)}`); + return; } - return clack.log[prop]; + clack.log.error(message); }, -}); +}; export { isCancel } from "@clack/prompts"; export { confirm, select, multiselect } from "@clack/prompts"; diff --git a/test/lib/init/formatters.test.ts b/test/lib/init/formatters.test.ts index a306eeaba..80b6cb3bb 100644 --- a/test/lib/init/formatters.test.ts +++ b/test/lib/init/formatters.test.ts @@ -23,7 +23,11 @@ const noop = () => { /* suppress clack output */ }; +let savedPlainOutput: string | undefined; + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; logMessageSpy = spyOn(clack.log, "message").mockImplementation(noop); outroSpy = spyOn(clack, "outro").mockImplementation(noop); cancelSpy = spyOn(clack, "cancel").mockImplementation(noop); @@ -39,6 +43,11 @@ afterEach(() => { logInfoSpy.mockRestore(); logWarnSpy.mockRestore(); logErrorSpy.mockRestore(); + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } }); describe("formatResult", () => { diff --git a/test/lib/init/git.test.ts b/test/lib/init/git.test.ts index e7adfdfe8..ef6c65b0b 100644 --- a/test/lib/init/git.test.ts +++ b/test/lib/init/git.test.ts @@ -19,7 +19,11 @@ let confirmSpy: ReturnType; let isCancelSpy: ReturnType; let logWarnSpy: ReturnType; +let savedPlainOutput: string | undefined; + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; isInsideWorkTreeSpy = spyOn(gitLib, "isInsideGitWorkTree"); getUncommittedFilesSpy = spyOn(gitLib, "getUncommittedFiles"); confirmSpy = spyOn(clack, "confirm").mockResolvedValue(true); @@ -35,6 +39,11 @@ afterEach(() => { confirmSpy.mockRestore(); isCancelSpy.mockRestore(); logWarnSpy.mockRestore(); + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } }); describe("isInsideGitWorkTree", () => { diff --git a/test/lib/init/interactive.test.ts b/test/lib/init/interactive.test.ts index f0ff04851..d6c23db0d 100644 --- a/test/lib/init/interactive.test.ts +++ b/test/lib/init/interactive.test.ts @@ -34,7 +34,11 @@ function makeOptions( }; } +let savedPlainOutput: string | undefined; + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; selectSpy = spyOn(clack, "select").mockImplementation( () => Promise.resolve("default") as any ); @@ -62,6 +66,11 @@ afterEach(() => { logWarnSpy.mockRestore(); cancelSpy.mockRestore(); isCancelSpy.mockRestore(); + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } }); describe("handleInteractive dispatcher", () => { diff --git a/test/lib/init/preflight.test.ts b/test/lib/init/preflight.test.ts index 4828ba6f9..9484d4fa4 100644 --- a/test/lib/init/preflight.test.ts +++ b/test/lib/init/preflight.test.ts @@ -43,7 +43,11 @@ let resolveOrCreateTeamSpy: ReturnType; let detectDsnSpy: ReturnType; let resolveDsnByPublicKeySpy: ReturnType; +let savedPlainOutput: string | undefined; + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; selectSpy = spyOn(clack, "select").mockResolvedValue("existing"); isCancelSpy = spyOn(clack, "isCancel").mockImplementation( (value: unknown) => value === Symbol.for("cancel") @@ -98,6 +102,11 @@ afterEach(() => { detectDsnSpy.mockRestore(); resolveDsnByPublicKeySpy.mockRestore(); process.exitCode = 0; + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } }); describe("resolveInitContext", () => { diff --git a/test/lib/init/spinner.test.ts b/test/lib/init/spinner.test.ts index 4cade8547..2c9352386 100644 --- a/test/lib/init/spinner.test.ts +++ b/test/lib/init/spinner.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { Writable } from "node:stream"; import { createWizardSpinner } from "../../../src/lib/init/spinner.js"; @@ -37,7 +37,21 @@ function stripAnsi(value: string): string { return value.replace(ANSI_CSI_RE, "").replace(ANSI_OSC_RE, ""); } +let savedPlainOutput: string | undefined; + describe("createWizardSpinner", () => { + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; + }); + afterEach(() => { + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } + }); + test("clears upward when repainting a multiline status block", () => { const output = new CaptureStream(); const spin = createWizardSpinner(output as unknown as NodeJS.WriteStream); diff --git a/test/lib/init/wizard-runner.test.ts b/test/lib/init/wizard-runner.test.ts index d3f46a09c..5b3f12438 100644 --- a/test/lib/init/wizard-runner.test.ts +++ b/test/lib/init/wizard-runner.test.ts @@ -102,7 +102,11 @@ let stderrSpy: ReturnType; */ let capturedClientOptions: { abortSignal?: AbortSignal }[] = []; +let savedPlainOutput: string | undefined; + beforeEach(() => { + savedPlainOutput = process.env.SENTRY_PLAIN_OUTPUT; + process.env.SENTRY_PLAIN_OUTPUT = "0"; mockStartResult = { status: "success", result: { platform: "React" } }; mockResumeResults = []; resumeCallCount = 0; @@ -211,6 +215,11 @@ afterEach(() => { stderrSpy.mockRestore(); process.exitCode = 0; + if (savedPlainOutput === undefined) { + delete process.env.SENTRY_PLAIN_OUTPUT; + } else { + process.env.SENTRY_PLAIN_OUTPUT = savedPlainOutput; + } }); describe("runWizard", () => {