diff --git a/packages/cli/package.json b/packages/cli/package.json index 4c5150f99..84d5dfad8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -125,7 +125,6 @@ "binpatch": "^0.4.2", "chalk": "^5.6.2", "cli-highlight": "^2.1.11", - "consola": "^3.4.2", "esbuild": "^0.28.1", "fast-check": "^4.8.0", "fflate": "^0.8.3", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index b3f27257b..73f8accc1 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -41,7 +41,7 @@ async function preloadProjectContext(cwd: string): Promise { // Apply .sentryclirc env shim (token + URL). The URL trust check is // deferred to buildCommand's wrapper where commands can opt out via // skipRcUrlCheck (used by auth login/logout). - // Dynamic import: sentryclirc chains into db/index → sqlite, logger → consola + // Dynamic import: sentryclirc chains into db/index → sqlite and the logger const { applySentryCliRcEnvShim } = await import("./lib/sentryclirc.js"); await applySentryCliRcEnvShim(cwd); diff --git a/packages/cli/src/commands/issue/plan.ts b/packages/cli/src/commands/issue/plan.ts index f89cc3691..717a442c2 100644 --- a/packages/cli/src/commands/issue/plan.ts +++ b/packages/cli/src/commands/issue/plan.ts @@ -189,7 +189,7 @@ async function confirmRootCauseAndContinue( initial: true, }); - // consola returns a non-boolean (Symbol(clack:cancel)) when cancelled. + // The prompt returns a non-boolean (clack's cancel symbol) when cancelled. if (proceed === true) { return; } diff --git a/packages/cli/src/commands/log/view.ts b/packages/cli/src/commands/log/view.ts index f0a41e784..7e92c691f 100644 --- a/packages/cli/src/commands/log/view.ts +++ b/packages/cli/src/commands/log/view.ts @@ -271,7 +271,7 @@ function formatIdList(ids: string[]): string { /** * Warn about IDs that weren't found in the API response. - * Uses the consola logger for structured output to stderr. + * Uses the logger for structured output to stderr. * * @param logIds - All requested IDs * @param logs - Logs actually returned by the API @@ -310,7 +310,7 @@ async function handleWebOpen(orgSlug: string, logIds: string[]): Promise { type: "confirm", initial: false, }); - // consola prompt returns Symbol(clack:cancel) on Ctrl+C — a truthy value. + // The prompt returns Symbol(clack:cancel) on Ctrl+C — a truthy value. // Strictly check for `true` to avoid opening tabs on cancel. if (confirmed !== true) { return; diff --git a/packages/cli/src/lib/logger.ts b/packages/cli/src/lib/logger.ts index be4c598fe..94e5007ce 100644 --- a/packages/cli/src/lib/logger.ts +++ b/packages/cli/src/lib/logger.ts @@ -1,28 +1,31 @@ /** * Structured logging for Sentry CLI. * - * Built on {@link https://github.com/unjs/consola | consola} — a lightweight CLI logger - * with log levels, tag scoping, and fancy TTY output. Two reporters are wired up: - * - * 1. **FancyReporter** (built-in) — writes to stderr with colors/icons for TTY, - * falls back to BasicReporter in CI/non-TTY environments. Its `formatDate` is - * overridden (see {@link patchReporterDates}) so the `[tag HH:MM:SS]` prefix - * renders in corrected local time — consola's default renders UTC in the SEA + * A small in-house logger with log levels, tag scoping, and consola-style + * terminal output. It replaced `consola`, the largest third-party package that + * `sentry __complete` loaded on every TAB press. Each log call goes to every + * registered reporter: + * + * 1. **The stderr reporter** (built in) — renders consola's "fancy" layout + * (icons, colors, `WARN`/`ERROR` badges, a right-aligned `tag HH:MM:SS`) and + * falls back to plain `[type] [tag] message` lines in CI and tests. The time + * comes from {@link formatLogTime}, which stays in local time in the SEA * binaries this CLI ships. * 2. **Sentry.createConsolaReporter()** — auto-forwards all log messages to Sentry - * structured logs via `_INTERNAL_captureLog`. Library mode drops these logs - * in `beforeSendLog` (see telemetry.ts). + * structured logs via `_INTERNAL_captureLog`. Log records keep consola's + * `{ date, args, type, level, tag }` shape so that reporter still reads them. + * Library mode drops these logs in `beforeSendLog` (see telemetry.ts). * * ## Log levels * - * | Level | consola | Shows | - * |-------|---------|-------| - * | error | 0 | fatal, error | - * | warn | 1 | + warn | - * | log | 2 | + log | - * | info | 3 | + info, success, start, ready, fail (default) | - * | debug | 4 | + debug | - * | trace | 5 | + trace, verbose | + * | Level | Value | Shows | + * |-------|-------|-------| + * | error | 0 | error | + * | warn | 1 | + warn | + * | log | 2 | + log | + * | info | 3 | + info, success, start, fail (default) | + * | debug | 4 | + debug | + * | trace | 5 | + trace | * * ## Usage * @@ -44,23 +47,18 @@ * ## Environment variables * * - `SENTRY_LOG_LEVEL` — Sets log level: `error`, `warn`, `info` (default), `debug`, `trace` - * - `NO_COLOR` — Disables color output (respected by consola natively) + * - `NO_COLOR` — Disables color output * - `FORCE_COLOR` — Forces color output even in non-TTY * - * ## withTag level propagation - * - * Consola's `withTag()` creates an independent instance that snapshots the - * parent's level at creation time. Scoped loggers created at module load - * time (e.g. `const log = logger.withTag("upgrade")`) would miss later - * `setLogLevel()` calls. To fix this, the logger's `withTag` method is - * wrapped to register all children in a registry, and `setLogLevel()` - * propagates the level to every registered child. + * Every logger, including the scoped ones `withTag()` returns, shares one level + * and one reporter list, so a later `setLogLevel()` also reaches the scoped + * loggers that modules create at import time. * * @module */ -import type { ConsolaInstance, ConsolaReporter } from "consola"; -import { createConsola } from "consola"; +import { sep } from "node:path"; +import { formatWithOptions } from "node:util"; import { getEnv } from "./env.js"; import { formatLogTime } from "./timezone.js"; @@ -68,7 +66,7 @@ import { formatLogTime } from "./timezone.js"; /** * Environment variable name for controlling CLI log verbosity. * - * Using `SENTRY_LOG_LEVEL` (not `CONSOLA_LEVEL`) to be consistent with + * Using `SENTRY_LOG_LEVEL` (not a logger-specific name) to be consistent with * other Sentry CLI env vars (`SENTRY_URL`, `SENTRY_AUTH_TOKEN`, etc.). */ export const LOG_LEVEL_ENV_VAR = "SENTRY_LOG_LEVEL"; @@ -76,13 +74,13 @@ export const LOG_LEVEL_ENV_VAR = "SENTRY_LOG_LEVEL"; /** * Valid log level names accepted by `SENTRY_LOG_LEVEL` and `--log-level`. * - * Array index IS the consola numeric level: - * - 0 = error (includes fatal) + * Array index IS the numeric level: + * - 0 = error * - 1 = warn * - 2 = log - * - 3 = info (default — also shows success, start, ready, fail) + * - 3 = info (default — also shows success, start, fail) * - 4 = debug - * - 5 = trace (includes verbose) + * - 5 = trace */ export const LOG_LEVEL_NAMES = [ "error", @@ -98,18 +96,18 @@ export type LogLevelName = (typeof LOG_LEVEL_NAMES)[number]; /** * Default log level when nothing is configured. - * Level 3 shows info, success, start, ready, log, fail — standard user-facing output. + * Level 3 shows info, success, start, log, fail — standard user-facing output. */ const DEFAULT_LOG_LEVEL = 3; /** - * Parse a log level name string into a consola numeric level. + * Parse a log level name string into a numeric level. * * Returns the default level (info = 3) for unrecognized values so that * a typo in `SENTRY_LOG_LEVEL` doesn't break the CLI. * * @param name - Level name from env var or CLI flag - * @returns consola numeric level + * @returns numeric level */ export function parseLogLevel(name: string): number { const idx = LOG_LEVEL_NAMES.indexOf( @@ -124,7 +122,7 @@ export function parseLogLevel(name: string): number { * Following the same pattern as `getSentryUrl()` in oauth.ts — env vars are read * at call time (not module load time) so tests can set them after import. * - * @returns consola numeric level, or null if not set + * @returns numeric level, or null if not set */ export function getEnvLogLevel(): number | null { const envLevel = getEnv()[LOG_LEVEL_ENV_VAR]; @@ -134,15 +132,159 @@ export function getEnvLogLevel(): number | null { return null; } +/** Level of each log method: a call prints when it is at most the logger's level. */ +const LOG_TYPE_LEVELS = { + error: 0, + warn: 1, + log: 2, + info: 3, + success: 3, + fail: 3, + start: 3, + debug: 4, + trace: 5, +} as const; + +/** A log method name, such as `info` or `warn`. */ +type LogType = keyof typeof LOG_TYPE_LEVELS; + +/** + * One log call, in the shape consola reporters read, so the reporter from + * `Sentry.createConsolaReporter()` can forward it. + */ +export type LogObject = { + date: Date; + args: unknown[]; + type: LogType; + level: number; + tag: string; +}; + +/** Receives every log call at or below the current level. */ +export type LogReporter = { + log(logObj: LogObject): void; +}; + +/** Options for a yes/no prompt. */ +type ConfirmPromptOptions = { type: "confirm"; initial?: boolean }; + +/** Options for a free-text prompt. */ +type TextPromptOptions = { + type: "text"; + placeholder?: string; + initial?: string; + default?: string; +}; + +/** + * Set to `true` by esbuild in the `dist/complete.cjs` bundle (see + * `script/bundle.ts`), so the `@clack/prompts` import below stays out of the + * shell completion bundle. Undefined when running from source. + */ +declare const SENTRY_CLI_COMPLETION_BUILD: boolean | undefined; + /** - * Registry of all scoped loggers created via `logger.withTag()` at any depth. + * Ask the user a question on the terminal. * - * When `setLogLevel()` is called, every registered descendant is updated so that - * module-level scoped loggers (created at import time, before the CLI parses - * `--log-level` or `SENTRY_LOG_LEVEL`) see the new level. Grandchildren and - * deeper descendants are tracked via recursive `patchWithTag()`. + * Resolves to the answer, or to clack's cancel symbol when the user presses + * Ctrl+C, so callers check for `=== true` or `typeof answer === "string"`. */ -const scopedLoggers: ConsolaInstance[] = []; +function askUser( + message: string, + opts: ConfirmPromptOptions, +): Promise; +function askUser( + message: string, + opts: TextPromptOptions, +): Promise; +async function askUser( + message: string, + opts: ConfirmPromptOptions | TextPromptOptions, +): Promise { + if ( + typeof SENTRY_CLI_COMPLETION_BUILD !== "undefined" && + SENTRY_CLI_COMPLETION_BUILD + ) { + throw new Error("Prompts are not available during shell completion."); + } else { + // The `else` lets esbuild drop this import from the completion bundle. + // Elsewhere `@clack/prompts` loads only when a command asks a question. + const clack = await import("@clack/prompts"); + if (opts.type === "confirm") { + return await clack.confirm({ message, initialValue: opts.initial }); + } + return await clack.text({ + message, + placeholder: opts.placeholder, + initialValue: opts.initial, + defaultValue: opts.default, + }); + } +} + +/** Level and reporters shared by the root logger and every scoped logger. */ +const state: { level: number; reporters: LogReporter[] } = { + level: DEFAULT_LOG_LEVEL, + reporters: [{ log: writeLogToStderr }], +}; + +/** A logger, optionally scoped to a tag shown with each message. */ +class Logger { + /** Tag shown with each message; nested tags join with `:`. */ + readonly tag: string; + + // Arrow properties keep the methods bound, so they can be passed around. + readonly error = (...args: unknown[]): void => this.emit("error", args); + readonly warn = (...args: unknown[]): void => this.emit("warn", args); + readonly log = (...args: unknown[]): void => this.emit("log", args); + readonly info = (...args: unknown[]): void => this.emit("info", args); + readonly success = (...args: unknown[]): void => this.emit("success", args); + readonly fail = (...args: unknown[]): void => this.emit("fail", args); + readonly start = (...args: unknown[]): void => this.emit("start", args); + readonly debug = (...args: unknown[]): void => this.emit("debug", args); + readonly trace = (...args: unknown[]): void => this.emit("trace", args); + readonly prompt = askUser; + + constructor(tag = "") { + this.tag = tag; + } + + /** The shared level: calls above it are dropped. */ + get level(): number { + return state.level; + } + + set level(level: number) { + state.level = level; + } + + /** Return a logger whose messages carry `tag`, nested under this logger's tag. */ + withTag(tag: string): Logger { + return new Logger(this.tag ? `${this.tag}:${tag}` : tag); + } + + /** Send every later log call to `reporter` too. */ + addReporter(reporter: LogReporter): void { + state.reporters.push(reporter); + } + + private emit(type: LogType, args: unknown[]): void { + const level = LOG_TYPE_LEVELS[type]; + if (level > state.level) { + return; + } + const logObj: LogObject = { + date: new Date(), + args, + type, + level, + tag: this.tag, + }; + for (const reporter of state.reporters) { + reporter.log(logObj); + } + } +} /** * Global CLI logger instance. @@ -151,6 +293,9 @@ const scopedLoggers: ConsolaInstance[] = []; * Use `logger.debug()`, `logger.trace()` for diagnostic output. * Use `logger.withTag('scope')` for domain-specific scoped loggers. * + * All diagnostic/log output goes to stderr — stdout is reserved for command + * output (data, JSON, tables). + * * The Sentry reporter is added lazily via {@link attachSentryReporter} after * Sentry.init() completes, since the reporter needs an active Sentry client. * @@ -158,21 +303,14 @@ const scopedLoggers: ConsolaInstance[] = []; * by bin.ts calling `setLogLevel(getEnvLogLevel())` — not at module load time — * so tests can override the env var after import. */ -export const logger = createConsola({ - level: DEFAULT_LOG_LEVEL, - // All diagnostic/log output goes to stderr — stdout is reserved for - // command output (data, JSON, tables). Both streams must be set because - // consola's BasicReporter (non-TTY) routes debug/info to stdout by default. - stdout: process.stderr, - stderr: process.stderr, -}); +export const logger = new Logger(); /** - * Write an already-formatted line to stderr verbatim, bypassing consola. + * Write an already-formatted line to stderr verbatim, bypassing the logger. * * Event tails (`sentry local`) render their own prefix — an event timestamp, * level tag, and source tag — before printing. Routing those lines through - * `logger.log()` makes consola's reporter prepend/append a *second* timestamp + * `logger.log()` makes the reporter prepend/append a *second* timestamp * (the wall-clock print time), so every line carried two different times. * Writing directly keeps the single, meaningful event timestamp. * @@ -195,63 +333,6 @@ export function printJsonLine(line: string): void { process.stdout.write(`${line}\n`); } -/** - * Patch a consola instance's `withTag` so every child (and grandchild) - * is registered in {@link scopedLoggers} for {@link setLogLevel} propagation. - */ -function patchWithTag(instance: ConsolaInstance): void { - const original = instance.withTag.bind(instance); - instance.withTag = (tag: string): ConsolaInstance => { - const child = original(tag); - scopedLoggers.push(child); - patchWithTag(child); - return child; - }; -} - -// Patch the root logger so all descendants are tracked. -// Consola's withTag() creates a completely independent instance that -// snapshots the level at creation time — children never see later -// setLogLevel() calls. By registering them here, setLogLevel() can -// propagate the new level to all descendants. -patchWithTag(logger); - -/** - * A consola reporter exposes a `formatDate(date, opts)` method that produces - * the time shown in the `[tag HH:MM:SS]` log prefix. Consola does not export - * its `FancyReporter`/`BasicReporter` classes, so we type just the slice we - * override. - */ -type DateFormattingReporter = { - formatDate?: (date: Date, opts: unknown) => string; -}; - -/** - * Replace each default reporter's `formatDate` with {@link formatLogTime}. - * - * Consola's built-in reporters format the prefix time via - * `date.toLocaleTimeString()` with no arguments. That call renders in whatever - * timezone the runtime resolved — which silently falls back to **UTC** in the - * SEA binaries this CLI ships, so users saw log timestamps hours off from their - * local clock. Overriding `formatDate` guarantees the prefix uses the corrected - * local time (see {@link formatLogTime}) regardless of ICU state, and switches - * to an unambiguous 24-hour format. - * - * The instances are reused from `logger.options.reporters` so all of consola's - * other formatting (icons, colors, alignment, badges) is preserved untouched. - */ -function patchReporterDates(instance: ConsolaInstance): void { - const reporters = (instance.options?.reporters ?? - []) as DateFormattingReporter[]; - for (const reporter of reporters) { - if (typeof reporter.formatDate === "function") { - reporter.formatDate = (date: Date) => formatLogTime(date); - } - } -} - -patchReporterDates(logger); - /** Whether the Sentry reporter has already been attached */ let sentryReporterAttached = false; @@ -263,15 +344,15 @@ let sentryReporterAttached = false; * The caller creates the reporter so this module never imports `@sentry/node`: * the `__complete` fast path loads the logger through the database modules. * - * The reporter auto-forwards all consola log messages to Sentry structured logs, - * mapping consola types to Sentry severity levels: - * - fatal → fatal, error → error, warn → warn - * - info/log/success/ready/start → info - * - debug/verbose → debug, trace → trace + * The reporter auto-forwards all log messages to Sentry structured logs, + * mapping log types to Sentry severity levels: + * - error → error, fail → error, warn → warn + * - info/log/success/start → info + * - debug → debug, trace → trace * * Tags are preserved as `consola.tag` attributes in Sentry. */ -export function attachSentryReporter(reporter: ConsolaReporter): void { +export function attachSentryReporter(reporter: LogReporter): void { if (sentryReporterAttached) { return; } @@ -281,22 +362,225 @@ export function attachSentryReporter(reporter: ConsolaReporter): void { } /** - * Set the logger level at runtime and propagate to all scoped children. + * Set the level of the logger and every scoped logger at runtime. * * Called from: * - bin.ts to apply `SENTRY_LOG_LEVEL` env var early * - `buildCommand` wrapper when `--log-level` or `--verbose` flags are parsed * - * Propagation is necessary because consola's `withTag()` creates independent - * instances that snapshot the parent's level at creation time. Module-level - * scoped loggers (e.g. `const log = logger.withTag("upgrade")`) would never - * see a later level change without this. - * - * @param level - consola numeric level (0-5) + * @param level - numeric level (0-5) */ export function setLogLevel(level: number): void { - logger.level = level; - for (const child of scopedLoggers) { - child.level = level; + state.level = level; +} + +// stderr rendering + +/** SGR open and close codes for the styles the reporter uses. */ +const ANSI_CODES = { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + magenta: [35, 39], + cyan: [36, 39], + gray: [90, 39], + underline: [4, 24], + bgRed: [41, 49], + bgYellow: [43, 49], +} as const; + +type AnsiStyle = keyof typeof ANSI_CODES; + +/** Icon color per log type. The error and warn levels render as badges instead. */ +const TYPE_COLORS: Record = { + error: "red", + warn: "yellow", + log: "gray", + info: "cyan", + success: "green", + fail: "red", + start: "magenta", + debug: "gray", + trace: "gray", +}; + +/** Badge background for the error (0) and warn (1) levels. */ +const BADGE_BACKGROUNDS: Record = { + 0: "bgRed", + 1: "bgYellow", +}; + +/** Unicode icon per log type, and its ASCII fallback. */ +const TYPE_ICONS: Record = { + error: ["✖", "×"], + warn: ["⚠", "‼"], + log: ["", ""], + info: ["ℹ", "i"], + success: ["✔", "√"], + fail: ["✖", "×"], + start: ["◐", "o"], + debug: ["⚙", "D"], + trace: ["→", "→"], +}; + +/** Matches SGR escape sequences, to measure the visible width of a line. */ +// oxlint-disable-next-line no-control-regex -- matching ANSI escapes requires ESC +const ANSI_PATTERN = /\x1b\[[0-9;]*m/g; + +/** Inline `code` spans, rendered in cyan without their backticks. */ +const CODE_SPAN_PATTERN = /`([^`]+)`/g; + +/** ` _word_ ` spans, rendered underlined. */ +const UNDERLINE_PATTERN = /\s+_([^_]+)_\s+/g; + +/** Whether an env var is set to something other than `""` or `"false"`. */ +function isEnabled(value: string | undefined): boolean { + return Boolean(value) && value !== "false"; +} + +/** + * Whether to render the fancy layout. CI logs and test runs get plain + * `[type] [tag] message` lines instead, as they did with consola. + */ +function useFancyLayout(): boolean { + const env = getEnv(); + return !(isEnabled(env.CI) || env.NODE_ENV === "test" || isEnabled(env.TEST)); +} + +/** Whether stderr takes ANSI colors: `NO_COLOR`, then `FORCE_COLOR`, then TTY detection. */ +function useColor(): boolean { + const env = getEnv(); + if (env.NO_COLOR) { + return false; } + if (env.FORCE_COLOR) { + return env.FORCE_COLOR !== "0"; + } + return Boolean(process.stderr.isTTY) && env.TERM !== "dumb"; +} + +/** Whether the terminal can draw the Unicode icons. */ +function supportsUnicode(): boolean { + const env = getEnv(); + if (process.platform !== "win32") { + return env.TERM !== "linux"; + } + return ( + Boolean(env.WT_SESSION) || + env.TERM_PROGRAM === "vscode" || + env.TERM === "xterm-256color" || + env.TERM === "alacritty" || + env.TERMINAL_EMULATOR === "JetBrains-JediTerm" + ); +} + +/** + * Approximate visible width of a line: its length without ANSI escapes. Wide + * characters count as one column, which only shifts the right-aligned tag. + */ +function visibleWidth(text: string): number { + return text.replace(ANSI_PATTERN, "").length; +} + +/** Options for formatting log arguments, as consola passed them. */ +const FORMAT_OPTIONS = { colors: false, compact: true } as const; + +/** An `Error`, or anything else carrying a stack. */ +type ErrorLike = { message?: unknown; stack: string; cause?: unknown }; + +function isErrorLike(value: unknown): value is ErrorLike { + return ( + typeof value === "object" && + value !== null && + typeof (value as { stack?: unknown }).stack === "string" + ); +} + +/** + * Render an error as its message, its stack frames, and its causes, as consola + * did. Unlike `util.inspect`, this leaves out the error's other properties, + * which can hold API response details. + */ +function formatError(error: ErrorLike, depth: number): string { + const message = + typeof error.message === "string" + ? error.message + : formatWithOptions(FORMAT_OPTIONS, error); + const indent = " ".repeat(depth + 1); + const cwd = process.cwd() + sep; + const frames = error.stack + .split("\n") + .slice(message.split("\n").length) + .map( + (frame) => indent + frame.trim().replace("file://", "").replace(cwd, ""), + ) + .join("\n"); + const prefix = depth > 0 ? `${" ".repeat(depth)}[cause]: ` : ""; + let cause = ""; + if (isErrorLike(error.cause)) { + cause = `\n\n${formatError(error.cause, depth + 1)}`; + } else if (error.cause) { + cause = `\n\n${" ".repeat(depth + 1)}[cause]: ${formatWithOptions(FORMAT_OPTIONS, error.cause)}`; + } + return `${prefix}${message}\n${frames}${cause}`; +} + +/** Render `logObj` as terminal lines, without a trailing newline. */ +function formatLogObj(logObj: LogObject): string { + const message = formatWithOptions( + FORMAT_OPTIONS, + ...logObj.args.map((arg) => (isErrorLike(arg) ? formatError(arg, 0) : arg)), + ); + if (!useFancyLayout()) { + return [`[${logObj.type}]`, logObj.tag && `[${logObj.tag}]`, message] + .filter(Boolean) + .join(" "); + } + + const color = useColor(); + const paint = (style: AnsiStyle, text: string): string => { + if (!(color && text)) { + return text; + } + const [open, close] = ANSI_CODES[style]; + return `\x1b[${open}m${text}\x1b[${close}m`; + }; + const markup = (text: string): string => + text + .replace(CODE_SPAN_PATTERN, (_, code: string) => paint("cyan", code)) + .replace( + UNDERLINE_PATTERN, + (_, word: string) => ` ${paint("underline", word)} `, + ); + + const [firstLine = "", ...additional] = message.split("\n"); + const badge = BADGE_BACKGROUNDS[logObj.level]; + const type = badge + ? paint(badge, paint("black", ` ${logObj.type.toUpperCase()} `)) + : paint( + TYPE_COLORS[logObj.type], + TYPE_ICONS[logObj.type][supportsUnicode() ? 0 : 1], + ); + + const columns = process.stderr.columns || 0; + const tag = logObj.tag ? paint("gray", logObj.tag) : ""; + const date = columns ? paint("gray", formatLogTime(logObj.date)) : ""; + const left = [type, markup(firstLine)].filter(Boolean).join(" "); + const right = [tag, date].filter(Boolean).join(" "); + const space = columns - visibleWidth(left) - visibleWidth(right) - 2; + + let line = + space > 0 && columns >= 80 + ? left + " ".repeat(space) + right + : (right ? `${paint("gray", `[${right}]`)} ` : "") + left; + if (additional.length > 0) { + line += markup(`\n${additional.join("\n")}`); + } + return badge ? `\n${line}\n` : line; +} + +/** The built-in reporter: render the log call and write it to stderr in one write. */ +function writeLogToStderr(logObj: LogObject): void { + process.stderr.write(`${formatLogObj(logObj)}\n`); } diff --git a/packages/cli/src/lib/mutate-command.ts b/packages/cli/src/lib/mutate-command.ts index baed2da39..e99ee1c1a 100644 --- a/packages/cli/src/lib/mutate-command.ts +++ b/packages/cli/src/lib/mutate-command.ts @@ -208,7 +208,7 @@ export async function confirmByTyping( placeholder: expected, }); - // consola prompt returns Symbol(clack:cancel) on Ctrl+C — a truthy value. + // The prompt returns Symbol(clack:cancel) on Ctrl+C — a truthy value. // Check type to avoid treating cancel as a valid response. if (typeof response !== "string") { return false; diff --git a/packages/cli/src/lib/polling.ts b/packages/cli/src/lib/polling.ts index fb411dc11..776d28f19 100644 --- a/packages/cli/src/lib/polling.ts +++ b/packages/cli/src/lib/polling.ts @@ -120,7 +120,7 @@ export async function poll(options: PollOptions): Promise { /** * Start an animated spinner that writes progress to stdout. * - * Uses stdout so the spinner doesn't collide with consola log messages + * Uses stdout so the spinner doesn't collide with logger messages * on stderr. The spinner is erased before command output is written, * and is suppressed entirely in JSON mode and when stdout is not a TTY. * @@ -188,7 +188,7 @@ export type WithProgressOptions = { * * The spinner uses the same braille frames as the Seer polling spinner, * giving a consistent look across all CLI commands. Progress output goes - * to stdout so it doesn't collide with consola log messages on stderr. + * to stdout so it doesn't collide with logger messages on stderr. * * The spinner is suppressed when: * - `options.json` is true (JSON mode — no ANSI noise for agents/CI) diff --git a/packages/cli/src/lib/seer-trial.ts b/packages/cli/src/lib/seer-trial.ts index 45f20ed85..c18204ab1 100644 --- a/packages/cli/src/lib/seer-trial.ts +++ b/packages/cli/src/lib/seer-trial.ts @@ -60,7 +60,7 @@ export function isTrialEligible(error: unknown): error is SeerError { * 2. Show context message + prompt user for confirmation * 3. Start the trial via API * - * Uses consola logger for all output (not raw stderr writes). + * Uses the logger for all output (not raw stderr writes). * * @param orgSlug - Organization slug * @param reason - The SeerError reason (for context message) diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 9a473c8d9..bfef24f53 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -786,7 +786,7 @@ export function initSentry( }); } - // Wire up consola → Sentry log forwarding now that the client is active + // Wire up logger → Sentry log forwarding now that the client is active attachSentryReporter(Sentry.createConsolaReporter()); // End healthy sessions and flush events when the event loop drains. diff --git a/packages/cli/src/lib/timezone.ts b/packages/cli/src/lib/timezone.ts index aa06d8b99..7cdf7299c 100644 --- a/packages/cli/src/lib/timezone.ts +++ b/packages/cli/src/lib/timezone.ts @@ -3,8 +3,8 @@ * * ## Why this exists * - * All human-facing timestamps in the CLI — the `[tag 6:28:59 AM]` log prefix - * emitted by consola, and every `toLocaleString()`/`toLocaleTimeString()` call + * All human-facing timestamps in the CLI — the `[tag 06:28:59]` log prefix + * emitted by the logger, and every `toLocaleString()`/`toLocaleTimeString()` call * in the formatters — are rendered by the JavaScript runtime using its notion * of the "local" timezone. That notion comes from ICU/`process.env.TZ`. * @@ -257,7 +257,7 @@ export function initTimezone(): string | null { /** * Format a `Date` as a local-time clock string for the log prefix. * - * Renders 24-hour `HH:MM:SS` in the machine's local timezone. Unlike consola's + * Renders 24-hour `HH:MM:SS` in the machine's local timezone. Unlike the * default `date.toLocaleTimeString()` this does **not** depend on ICU being * able to resolve a zone: it derives the local wall-clock time from the UTC * time and `getTimezoneOffset()`, which reflects the OS offset even in runtimes diff --git a/packages/cli/test/commands/auth/login.test.ts b/packages/cli/test/commands/auth/login.test.ts index 6a63a465b..e65019257 100644 --- a/packages/cli/test/commands/auth/login.test.ts +++ b/packages/cli/test/commands/auth/login.test.ts @@ -915,7 +915,7 @@ describe("login re-authentication interactive prompt", () => { test("cancel (Ctrl+C): returns without re-auth", async () => { isAuthenticatedSpy.mockReturnValue(true); getUserInfoSpy.mockReturnValue(undefined); - // consola returns Symbol(clack:cancel) on Ctrl+C — truthy but not `true`. + // The prompt returns Symbol(clack:cancel) on Ctrl+C — truthy but not `true`. mockPrompt.mockResolvedValue(Symbol("clack:cancel")); const context = createPromptContext(); diff --git a/packages/cli/test/commands/cli.test.ts b/packages/cli/test/commands/cli.test.ts index 7506f217f..df97de88a 100644 --- a/packages/cli/test/commands/cli.test.ts +++ b/packages/cli/test/commands/cli.test.ts @@ -3,7 +3,7 @@ * * Tests for the sentry cli command group. * - * Progress messages go through consola (→ process.stderr). Final results are + * Progress messages go through the logger (→ process.stderr). Final results are * returned as structured data and rendered to stdout by the output system. * Tests capture both stderr (progress) and stdout (results) to verify behavior. */ @@ -16,7 +16,7 @@ import { extractInputUrl } from "../../src/lib/fetch-input.js"; /** * Create a mock context with a process.stderr.write spy for capturing - * consola output, plus stdout capture for structured output. + * logger output, plus stdout capture for structured output. */ function createMockContext(overrides: Partial<{ execPath: string }> = {}): { context: Record; @@ -29,7 +29,7 @@ function createMockContext(overrides: Partial<{ execPath: string }> = {}): { const stdoutChunks: string[] = []; const errors: string[] = []; - // Capture consola output (routed to process.stderr) + // Capture logger output (routed to process.stderr) const origWrite = process.stderr.write.bind(process.stderr); process.stderr.write = ((chunk: string | Uint8Array) => { stderrChunks.push(String(chunk)); diff --git a/packages/cli/test/commands/cli/fix.test.ts b/packages/cli/test/commands/cli/fix.test.ts index 87ece5ce2..6dd5ec002 100644 --- a/packages/cli/test/commands/cli/fix.test.ts +++ b/packages/cli/test/commands/cli/fix.test.ts @@ -88,7 +88,7 @@ function createContext() { }, stderr: { write: vi.fn((_s: string) => { - /* consola routes here — not used for structured output */ + /* the logger routes here — not used for structured output */ }), }, process: { exitCode: 0 }, diff --git a/packages/cli/test/commands/cli/setup.test.ts b/packages/cli/test/commands/cli/setup.test.ts index ba03d92db..37a482720 100644 --- a/packages/cli/test/commands/cli/setup.test.ts +++ b/packages/cli/test/commands/cli/setup.test.ts @@ -3,7 +3,7 @@ * * Tests the `sentry cli setup` command end-to-end through Stricli's run(). * - * Status messages go through consola (→ process.stderr). Tests capture stderr + * Status messages go through the logger (→ process.stderr). Tests capture stderr * via a spy on process.stderr.write and assert on the collected output. */ @@ -86,11 +86,11 @@ function mockFetch( } /** - * Create a mock Stricli context and a stderr capture for consola output. + * Create a mock Stricli context and a stderr capture for logger output. * * The context provides process/env stubs for the setup command, while - * `getOutput()` returns the combined consola output captured from - * `process.stderr.write` (where consola routes all messages). + * `getOutput()` returns the combined logger output captured from + * `process.stderr.write` (where the logger routes all messages). */ function createMockContext( overrides: Partial<{ diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts index ee57b7976..7f94e8fc9 100644 --- a/packages/cli/test/commands/cli/upgrade.test.ts +++ b/packages/cli/test/commands/cli/upgrade.test.ts @@ -5,7 +5,7 @@ * Covers resolveTargetVersion branches (check mode, already up-to-date, * version validation) and error paths. * - * Status messages go through consola (→ process.stderr). Tests capture stderr + * Status messages go through the logger (→ process.stderr). Tests capture stderr * via a spy on process.stderr.write and assert on the collected output. */ @@ -87,7 +87,7 @@ function mockFetch( /** * Create a mock Stricli context with stderr and stdout capture. * - * `getOutput()` returns **both** consola output (stderr) and structured + * `getOutput()` returns **both** logger output (stderr) and structured * output (stdout) combined, so assertions work regardless of whether * a message is a progress log or a rendered result. * `errors` captures Stricli error output written to context.stderr. @@ -121,7 +121,7 @@ function createMockContext( const origPlain = process.env.SENTRY_PLAIN_OUTPUT; process.env.SENTRY_PLAIN_OUTPUT = "0"; - // Capture consola output (routed to process.stderr) + // Capture logger output (routed to process.stderr) const origStderrWrite = process.stderr.write.bind(process.stderr); process.stderr.write = ((chunk: string | Uint8Array) => { stderrChunks.push(String(chunk)); diff --git a/packages/cli/test/commands/event/view.test.ts b/packages/cli/test/commands/event/view.test.ts index 5a186f29c..baf2b266c 100644 --- a/packages/cli/test/commands/event/view.test.ts +++ b/packages/cli/test/commands/event/view.test.ts @@ -1135,7 +1135,7 @@ describe("viewCommand.func", () => { "test-org/test-proj", ); - // Command should complete without error (warning goes to consola, not stdout) + // Command should complete without error (warning goes to the logger, not stdout) expect(getEventSpy).toHaveBeenCalled(); }); diff --git a/packages/cli/test/commands/issue/merge.func.test.ts b/packages/cli/test/commands/issue/merge.func.test.ts index ad90cc39f..a61e27c59 100644 --- a/packages/cli/test/commands/issue/merge.func.test.ts +++ b/packages/cli/test/commands/issue/merge.func.test.ts @@ -472,7 +472,7 @@ describe("mergeCommand.func()", () => { // User asked for CLI-B, but Sentry picked CLI-A (e.g. larger by count) mergeSpy.mockResolvedValue({ parent: "10A", children: ["10B"] }); - // Warning now goes through log.warn() (consola → process.stderr), not + // Warning now goes through log.warn() (logger → process.stderr), not // this.stderr.write(). Spy on process.stderr.write to capture it. const stderrSpy = vi.spyOn(process.stderr, "write"); try { diff --git a/packages/cli/test/commands/issue/plan.func.test.ts b/packages/cli/test/commands/issue/plan.func.test.ts index e6dcd7b4a..e3773e832 100644 --- a/packages/cli/test/commands/issue/plan.func.test.ts +++ b/packages/cli/test/commands/issue/plan.func.test.ts @@ -130,7 +130,7 @@ describe("issue plan waiting-for-user-response", () => { test("throws when the user cancels the prompt", async () => { vi.mocked(isatty).mockReturnValue(true); analyzeSpy.mockResolvedValue(waitingState([sampleCause(0, "A cause")])); - // consola returns a cancel symbol (non-boolean) when cancelled. + // The prompt returns a cancel symbol (non-boolean) when cancelled. promptSpy.mockResolvedValue(Symbol("clack:cancel")); await expect(runPlan(createMockContext())).rejects.toBeInstanceOf(CliError); diff --git a/packages/cli/test/commands/log/list.test.ts b/packages/cli/test/commands/log/list.test.ts index 0a4ff21e5..5748b6cb2 100644 --- a/packages/cli/test/commands/log/list.test.ts +++ b/packages/cli/test/commands/log/list.test.ts @@ -959,7 +959,7 @@ describe("listCommand.func — trace mode org resolution failure", () => { /** * Collect all output written to a `process.stderr.write` spy. - * Handles both string and Buffer arguments from consola/logger. + * Handles both string and Buffer arguments from the logger. */ function collectProcessStderr( spy: ReturnType>, diff --git a/packages/cli/test/commands/log/view.func.test.ts b/packages/cli/test/commands/log/view.func.test.ts index 8283f48d7..a804ff3a0 100644 --- a/packages/cli/test/commands/log/view.func.test.ts +++ b/packages/cli/test/commands/log/view.func.test.ts @@ -244,7 +244,7 @@ describe("viewCommand.func", () => { }); test("still outputs found logs when some IDs are missing", async () => { - // Only ID1 found, ID2 and ID3 missing — warning goes through consola + // Only ID1 found, ID2 and ID3 missing — warning goes through the logger getLogsSpy.mockResolvedValue([makeSampleLog(ID1)]); const { context, stdoutWrite } = createMockContext(); diff --git a/packages/cli/test/commands/log/view.test.ts b/packages/cli/test/commands/log/view.test.ts index 502c34aae..ce6b6a360 100644 --- a/packages/cli/test/commands/log/view.test.ts +++ b/packages/cli/test/commands/log/view.test.ts @@ -732,8 +732,8 @@ describe("log view --web interactive prompt", () => { }); test("aborts when user cancels prompt with Ctrl+C (truthy Symbol)", async () => { - // consola returns Symbol(clack:cancel) on Ctrl+C — truthy but not `true`. - // Cast needed because the mock is typed as boolean but consola actually + // The prompt returns Symbol(clack:cancel) on Ctrl+C — truthy but not `true`. + // Cast needed because the mock is typed as boolean but the prompt actually // returns a Symbol on cancel. mockPrompt.mockResolvedValue(Symbol("clack:cancel") as unknown as boolean); openInBrowserSpy.mockResolvedValue(undefined); diff --git a/packages/cli/test/commands/project/delete.test.ts b/packages/cli/test/commands/project/delete.test.ts index 151469704..73456c37f 100644 --- a/packages/cli/test/commands/project/delete.test.ts +++ b/packages/cli/test/commands/project/delete.test.ts @@ -491,7 +491,7 @@ describe("project delete — interactive confirmation", () => { }); test("cancels when user presses Ctrl+C (Symbol)", async () => { - // consola returns Symbol(clack:cancel) on Ctrl+C — truthy but not a string. + // The prompt returns Symbol(clack:cancel) on Ctrl+C — truthy but not a string. mockPrompt.mockResolvedValue(Symbol("clack:cancel")); const { context } = createPromptMockContext(); diff --git a/packages/cli/test/commands/trace/view.func.test.ts b/packages/cli/test/commands/trace/view.func.test.ts index 20c35834b..5b53aa64d 100644 --- a/packages/cli/test/commands/trace/view.func.test.ts +++ b/packages/cli/test/commands/trace/view.func.test.ts @@ -717,7 +717,7 @@ describe("viewCommand.func", () => { "test-org/test-project", ); - // Command should complete (warning goes to consola, not stdout) + // Command should complete (warning goes to the logger, not stdout) expect(getDetailedTraceSpy).toHaveBeenCalled(); }); diff --git a/packages/cli/test/e2e/auth.test.ts b/packages/cli/test/e2e/auth.test.ts index 1c401fe4e..678ccb501 100644 --- a/packages/cli/test/e2e/auth.test.ts +++ b/packages/cli/test/e2e/auth.test.ts @@ -60,7 +60,7 @@ describe("sentry auth status", () => { const result = await ctx.run(["auth", "status"]); - // Status messages go to stderr via consola + // Status messages go to stderr via the logger const output = result.stdout + result.stderr; expect(output).toContain("Authenticated"); expect(result.exitCode).toBe(0); @@ -72,7 +72,7 @@ describe("sentry auth status", () => { const result = await ctx.run(["auth", "status"]); expect(result.exitCode).toBe(0); - // Status messages go to stderr via consola + // Status messages go to stderr via the logger const output = result.stdout + result.stderr; expect(output).toContain("Authenticated"); expect(output).toContain("Access verified"); @@ -90,7 +90,7 @@ describe("sentry auth login --token", () => { ctx.serverUrl, ]); - // Login messages go to stderr via consola + // Login messages go to stderr via the logger const output = result.stdout + result.stderr; expect(output).toContain("Authenticated"); expect(result.exitCode).toBe(0); @@ -233,7 +233,7 @@ describe("sentry auth logout", () => { const result = await ctx.run(["auth", "logout"]); expect(result.exitCode).toBe(0); - // Logout messages go to stderr via consola + // Logout messages go to stderr via the logger const logoutOutput = result.stdout + result.stderr; expect(logoutOutput).toMatch(/logged out/i); diff --git a/packages/cli/test/lib/auth-hint.test.ts b/packages/cli/test/lib/auth-hint.test.ts index 3c06746ed..e2d7b60c8 100644 --- a/packages/cli/test/lib/auth-hint.test.ts +++ b/packages/cli/test/lib/auth-hint.test.ts @@ -64,10 +64,10 @@ afterEach(() => { }); /** - * Capture stderr output from consola's default reporter. + * Capture stderr output from the logger's stderr reporter. * * We can't reliably spy on `logger.withTag("auth").info` from a test - * because each `withTag()` call returns a fresh consola instance — the + * because each `withTag()` call returns a fresh logger instance — the * spy target and the instance used inside `auth-hint.ts` are * different objects. Hooking `process.stderr.write` captures the final * rendered output regardless of which instance emitted it. diff --git a/packages/cli/test/lib/logger.test.ts b/packages/cli/test/lib/logger.test.ts index 06ee25eb3..98305916e 100644 --- a/packages/cli/test/lib/logger.test.ts +++ b/packages/cli/test/lib/logger.test.ts @@ -12,12 +12,20 @@ import { getEnvLogLevel, LOG_LEVEL_ENV_VAR, LOG_LEVEL_NAMES, + type LogObject, logger, parseLogLevel, printLine, setLogLevel, } from "../../src/lib/logger.js"; +const clack = vi.hoisted(() => ({ + confirm: vi.fn(), + text: vi.fn(), +})); + +vi.mock("@clack/prompts", () => clack); + describe("parseLogLevel", () => { test("maps known level names to correct numeric values", () => { expect(parseLogLevel("error")).toBe(0); @@ -49,7 +57,7 @@ describe("parseLogLevel", () => { }); describe("LOG_LEVEL_NAMES", () => { - test("contains all expected level names with index = consola numeric level", () => { + test("contains all expected level names with index = numeric level", () => { expect(LOG_LEVEL_NAMES).toEqual([ "error", // 0 "warn", // 1 @@ -185,7 +193,7 @@ describe("setLogLevel", () => { }); describe("logger instance", () => { - test("is a consola instance with expected methods", () => { + test("exposes the log methods", () => { expect(typeof logger.info).toBe("function"); expect(typeof logger.debug).toBe("function"); expect(typeof logger.warn).toBe("function"); @@ -282,3 +290,140 @@ describe("attachSentryReporter", () => { expect(() => attachSentryReporter(createConsolaReporter())).not.toThrow(); }); }); + +describe("reporters", () => { + let originalLevel: number; + let records: LogObject[]; + let stderr: ReturnType; + + beforeEach(() => { + originalLevel = logger.level; + records = []; + stderr = vi.spyOn(process.stderr, "write").mockImplementation(() => true); + }); + + afterEach(() => { + setLogLevel(originalLevel); + stderr.mockRestore(); + }); + + test("receive consola-shaped records that Sentry's reporter reads", () => { + // The level and reporter list are shared, so a reporter added through the + // root logger also sees records from scoped loggers. + logger.addReporter({ log: (logObj) => records.push(logObj) }); + const scoped = logger.withTag("upgrade").withTag("delta"); + setLogLevel(3); + + scoped.warn("Patch %s failed", "0.13.0"); + scoped.debug("dropped above the level"); + + expect(records).toEqual([ + { + date: expect.any(Date), + args: ["Patch %s failed", "0.13.0"], + type: "warn", + level: 1, + tag: "upgrade:delta", + }, + ]); + }); +}); + +describe("stderr output", () => { + const ENV_KEYS = ["CI", "TEST", "NODE_ENV", "NO_COLOR", "FORCE_COLOR"]; + let savedEnv: Record; + let stderr: ReturnType; + + /** Text written to stderr so far. */ + const written = () => + stderr.mock.calls.map((call) => String(call[0])).join(""); + + beforeEach(() => { + savedEnv = Object.fromEntries( + ENV_KEYS.map((key) => [key, process.env[key]]), + ); + stderr = vi.spyOn(process.stderr, "write").mockImplementation(() => true); + }); + + afterEach(() => { + for (const key of ENV_KEYS) { + if (savedEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = savedEnv[key]; + } + } + stderr.mockRestore(); + }); + + test("writes plain [type] [tag] lines in tests and CI", () => { + process.env.CI = "true"; + logger.withTag("auth").info("Logged in as %s", "jane"); + expect(written()).toBe("[info] [auth] Logged in as jane\n"); + }); + + test("writes an error's message, stack, and cause but not its other properties", () => { + process.env.CI = "true"; + const error = Object.assign( + new Error("Request failed", { cause: new Error("socket hang up") }), + { response: { headers: { authorization: "Bearer secret" } } }, + ); + + logger.warn("Upload failed:", error); + + const output = written(); + expect(output).toMatch(/^\[warn\] Upload failed: Request failed\n {2}at /); + expect(output).toContain("\n\n [cause]: socket hang up\n at "); + expect(output).not.toContain("secret"); + }); + + test("writes icons, badges, and inline code in the interactive layout", () => { + for (const key of ["CI", "TEST"]) { + delete process.env[key]; + } + process.env.NODE_ENV = "production"; + process.env.NO_COLOR = "1"; + + logger.withTag("auth").success("Run `sentry auth status`"); + logger.warn("Token expires soon"); + + // Non-TTY stderr has no columns, so the tag leads the line. + expect(written()).toBe( + "[auth] ✔ Run sentry auth status\n\n WARN Token expires soon\n\n", + ); + }); +}); + +describe("prompt", () => { + test("returns clack's cancel symbol instead of the initial value on Ctrl+C", async () => { + const cancel = Symbol("clack:cancel"); + clack.confirm.mockResolvedValue(cancel); + + const answer = await logger.prompt("Import from .sentryclirc?", { + type: "confirm", + initial: true, + }); + + expect(clack.confirm).toHaveBeenCalledWith({ + message: "Import from .sentryclirc?", + initialValue: true, + }); + expect(answer).toBe(cancel); + }); + + test("passes text prompt options through to clack", async () => { + clack.text.mockResolvedValue("my-project"); + + const answer = await logger + .withTag("delete") + .prompt("Type the project slug:", { type: "text", placeholder: "slug" }); + + expect(clack.text).toHaveBeenCalledWith({ + message: "Type the project slug:", + placeholder: "slug", + initialValue: undefined, + defaultValue: undefined, + }); + expect(answer).toBe("my-project"); + }); +}); diff --git a/packages/cli/test/lib/seer-trial.test.ts b/packages/cli/test/lib/seer-trial.test.ts index 29728c305..bb3d73647 100644 --- a/packages/cli/test/lib/seer-trial.test.ts +++ b/packages/cli/test/lib/seer-trial.test.ts @@ -178,7 +178,7 @@ describe("promptAndStartTrial", () => { test("returns false when user cancels with Ctrl+C (Symbol)", async () => { getProductTrialsSpy.mockResolvedValue([MOCK_SEER_TRIAL]); - // consola returns Symbol(clack:cancel) on Ctrl+C + // The prompt returns Symbol(clack:cancel) on Ctrl+C loggerPromptSpy.mockResolvedValue(Symbol("clack:cancel")); const result = await promptAndStartTrial("test-org", "no_budget"); diff --git a/packages/cli/test/script/cli-startup.test.ts b/packages/cli/test/script/cli-startup.test.ts index 6f55bd444..adf72cbb1 100644 --- a/packages/cli/test/script/cli-startup.test.ts +++ b/packages/cli/test/script/cli-startup.test.ts @@ -22,8 +22,6 @@ const COMPLETION_PACKAGES = new Set([ "@sentry/toolkit-core/auth-token", // `errors.ts` (exit codes) "@stricli/core", - // `logger.ts`, which the database modules log through - "consola", // `db/accounts.ts` account IDs "uuidv7", ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a4b2895a..34b47fec2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -537,9 +537,6 @@ importers: cli-highlight: specifier: ^2.1.11 version: 2.1.11 - consola: - specifier: ^3.4.2 - version: 3.4.2 esbuild: specifier: ^0.28.1 version: 0.28.1 @@ -5861,10 +5858,6 @@ packages: resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} engines: {node: '>= 18'} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@2.0.1: resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} engines: {node: '>=18'} @@ -14165,8 +14158,6 @@ snapshots: common-ancestor-path@2.0.0: {} - consola@3.4.2: {} - content-disposition@2.0.1: {} content-type@1.0.5: {}