diff --git a/biome.jsonc b/biome.jsonc index 87c1bfa4..a41a91c1 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,6 +1,10 @@ { "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "extends": ["ultracite/core"], + "plugins": [ + "./lint-rules/no-stdout-write-in-commands.grit", + "./lint-rules/no-process-stdout-in-commands.grit" + ], "files": { "includes": ["!docs", "!test/init-eval/templates"] }, @@ -77,6 +81,31 @@ } } } + }, + { + // Commands must use colorTag() from markdown.ts — not raw chalk. + // Raw chalk bypasses the plain-output pipeline (NO_COLOR, SENTRY_PLAIN_OUTPUT). + "includes": ["src/commands/**/*.ts"], + "linter": { + "rules": { + "style": { + "noRestrictedImports": { + "level": "error", + "options": { + "paths": { + "@stricli/core": { + "importNames": ["buildCommand"], + "message": "Import buildCommand from '../lib/command.js' instead. The wrapper injects telemetry, --log-level, and --verbose." + }, + "chalk": { + "message": "Use colorTag() from formatters/markdown.js for colored output. Raw chalk bypasses the plain-output pipeline (NO_COLOR, SENTRY_PLAIN_OUTPUT)." + } + } + } + } + } + } + } } ] } diff --git a/lint-rules/no-process-stdout-in-commands.grit b/lint-rules/no-process-stdout-in-commands.grit new file mode 100644 index 00000000..a70a27f6 --- /dev/null +++ b/lint-rules/no-process-stdout-in-commands.grit @@ -0,0 +1,5 @@ +file($name, $body) where { + $name <: r".*src/commands/.*", + $body <: contains `process.stdout` as $access, + register_diagnostic(span=$access, message="Don't use process.stdout in commands. Yield CommandOutput and let the buildCommand wrapper handle output.") +} diff --git a/lint-rules/no-stdout-write-in-commands.grit b/lint-rules/no-stdout-write-in-commands.grit new file mode 100644 index 00000000..ceb328f8 --- /dev/null +++ b/lint-rules/no-stdout-write-in-commands.grit @@ -0,0 +1,5 @@ +file($name, $body) where { + $name <: r".*src/commands/.*", + $body <: contains `stdout.write($args)` as $call, + register_diagnostic(span=$call, message="Don't call stdout.write() in commands. Yield CommandOutput instead and let the buildCommand wrapper handle output rendering.") +} diff --git a/src/commands/issue/view.ts b/src/commands/issue/view.ts index 80828429..2d1a7c02 100644 --- a/src/commands/issue/view.ts +++ b/src/commands/issue/view.ts @@ -12,6 +12,7 @@ import { buildCommand } from "../../lib/command.js"; import { formatEventDetails, formatIssueDetails, + isPlainOutput, muted, } from "../../lib/formatters/index.js"; import { CommandOutput } from "../../lib/formatters/output.js"; @@ -159,11 +160,11 @@ export const viewCommand = buildCommand({ if (spanTreeResult) { spanTreeLines = spanTreeResult.lines; } else if (!orgSlug) { - spanTreeLines = [ - muted("\nOrganization context required to fetch span tree."), - ]; + const msg = "\nOrganization context required to fetch span tree."; + spanTreeLines = [isPlainOutput() ? msg : muted(msg)]; } else if (!event) { - spanTreeLines = [muted("\nCould not fetch event to display span tree.")]; + const msg = "\nCould not fetch event to display span tree."; + spanTreeLines = [isPlainOutput() ? msg : muted(msg)]; } const trace = spanTreeResult?.success